Class: Redis::Lock
- Inherits:
-
Object
- Object
- Redis::Lock
- Defined in:
- lib/redis-lock.rb,
lib/redis-lock/version.rb
Defined Under Namespace
Classes: LockError
Constant Summary collapse
- VERSION =
"0.0.1"
Instance Attribute Summary collapse
-
#acquire_timeout ⇒ Object
readonly
Returns the value of attribute acquire_timeout.
-
#before_delete_callback ⇒ Object
Returns the value of attribute before_delete_callback.
-
#before_extend_callback ⇒ Object
Returns the value of attribute before_extend_callback.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#lock_duration ⇒ Object
readonly
Returns the value of attribute lock_duration.
-
#lockname ⇒ Object
readonly
Returns the value of attribute lockname.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#redis ⇒ Object
readonly
Returns the value of attribute redis.
Instance Method Summary collapse
- #acquire_lock ⇒ Object
- #add_expiration ⇒ Object
- #extend_lock(extend_by = 10) ⇒ Object
-
#initialize(redis, lock_name, options = {}) ⇒ Lock
constructor
A new instance of Lock.
- #lock(&block) ⇒ Object
- #lock_owner? ⇒ Boolean
- #locked? ⇒ Boolean
- #log(level, message) ⇒ Object
- #missing_expiration? ⇒ Boolean
- #release_lock ⇒ Object
- #release_with_watch(&block) ⇒ Object
- #unlock ⇒ Object
- #with_watch(&block) ⇒ Object
Constructor Details
#initialize(redis, lock_name, options = {}) ⇒ Lock
Returns a new instance of Lock.
21 22 23 24 25 26 27 28 29 30 |
# File 'lib/redis-lock.rb', line 21 def initialize(redis, lock_name, = {}) @redis = redis @lockname = "lock:#{lock_name}" @acquire_timeout = [:acquire_timeout] || 5 @lock_duration = [:lock_duration] || 10 @logger = [:logger] # generate a unique UUID for this lock @id = SecureRandom.uuid end |
Instance Attribute Details
#acquire_timeout ⇒ Object (readonly)
Returns the value of attribute acquire_timeout.
15 16 17 |
# File 'lib/redis-lock.rb', line 15 def acquire_timeout @acquire_timeout end |
#before_delete_callback ⇒ Object
Returns the value of attribute before_delete_callback.
18 19 20 |
# File 'lib/redis-lock.rb', line 18 def before_delete_callback @before_delete_callback end |
#before_extend_callback ⇒ Object
Returns the value of attribute before_extend_callback.
19 20 21 |
# File 'lib/redis-lock.rb', line 19 def before_extend_callback @before_extend_callback end |
#id ⇒ Object (readonly)
Returns the value of attribute id.
13 14 15 |
# File 'lib/redis-lock.rb', line 13 def id @id end |
#lock_duration ⇒ Object (readonly)
Returns the value of attribute lock_duration.
16 17 18 |
# File 'lib/redis-lock.rb', line 16 def lock_duration @lock_duration end |
#lockname ⇒ Object (readonly)
Returns the value of attribute lockname.
14 15 16 |
# File 'lib/redis-lock.rb', line 14 def lockname @lockname end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
17 18 19 |
# File 'lib/redis-lock.rb', line 17 def logger @logger end |
#redis ⇒ Object (readonly)
Returns the value of attribute redis.
12 13 14 |
# File 'lib/redis-lock.rb', line 12 def redis @redis end |
Instance Method Details
#acquire_lock ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/redis-lock.rb', line 51 def acquire_lock try_until = Time.now + acquire_timeout # loop until now + timeout trying to get the lock while Time.now < try_until log :debug, "attempting to acquire lock #{lockname}" # try and obtain the lock if redis.setnx(lockname, id) log :info, "lock #{lockname} acquired for #{id}" # lock was obtained, so add an expiration add_expiration return true elsif missing_expiration? # if no expiration, client that obtained lock likely crashed - add an expiration # and wait log :debug, "expiration missing on lock #{lockname}" add_expiration end # didn't get the lock, sleep briefly and try again sleep(0.001) end # was never able to get the lock - give up return false end |
#add_expiration ⇒ Object
150 151 152 153 |
# File 'lib/redis-lock.rb', line 150 def add_expiration() log :debug, "adding expiration of #{lock_duration} seconds to #{lockname}" redis.expire(lockname, lock_duration) end |
#extend_lock(extend_by = 10) ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/redis-lock.rb', line 79 def extend_lock(extend_by = 10) begin with_watch do if lock_owner? log :debug, "we are the lock owner - extending lock by #{extend_by} seconds" # check if we want to do a callback if before_extend_callback log :debug, "calling callback" before_extend_callback.call(redis) end redis.multi do |multi| multi.expire lockname, extend_by end # we extended the lock, return the lock return self end log :debug, "we aren't the lock owner - raising LockError" # we aren't the lock owner anymore - raise LockError raise LockError.new("unable to extend #{lockname} - no longer the lock owner") end rescue LockError => e raise e rescue StandardError => e log :warn, "#{lockname} changed while attempting to release key - retrying" # try extending the lock again, just in case extend_lock extend_by end end |
#lock(&block) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/redis-lock.rb', line 32 def lock(&block) acquire_lock or raise LockError.new(lockname) if block begin block.call(self) ensure release_lock end end self end |
#lock_owner? ⇒ Boolean
155 156 157 158 |
# File 'lib/redis-lock.rb', line 155 def lock_owner? log :debug, "our id: #{id} - lock owner: #{redis.get(lockname)}" redis.get(lockname) == id end |
#locked? ⇒ Boolean
142 143 144 |
# File 'lib/redis-lock.rb', line 142 def locked? lock_owner? end |
#log(level, message) ⇒ Object
180 181 182 183 184 |
# File 'lib/redis-lock.rb', line 180 def log(level, ) if logger logger.send(level) { } end end |
#missing_expiration? ⇒ Boolean
146 147 148 |
# File 'lib/redis-lock.rb', line 146 def missing_expiration? redis.ttl(lockname) == -1 end |
#release_lock ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/redis-lock.rb', line 113 def release_lock # we are going to watch the lock key while attempting to remove it, so we can # retry removing the lock if the lock is changed while we are removing it. release_with_watch do log :debug, "releasing #{lockname}..." # make sure we still own the lock if lock_owner? log :debug, "we are the lock owner" # check if we want to do a callback if before_delete_callback log :debug, "calling callback" before_delete_callback.call(redis) end redis.multi do |multi| multi.del lockname end return true end # we weren't the owner of the lock anymore - just return return false end end |
#release_with_watch(&block) ⇒ Object
160 161 162 163 164 165 166 167 168 169 |
# File 'lib/redis-lock.rb', line 160 def release_with_watch(&block) with_watch do begin block.call rescue => e log :warn, "#{lockname} changed while attempting to release key - retrying" release_with_watch &block end end end |
#unlock ⇒ Object
46 47 48 49 |
# File 'lib/redis-lock.rb', line 46 def unlock release_lock self end |
#with_watch(&block) ⇒ Object
171 172 173 174 175 176 177 178 |
# File 'lib/redis-lock.rb', line 171 def with_watch(&block) redis.watch lockname begin block.call ensure redis.unwatch end end |