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
-
#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 = {}) ⇒ 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
116 117 118 119 |
# File 'lib/redis/mutex.rb', line 116 def lock(object, = {}) raise_assertion_error if block_given? new(object, ).lock end |
.lock!(object, options = {}) ⇒ Object
121 122 123 |
# File 'lib/redis/mutex.rb', line 121 def lock!(object, = {}) new(object, ).lock! end |
.raise_assertion_error ⇒ Object
129 130 131 |
# File 'lib/redis/mutex.rb', line 129 def raise_assertion_error raise AssertionError, 'block syntax has been removed from #lock, use #with_lock instead' end |
.sweep ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/redis/mutex.rb', line 98 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
125 126 127 |
# File 'lib/redis/mutex.rb', line 125 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
89 90 91 |
# File 'lib/redis/mutex.rb', line 89 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
61 62 63 |
# File 'lib/redis/mutex.rb', line 61 def locked? get.to_f > Time.now.to_f end |
#try_lock ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/redis/mutex.rb', line 45 def try_lock now = Time.now.to_f @expires_at = now + @expire # Extend in each blocking loop loop do 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
65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/redis/mutex.rb', line 65 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
93 94 95 |
# File 'lib/redis/mutex.rb', line 93 def unlock!(force = false) unlock(force) or raise UnlockError, "failed to release lock #{key.inspect}" end |
#with_lock ⇒ Object
78 79 80 81 82 83 84 85 86 87 |
# File 'lib/redis/mutex.rb', line 78 def with_lock if lock! begin @result = yield ensure unlock end end @result end |