Class: SimpleMutex::Mutex
- Inherits:
-
Object
- Object
- SimpleMutex::Mutex
- Defined in:
- lib/simple_mutex/mutex.rb
Constant Summary collapse
- DEFAULT_EXPIRES_IN =
1 hour
60 * 60
- MAX_DEL_ATTEMPTS =
3- ERROR_MESSAGES =
{ unlock: { unknown: lambda do |lock_key| "something when wrong when deleting lock key <#{lock_key}>." end, key_not_found: lambda do |lock_key| "lock not found for lock key <#{lock_key}>." end, signature_mismatch: lambda do |lock_key| "signature mismatch for lock key <#{lock_key}>." end, repeated_synchronization_anomaly: lambda do |lock_key| "repeated synchronization anomaly for <#{lock_key}>." end, }.freeze, lock: { basic: lambda do |lock_key| "failed to acquire lock <#{lock_key}>." end, }.freeze, }.freeze
- SynchronizationAnomalyError =
Class.new(::StandardError)
- BaseError =
Class.new(::StandardError) do attr_reader :lock_key def initialize(msg, lock_key) @lock_key = lock_key super(msg) end end
- LockError =
Class.new(BaseError)
- UnlockError =
Class.new(BaseError)
Class Attribute Summary collapse
-
.redis ⇒ Object
Returns the value of attribute redis.
Instance Attribute Summary collapse
-
#expires_in ⇒ Object
readonly
Returns the value of attribute expires_in.
-
#lock_key ⇒ Object
readonly
Returns the value of attribute lock_key.
-
#payload ⇒ Object
readonly
Returns the value of attribute payload.
-
#signature ⇒ Object
readonly
Returns the value of attribute signature.
Class Method Summary collapse
- .lock(lock_key, **options) ⇒ Object
- .lock!(lock_key, **options) ⇒ Object
- .raise_error(error_class, msg_template, lock_key) ⇒ Object
- .signature_valid?(raw_data, signature) ⇒ Boolean
- .unlock(lock_key, signature: nil, force: false) ⇒ Object
-
.unlock!(lock_key, signature: nil, force: false) ⇒ Object
rubocop:disable Metrics/MethodLength.
-
.with_lock(lock_key, **options, &block) ⇒ Object
rubocop:enable Metrics/MethodLength.
Instance Method Summary collapse
-
#initialize(lock_key, expires_in: DEFAULT_EXPIRES_IN, signature: SecureRandom.uuid, payload: nil) ⇒ Mutex
constructor
A new instance of Mutex.
- #lock ⇒ Object
- #lock! ⇒ Object
- #lock_obtained? ⇒ Boolean
- #unlock(force: false) ⇒ Object
- #unlock!(force: false) ⇒ Object
- #with_lock ⇒ Object
Constructor Details
#initialize(lock_key, expires_in: DEFAULT_EXPIRES_IN, signature: SecureRandom.uuid, payload: nil) ⇒ Mutex
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/simple_mutex/mutex.rb', line 142 def initialize(lock_key, expires_in: DEFAULT_EXPIRES_IN, signature: SecureRandom.uuid, payload: nil) ::SimpleMutex.redis_check! self.lock_key = lock_key self.expires_in = expires_in.to_i self.signature = signature self.payload = payload end |
Class Attribute Details
.redis ⇒ Object
Returns the value of attribute redis.
48 49 50 |
# File 'lib/simple_mutex/mutex.rb', line 48 def redis @redis end |
Instance Attribute Details
#expires_in ⇒ Object
Returns the value of attribute expires_in.
140 141 142 |
# File 'lib/simple_mutex/mutex.rb', line 140 def expires_in @expires_in end |
#lock_key ⇒ Object
Returns the value of attribute lock_key.
140 141 142 |
# File 'lib/simple_mutex/mutex.rb', line 140 def lock_key @lock_key end |
#payload ⇒ Object
Returns the value of attribute payload.
140 141 142 |
# File 'lib/simple_mutex/mutex.rb', line 140 def payload @payload end |
#signature ⇒ Object
Returns the value of attribute signature.
140 141 142 |
# File 'lib/simple_mutex/mutex.rb', line 140 def signature @signature end |
Class Method Details
.lock(lock_key, **options) ⇒ Object
50 51 52 |
# File 'lib/simple_mutex/mutex.rb', line 50 def lock(lock_key, **) new(lock_key, **).lock end |
.lock!(lock_key, **options) ⇒ Object
54 55 56 |
# File 'lib/simple_mutex/mutex.rb', line 54 def lock!(lock_key, **) new(lock_key, **).lock! end |
.raise_error(error_class, msg_template, lock_key) ⇒ Object
124 125 126 127 128 129 |
# File 'lib/simple_mutex/mutex.rb', line 124 def raise_error(error_class, msg_template, lock_key) template_base = error_class.name.split("::").last.gsub("Error", "").downcase.to_sym error_msg = ERROR_MESSAGES[template_base][msg_template].call(lock_key) raise(error_class.new(error_msg, lock_key)) end |
.signature_valid?(raw_data, signature) ⇒ Boolean
131 132 133 134 135 136 137 |
# File 'lib/simple_mutex/mutex.rb', line 131 def signature_valid?(raw_data, signature) return false if raw_data.nil? JSON.parse(raw_data)["signature"] == signature rescue JSON::ParserError, TypeError false end |
.unlock(lock_key, signature: nil, force: false) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/simple_mutex/mutex.rb', line 58 def unlock(lock_key, signature: nil, force: false) ::SimpleMutex.redis_check! redis = ::SimpleMutex.redis attempt = 0 begin redis.watch(lock_key) do raw_data = redis.get(lock_key) raw_data = raw_data.value if raw_data.is_a?(Redis::Future) return false if raw_data.nil? return false unless force || signature_valid?(raw_data, signature) result = redis.multi { |multi| multi.del(lock_key) }.first raise SynchronizationAnomalyError, "Sync anomaly." unless result.is_a?(Integer) result.positive? ensure redis.unwatch end rescue SynchronizationAnomalyError retry if (attempt += 1) < MAX_DEL_ATTEMPTS raise_error(UnlockError, :repeated_synchronization_anomaly, lock_key) end end |
.unlock!(lock_key, signature: nil, force: false) ⇒ Object
rubocop:disable Metrics/MethodLength
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/simple_mutex/mutex.rb', line 88 def unlock!(lock_key, signature: nil, force: false) ::SimpleMutex.redis_check! redis = ::SimpleMutex.redis attempt = 0 begin redis.watch(lock_key) do raw_data = redis.get(lock_key) raw_data = raw_data.value if raw_data.is_a?(Redis::Future) raise_error(UnlockError, :key_not_found, lock_key) if raw_data.nil? unless force || signature_valid?(raw_data, signature) raise_error(UnlockError, :signature_mismatch, lock_key) end result = redis.multi { |multi| multi.del(lock_key) }.first raise SynchronizationAnomalyError, "Sync anomaly." unless result.is_a?(Integer) raise_error(UnlockError, :unknown, lock_key) unless result.positive? ensure redis.unwatch end rescue SynchronizationAnomalyError retry if (attempt += 1) < MAX_DEL_ATTEMPTS raise_error(UnlockError, :repeated_synchronization_anomaly, lock_key) end end |
.with_lock(lock_key, **options, &block) ⇒ Object
rubocop:enable Metrics/MethodLength
120 121 122 |
# File 'lib/simple_mutex/mutex.rb', line 120 def with_lock(lock_key, **, &block) new(lock_key, **).with_lock(&block) end |
Instance Method Details
#lock ⇒ Object
154 155 156 |
# File 'lib/simple_mutex/mutex.rb', line 154 def lock !!redis.set(lock_key, generate_data, nx: true, ex: expires_in) end |
#lock! ⇒ Object
176 177 178 |
# File 'lib/simple_mutex/mutex.rb', line 176 def lock! lock or raise_error(LockError, :basic) end |
#lock_obtained? ⇒ Boolean
172 173 174 |
# File 'lib/simple_mutex/mutex.rb', line 172 def lock_obtained? self.class.signature_valid?(redis.get(lock_key), signature) end |
#unlock(force: false) ⇒ Object
158 159 160 |
# File 'lib/simple_mutex/mutex.rb', line 158 def unlock(force: false) self.class.unlock(lock_key, signature: signature, force: force) end |
#unlock!(force: false) ⇒ Object
180 181 182 |
# File 'lib/simple_mutex/mutex.rb', line 180 def unlock!(force: false) self.class.unlock!(lock_key, signature: signature, force: force) end |
#with_lock ⇒ Object
162 163 164 165 166 167 168 169 170 |
# File 'lib/simple_mutex/mutex.rb', line 162 def with_lock lock! begin yield ensure unlock end end |