Class: Redis::Mutex
- Inherits:
-
Classy
- Object
- Classy
- Redis::Mutex
- Defined in:
- lib/redis/mutex.rb,
lib/redis/mutex/macro.rb
Overview
Options
:block => Specify in seconds how long you want to wait for the lock to be released. Speficy 0
if you need non-blocking sematics and return false immediately. (default: 1)
:sleep => Specify in seconds how long the polling interval should be when :block is given.
It is recommended that you do NOT go below 0.01. (default: 0.1)
:expire => Specify in seconds when the lock should forcibly be removed when something went wrong
with the one who held the lock. (default: 10)
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 = {}) ⇒ Mutex
constructor
A new instance of Mutex.
- #lock ⇒ Object
- #lock! ⇒ Object
- #try_lock ⇒ Object
- #unlock(force = false) ⇒ Object
- #unlock!(force = false) ⇒ Object
- #with_lock ⇒ Object
Constructor Details
#initialize(object, options = {}) ⇒ Mutex
Returns a new instance of Mutex.
20 21 22 23 24 25 |
# File 'lib/redis/mutex.rb', line 20 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
107 108 109 110 |
# File 'lib/redis/mutex.rb', line 107 def lock(object, = {}) raise_assertion_error if block_given? new(object, ).lock end |
.lock!(object, options = {}) ⇒ Object
112 113 114 |
# File 'lib/redis/mutex.rb', line 112 def lock!(object, = {}) new(object, ).lock! end |
.raise_assertion_error ⇒ Object
120 121 122 |
# File 'lib/redis/mutex.rb', line 120 def raise_assertion_error raise AssertionError, 'block syntax has been removed from #lock, use #with_lock instead' end |
.sweep ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/redis/mutex.rb', line 89 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
116 117 118 |
# File 'lib/redis/mutex.rb', line 116 def with_lock(object, = {}, &block) new(object, ).with_lock(&block) end |
Instance Method Details
#lock ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/redis/mutex.rb', line 27 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
80 81 82 |
# File 'lib/redis/mutex.rb', line 80 def lock! lock or raise LockError, "failed to acquire lock #{key.inspect}" end |
#try_lock ⇒ Object
45 46 47 48 49 50 51 52 53 54 |
# File 'lib/redis/mutex.rb', line 45 def try_lock now = Time.now.to_f @expires_at = now + @expire # Extend in each blocking loop return true if setnx(@expires_at) # Success, the lock has been acquired return false if get.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
56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/redis/mutex.rb', line 56 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
84 85 86 |
# File 'lib/redis/mutex.rb', line 84 def unlock!(force = false) unlock(force) or raise UnlockError, "failed to release lock #{key.inspect}" end |
#with_lock ⇒ Object
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/redis/mutex.rb', line 69 def with_lock if lock! begin @result = yield ensure unlock end end @result end |