Class: RedisMutex
- Inherits:
-
RedisClassy
- Object
- RedisClassy
- RedisMutex
- Defined in:
- lib/redis_mutex.rb,
lib/redis_mutex/macro.rb
Defined Under Namespace
Modules: Macro
Constant Summary collapse
- DEFAULT_EXPIRE =
10
- LockError =
Class.new(StandardError)
- UnlockError =
Class.new(StandardError)
- AssertionError =
Class.new(StandardError)
Class Method Summary collapse
- .lock(object, options = {}) ⇒ Object
- .lock!(object, options = {}) ⇒ Object
- .raise_assertion_error ⇒ Object
- .sweep ⇒ Object
- .with_lock(object, options = {}, &block) ⇒ Object
Instance Method Summary collapse
-
#initialize(object, options = {}) ⇒ RedisMutex
constructor
A new instance of RedisMutex.
- #lock ⇒ Object
- #lock! ⇒ Object
-
#locked? ⇒ Boolean
Returns true if resource is locked.
- #try_lock ⇒ Object
- #unlock(force = false) ⇒ Object
- #unlock!(force = false) ⇒ Object
- #with_lock ⇒ Object
Constructor Details
#initialize(object, options = {}) ⇒ RedisMutex
Returns a new instance of RedisMutex.
19 20 21 22 23 24 |
# File 'lib/redis_mutex.rb', line 19 def initialize(object, ={}) super(object.is_a?(String) || object.is_a?(Symbol) ? object : "#{object.class.name}:#{object.id}") @block = [:block] || 1 @sleep = [:sleep] || 0.1 @expire = [:expire] || DEFAULT_EXPIRE end |
Class Method Details
.lock(object, options = {}) ⇒ Object
115 116 117 118 |
# File 'lib/redis_mutex.rb', line 115 def lock(object, = {}) raise_assertion_error if block_given? new(object, ).lock end |
.lock!(object, options = {}) ⇒ Object
120 121 122 |
# File 'lib/redis_mutex.rb', line 120 def lock!(object, = {}) new(object, ).lock! end |
.raise_assertion_error ⇒ Object
128 129 130 |
# File 'lib/redis_mutex.rb', line 128 def raise_assertion_error raise AssertionError, 'block syntax has been removed from #lock, use #with_lock instead' end |
.sweep ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/redis_mutex.rb', line 97 def sweep return 0 if (all_keys = keys).empty? now = Time.now.to_f values = mget(*all_keys) expired_keys = all_keys.zip(values).select do |key, time| time && time.to_f <= now end expired_keys.each do |key, _| # Make extra sure that anyone haven't extended the lock del(key) if getset(key, now + DEFAULT_EXPIRE).to_f <= now end expired_keys.size end |
.with_lock(object, options = {}, &block) ⇒ Object
124 125 126 |
# File 'lib/redis_mutex.rb', line 124 def with_lock(object, = {}, &block) new(object, ).with_lock(&block) end |
Instance Method Details
#lock ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/redis_mutex.rb', line 26 def lock self.class.raise_assertion_error if block_given? @locking = false if @block > 0 # Blocking mode start_at = Time.now while Time.now - start_at < @block @locking = true and break if try_lock sleep @sleep end else # Non-blocking mode @locking = try_lock end @locking end |
#lock! ⇒ Object
88 89 90 |
# File 'lib/redis_mutex.rb', line 88 def lock! lock or raise LockError, "failed to acquire lock #{key.inspect}" end |
#locked? ⇒ Boolean
Returns true if resource is locked. Note that nil.to_f returns 0.0
60 61 62 |
# File 'lib/redis_mutex.rb', line 60 def locked? get.to_f > Time.now.to_f end |
#try_lock ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/redis_mutex.rb', line 44 def try_lock now = Time.now.to_f @expires_at = now + @expire # Extend in each blocking loop begin return true if setnx(@expires_at) # Success, the lock has been acquired end until old_value = get # Repeat if unlocked before get return false if old_value.to_f > now # Check if the lock is still effective # The lock has expired but wasn't released... BAD! return true if getset(@expires_at).to_f <= now # Success, we acquired the previously expired lock return false # Dammit, it seems that someone else was even faster than us to remove the expired lock! end |
#unlock(force = false) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/redis_mutex.rb', line 64 def unlock(force = false) # Since it's possible that the operations in the critical section took a long time, # we can't just simply release the lock. The unlock method checks if @expires_at # remains the same, and do not release when the lock timestamp was overwritten. if get == @expires_at.to_s or force # Redis#del with a single key returns '1' or nil !!del else false end end |
#unlock!(force = false) ⇒ Object
92 93 94 |
# File 'lib/redis_mutex.rb', line 92 def unlock!(force = false) unlock(force) or raise UnlockError, "failed to release lock #{key.inspect}" end |
#with_lock ⇒ Object
77 78 79 80 81 82 83 84 85 86 |
# File 'lib/redis_mutex.rb', line 77 def with_lock if lock! begin @result = yield ensure unlock end end @result end |