redis-em-mutex 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +9 -0
- data/README.rdoc +92 -2
- data/lib/redis/em-mutex/ns.rb +47 -0
- data/lib/redis/em-mutex/version.rb +1 -1
- data/lib/redis/em-mutex.rb +215 -132
- data/spec/redis-em-mutex-condition.rb +162 -0
- data/spec/redis-em-mutex-namespaces.rb +16 -16
- data/spec/redis-em-mutex-owners.rb +156 -0
- data/spec/redis-em-mutex-semaphores.rb +146 -76
- metadata +19 -14
data/lib/redis/em-mutex.rb
CHANGED
@@ -22,6 +22,7 @@ class Redis
|
|
22
22
|
#
|
23
23
|
class Mutex
|
24
24
|
|
25
|
+
autoload :NS, 'redis/em-mutex/ns'
|
25
26
|
autoload :Macro, 'redis/em-mutex/macro'
|
26
27
|
|
27
28
|
module Errors
|
@@ -46,62 +47,34 @@ class Redis
|
|
46
47
|
@@ns = nil
|
47
48
|
@@uuid = nil
|
48
49
|
|
49
|
-
|
50
|
-
attr_reader :names, :ns
|
50
|
+
attr_reader :names, :ns, :block_timeout
|
51
51
|
alias_method :namespace, :ns
|
52
52
|
|
53
|
-
|
54
|
-
attr_reader :ns
|
55
|
-
alias_method :namespace, :ns
|
56
|
-
# Creates a new namespace (Mutex factory).
|
57
|
-
#
|
58
|
-
# - ns = namespace
|
59
|
-
# - opts = options hash:
|
60
|
-
# - :block - default block timeout
|
61
|
-
# - :expire - default expire timeout
|
62
|
-
def initialize(ns, opts = {})
|
63
|
-
@ns = ns
|
64
|
-
@opts = (opts || {}).merge(:ns => ns)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Creates a namespaced cross machine/process/fiber semaphore.
|
68
|
-
#
|
69
|
-
# for arguments see: Redis::EM::Mutex.new
|
70
|
-
def new(*args)
|
71
|
-
if args.last.kind_of?(Hash)
|
72
|
-
args[-1] = @opts.merge(args.last)
|
73
|
-
else
|
74
|
-
args.push @opts
|
75
|
-
end
|
76
|
-
Redis::EM::Mutex.new(*args)
|
77
|
-
end
|
53
|
+
def expire_timeout; @expire_timeout || @@default_expire; end
|
78
54
|
|
79
|
-
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
mutex = new(*args)
|
84
|
-
mutex if mutex.lock
|
85
|
-
end
|
55
|
+
def expire_timeout=(value)
|
56
|
+
raise ArgumentError, "#{self.class.name}\#expire_timeout value must be greater than 0" unless (value = value.to_f) > 0
|
57
|
+
@expire_timeout = value
|
58
|
+
end
|
86
59
|
|
87
|
-
|
88
|
-
|
89
|
-
# See: Redis::EM::Mutex.synchronize
|
90
|
-
def synchronize(*args, &block)
|
91
|
-
new(*args).synchronize(&block)
|
92
|
-
end
|
60
|
+
def block_timeout=(value)
|
61
|
+
@block_timeout = value.nil? ? nil : value.to_f
|
93
62
|
end
|
94
63
|
|
95
64
|
# Creates a new cross machine/process/fiber semaphore
|
96
65
|
#
|
97
|
-
# Redis::EM::Mutex.new(*names,
|
66
|
+
# Redis::EM::Mutex.new(*names, options = {})
|
98
67
|
#
|
99
68
|
# - *names = lock identifiers - if none they are auto generated
|
100
|
-
# -
|
69
|
+
# - options = hash:
|
101
70
|
# - :name - same as *names (in case *names arguments were omitted)
|
102
71
|
# - :block - default block timeout
|
103
72
|
# - :expire - default expire timeout (see: Mutex#lock and Mutex#try_lock)
|
104
73
|
# - :ns - local namespace (otherwise global namespace is used)
|
74
|
+
# - :owner - owner definition instead of Fiber#__id__
|
75
|
+
#
|
76
|
+
# Raises MutexError if used before Mutex.setup.
|
77
|
+
# Raises ArgumentError on invalid options.
|
105
78
|
def initialize(*args)
|
106
79
|
raise MutexError, "call #{self.class}::setup first" unless @@redis_pool
|
107
80
|
|
@@ -109,13 +82,23 @@ class Redis
|
|
109
82
|
|
110
83
|
@names = args
|
111
84
|
@names = Array(opts[:name] || "#{@@name_index.succ!}.lock") if @names.empty?
|
112
|
-
|
85
|
+
@slept = {}
|
86
|
+
raise ArgumentError, "semaphore names must not be empty" if @names.empty?
|
113
87
|
@multi = !@names.one?
|
114
88
|
@ns = opts[:ns] || @@ns
|
115
89
|
@ns_names = @ns ? @names.map {|n| "#@ns:#{n}".freeze }.freeze : @names.map {|n| n.to_s.dup.freeze }.freeze
|
116
|
-
|
117
|
-
|
118
|
-
@locked_id = nil
|
90
|
+
self.expire_timeout = opts[:expire] if opts.key?(:expire)
|
91
|
+
self.block_timeout = opts[:block] if opts.key?(:block)
|
92
|
+
@locked_owner_id = @locked_id = nil
|
93
|
+
if (owner = opts[:owner])
|
94
|
+
self.define_singleton_method(:owner_ident) do |lock_id = nil|
|
95
|
+
if lock_id
|
96
|
+
"#@@uuid$#$$@#{owner} #{lock_id}"
|
97
|
+
else
|
98
|
+
"#@@uuid$#$$@#{owner}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
119
102
|
end
|
120
103
|
|
121
104
|
# Returns `true` if this semaphore (at least one of locked `names`) is currently being held by some owner.
|
@@ -129,49 +112,92 @@ class Redis
|
|
129
112
|
end
|
130
113
|
end
|
131
114
|
|
132
|
-
# Returns `true` if this semaphore (all the locked `names`) is currently being held by calling
|
133
|
-
#
|
115
|
+
# Returns `true` if this semaphore (all the locked `names`) is currently being held by calling owner.
|
116
|
+
# This is the method you should use to check if lock is still held and valid.
|
134
117
|
def owned?
|
135
|
-
!!if @locked_id
|
136
|
-
lock_full_ident = owner_ident(@locked_id)
|
118
|
+
!!if @locked_id && owner_ident(@locked_id) == (lock_full_ident = @locked_owner_id)
|
137
119
|
@@redis_pool.mget(*@ns_names).all? {|v| v == lock_full_ident}
|
138
120
|
end
|
139
121
|
end
|
140
122
|
|
123
|
+
# Returns `true` when the semaphore is being held and have already expired.
|
124
|
+
# Returns `false` when the semaphore is still locked and valid
|
125
|
+
# or `nil` if the semaphore wasn't locked by current owner.
|
126
|
+
#
|
127
|
+
# The check is performed only on the Mutex object instance and should only be used as a hint.
|
128
|
+
# For reliable lock status information use #refresh or #owned? instead.
|
129
|
+
def expired?
|
130
|
+
Time.now.to_f > @locked_id.to_f if @locked_id && owner_ident(@locked_id) == @locked_owner_id
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the number of seconds left until the semaphore expires.
|
134
|
+
# The number of seconds less than 0 means that the semaphore expired and could be grabbed
|
135
|
+
# by some other owner.
|
136
|
+
# Returns `nil` if the semaphore wasn't locked by current owner.
|
137
|
+
#
|
138
|
+
# The check is performed only on the Mutex object instance and should only be used as a hint.
|
139
|
+
# For reliable lock status information use #refresh or #owned? instead.
|
140
|
+
def expires_in
|
141
|
+
@locked_id.to_f - Time.now.to_f if @locked_id && owner_ident(@locked_id) == @locked_owner_id
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns local time at which the semaphore will expire or have expired.
|
145
|
+
# Returns `nil` if the semaphore wasn't locked by current owner.
|
146
|
+
#
|
147
|
+
# The check is performed only on the Mutex object instance and should only be used as a hint.
|
148
|
+
# For reliable lock status information use #refresh or #owned? instead.
|
149
|
+
def expires_at
|
150
|
+
Time.at(@locked_id.to_f) if @locked_id && owner_ident(@locked_id) == @locked_owner_id
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns timestamp at which the semaphore will expire or have expired.
|
154
|
+
# Returns `nil` if the semaphore wasn't locked by current owner.
|
155
|
+
#
|
156
|
+
# The check is performed only on the Mutex object instance and should only be used as a hint.
|
157
|
+
# For reliable lock status information use #refresh or #owned? instead.
|
158
|
+
def expiration_timestamp
|
159
|
+
@locked_id.to_f if @locked_id && owner_ident(@locked_id) == @locked_owner_id
|
160
|
+
end
|
161
|
+
|
141
162
|
# Attempts to obtain the lock and returns immediately.
|
142
163
|
# Returns `true` if the lock was granted.
|
143
|
-
# Use Mutex#expire_timeout= to set
|
164
|
+
# Use Mutex#expire_timeout= to set lock expiration time in secods.
|
144
165
|
# Otherwise global Mutex.default_expire is used.
|
145
166
|
#
|
146
|
-
# This method
|
167
|
+
# This method doesn't capture expired semaphores
|
168
|
+
# and therefore it should not be used under normal circumstances.
|
147
169
|
# Use Mutex#lock with block_timeout = 0 to obtain expired lock without blocking.
|
148
170
|
def try_lock
|
149
|
-
lock_id = (Time.now +
|
171
|
+
lock_id = (Time.now + expire_timeout).to_f.to_s
|
172
|
+
lock_full_ident = owner_ident(lock_id)
|
150
173
|
!!if @multi
|
151
|
-
lock_full_ident = owner_ident(lock_id)
|
152
174
|
if @@redis_pool.msetnx(*@ns_names.map {|k| [k, lock_full_ident]}.flatten)
|
153
175
|
@locked_id = lock_id
|
176
|
+
@locked_owner_id = lock_full_ident
|
154
177
|
end
|
155
|
-
elsif @@redis_pool.setnx(@ns_names.first,
|
178
|
+
elsif @@redis_pool.setnx(@ns_names.first, lock_full_ident)
|
156
179
|
@locked_id = lock_id
|
180
|
+
@locked_owner_id = lock_full_ident
|
157
181
|
end
|
158
182
|
end
|
159
183
|
|
160
184
|
# Refreshes lock expiration timeout.
|
161
|
-
# Returns true if refresh was successfull
|
185
|
+
# Returns `true` if refresh was successfull.
|
186
|
+
# Returns `false` if the semaphore wasn't locked or when it was locked but it has expired
|
187
|
+
# and now it's got a new owner.
|
162
188
|
def refresh(expire_timeout=nil)
|
163
189
|
ret = false
|
164
|
-
if @locked_id
|
165
|
-
new_lock_id = (Time.now + (expire_timeout.to_f.nonzero? ||
|
190
|
+
if @locked_id && owner_ident(@locked_id) == (lock_full_ident = @locked_owner_id)
|
191
|
+
new_lock_id = (Time.now + (expire_timeout.to_f.nonzero? || self.expire_timeout)).to_f.to_s
|
166
192
|
new_lock_full_ident = owner_ident(new_lock_id)
|
167
|
-
lock_full_ident = owner_ident(@locked_id)
|
168
193
|
@@redis_pool.execute(false) do |r|
|
169
194
|
r.watch(*@ns_names) do
|
170
195
|
if r.mget(*@ns_names).all? {|v| v == lock_full_ident}
|
171
|
-
|
172
|
-
|
196
|
+
if r.multi {|m| m.mset(*@ns_names.map {|k| [k, new_lock_full_ident]}.flatten)}
|
197
|
+
@locked_id = new_lock_id
|
198
|
+
@locked_owner_id = new_lock_full_ident
|
199
|
+
ret = true
|
173
200
|
end
|
174
|
-
@locked_id = new_lock_id if ret
|
175
201
|
else
|
176
202
|
r.unwatch
|
177
203
|
end
|
@@ -181,16 +207,16 @@ class Redis
|
|
181
207
|
ret
|
182
208
|
end
|
183
209
|
|
184
|
-
# Releases the lock
|
185
|
-
#
|
186
|
-
#
|
187
|
-
def unlock
|
188
|
-
|
189
|
-
|
210
|
+
# Releases the lock. Returns self on success.
|
211
|
+
# Returns `false` if the semaphore wasn't locked or when it was locked but it has expired
|
212
|
+
# and now it's got a new owner.
|
213
|
+
def unlock!
|
214
|
+
ret = false
|
215
|
+
if @locked_id && owner_ident(@locked_id) == (lock_full_ident = @locked_owner_id)
|
190
216
|
@@redis_pool.execute(false) do |r|
|
191
217
|
r.watch(*@ns_names) do
|
192
218
|
if r.mget(*@ns_names).all? {|v| v == lock_full_ident}
|
193
|
-
r.multi do |multi|
|
219
|
+
ret = !!r.multi do |multi|
|
194
220
|
multi.del(*@ns_names)
|
195
221
|
multi.publish SIGNAL_QUEUE_CHANNEL, Marshal.dump(@ns_names)
|
196
222
|
end
|
@@ -199,7 +225,16 @@ class Redis
|
|
199
225
|
end
|
200
226
|
end
|
201
227
|
end
|
228
|
+
@locked_owner_id = @locked_id = nil
|
202
229
|
end
|
230
|
+
ret && self
|
231
|
+
end
|
232
|
+
|
233
|
+
# Releases the lock unconditionally.
|
234
|
+
# If the semaphore wasn’t locked by the current owner it is silently ignored.
|
235
|
+
# Returns self.
|
236
|
+
def unlock
|
237
|
+
unlock!
|
203
238
|
self
|
204
239
|
end
|
205
240
|
|
@@ -215,7 +250,7 @@ class Redis
|
|
215
250
|
# Use Mutex#expire_timeout= to set lock expiration timeout.
|
216
251
|
# Otherwise global Mutex.default_expire is used.
|
217
252
|
def lock(block_timeout = nil)
|
218
|
-
block_timeout||=
|
253
|
+
block_timeout||= self.block_timeout
|
219
254
|
names = @ns_names
|
220
255
|
timer = fiber = nil
|
221
256
|
try_again = false
|
@@ -223,62 +258,97 @@ class Redis
|
|
223
258
|
try_again = true
|
224
259
|
::EM.next_tick { fiber.resume if fiber } if fiber
|
225
260
|
end
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
261
|
+
begin
|
262
|
+
queues = names.map {|n| @@signal_queue[n] << handler }
|
263
|
+
ident_match = owner_ident
|
264
|
+
until try_lock
|
265
|
+
Mutex.start_watcher unless watching?
|
266
|
+
start_time = Time.now.to_f
|
267
|
+
expire_time = nil
|
268
|
+
@@redis_pool.execute(false) do |r|
|
269
|
+
r.watch(*names) do
|
270
|
+
expired_names = names.zip(r.mget(*names)).map do |name, lock_value|
|
271
|
+
if lock_value
|
272
|
+
owner, exp_id = lock_value.split ' '
|
273
|
+
exp_time = exp_id.to_f
|
274
|
+
expire_time = exp_time if expire_time.nil? || exp_time < expire_time
|
275
|
+
raise MutexError, "deadlock; recursive locking #{owner}" if owner == ident_match
|
276
|
+
if exp_time < start_time
|
277
|
+
name
|
278
|
+
end
|
242
279
|
end
|
243
280
|
end
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
281
|
+
if expire_time && expire_time < start_time
|
282
|
+
r.multi do |multi|
|
283
|
+
expired_names = expired_names.compact
|
284
|
+
multi.del(*expired_names)
|
285
|
+
multi.publish SIGNAL_QUEUE_CHANNEL, Marshal.dump(expired_names)
|
286
|
+
end
|
287
|
+
else
|
288
|
+
r.unwatch
|
250
289
|
end
|
251
|
-
else
|
252
|
-
r.unwatch
|
253
290
|
end
|
254
291
|
end
|
255
|
-
|
256
|
-
|
257
|
-
timeout = block_timeout if block_timeout && block_timeout < timeout
|
292
|
+
timeout = (expire_time = expire_time.to_f) - start_time
|
293
|
+
timeout = block_timeout if block_timeout && block_timeout < timeout
|
258
294
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
295
|
+
if !try_again && timeout > 0
|
296
|
+
timer = ::EM::Timer.new(timeout) do
|
297
|
+
timer = nil
|
298
|
+
::EM.next_tick { fiber.resume if fiber } if fiber
|
299
|
+
end
|
300
|
+
fiber = Fiber.current
|
301
|
+
Fiber.yield
|
302
|
+
fiber = nil
|
303
|
+
end
|
304
|
+
finish_time = Time.now.to_f
|
305
|
+
if try_again || finish_time > expire_time
|
306
|
+
block_timeout-= finish_time - start_time if block_timeout
|
307
|
+
try_again = false
|
308
|
+
else
|
309
|
+
return false
|
263
310
|
end
|
264
|
-
fiber = Fiber.current
|
265
|
-
Fiber.yield
|
266
|
-
fiber = nil
|
267
311
|
end
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
312
|
+
true
|
313
|
+
ensure
|
314
|
+
timer.cancel if timer
|
315
|
+
timer = nil
|
316
|
+
queues.each {|q| q.delete handler }
|
317
|
+
names.each {|n| @@signal_queue.delete(n) if @@signal_queue[n].empty? }
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Wakes up currently sleeping fiber on a mutex.
|
322
|
+
def wakeup(fiber)
|
323
|
+
fiber.resume if @slept.delete(fiber)
|
324
|
+
end
|
325
|
+
|
326
|
+
# for compatibility with EventMachine::Synchrony::Thread::ConditionVariable
|
327
|
+
alias_method :_wakeup, :wakeup
|
328
|
+
|
329
|
+
# Releases the lock and sleeps `timeout` seconds if it is given and non-nil or forever.
|
330
|
+
# Raises MutexError if mutex wasn’t locked by the current owner.
|
331
|
+
# Raises MutexTimeout if #block_timeout= was set and timeout
|
332
|
+
# occured while locking after sleep.
|
333
|
+
# If code block is provided it is executed after waking up, just before grabbing a lock.
|
334
|
+
def sleep(timeout = nil)
|
335
|
+
raise MutexError, "can't sleep #{self.class} wasn't locked" unless unlock!
|
336
|
+
start = Time.now
|
337
|
+
current = Fiber.current
|
338
|
+
@slept[current] = true
|
339
|
+
if timeout
|
340
|
+
timer = ::EM.add_timer(timeout) do
|
341
|
+
wakeup(current)
|
274
342
|
end
|
343
|
+
Fiber.yield
|
344
|
+
::EM.cancel_timer timer
|
345
|
+
else
|
346
|
+
Fiber.yield
|
275
347
|
end
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
queues.each {|q| q.delete handler }
|
281
|
-
names.each {|n| @@signal_queue.delete(n) if @@signal_queue[n].empty? }
|
348
|
+
@slept.delete current
|
349
|
+
yield if block_given?
|
350
|
+
raise MutexTimeout unless lock
|
351
|
+
Time.now - start
|
282
352
|
end
|
283
353
|
|
284
354
|
# Execute block of code protected with semaphore.
|
@@ -288,7 +358,7 @@ class Redis
|
|
288
358
|
# If `block_timeout` or Mutex#block_timeout is set and
|
289
359
|
# lock isn't obtained within `block_timeout` seconds this method raises
|
290
360
|
# MutexTimeout.
|
291
|
-
def synchronize(block_timeout
|
361
|
+
def synchronize(block_timeout=nil)
|
292
362
|
if lock(block_timeout)
|
293
363
|
begin
|
294
364
|
yield self
|
@@ -300,18 +370,27 @@ class Redis
|
|
300
370
|
end
|
301
371
|
end
|
302
372
|
|
373
|
+
# Returns true if watcher is connected
|
374
|
+
def watching?; @@watching == $$; end
|
375
|
+
|
376
|
+
# Returns true if watcher is connected
|
377
|
+
def self.watching?; @@watching == $$; end
|
378
|
+
|
303
379
|
class << self
|
304
380
|
def ns; @@ns; end
|
305
381
|
def ns=(namespace); @@ns = namespace; end
|
306
382
|
alias_method :namespace, :ns
|
307
383
|
alias_method :'namespace=', :'ns='
|
308
|
-
|
384
|
+
|
309
385
|
# Default value of expiration timeout in seconds.
|
310
386
|
def default_expire; @@default_expire; end
|
311
|
-
|
387
|
+
|
312
388
|
# Assigns default value of expiration timeout in seconds.
|
313
389
|
# Must be > 0.
|
314
|
-
def default_expire=(value)
|
390
|
+
def default_expire=(value)
|
391
|
+
raise ArgumentError, "#{name}.default_expire value must be greater than 0" unless (value = value.to_f) > 0
|
392
|
+
@@default_expire = value
|
393
|
+
end
|
315
394
|
|
316
395
|
# Setup redis database and other defaults
|
317
396
|
# MUST BE called once before any semaphore is created.
|
@@ -414,8 +493,8 @@ class Redis
|
|
414
493
|
# Mutex.setup for "lightweight" startup procedure.
|
415
494
|
def start_watcher
|
416
495
|
raise MutexError, "call #{self.class}::setup first" unless @@redis_watcher
|
417
|
-
return if
|
418
|
-
if @@watching
|
496
|
+
return if watching?
|
497
|
+
if @@watching # Process id changed, we've been forked alive!
|
419
498
|
@@redis_watcher = Redis.new @redis_options
|
420
499
|
@@signal_queue.clear
|
421
500
|
end
|
@@ -456,16 +535,17 @@ class Redis
|
|
456
535
|
else
|
457
536
|
sleep retries > 1 ? 1 : 0.1
|
458
537
|
end
|
459
|
-
end while
|
538
|
+
end while watching?
|
460
539
|
end.resume
|
461
540
|
until @@watcher_subscribed
|
462
|
-
raise MutexError, "Can not establish watcher channel connection!" unless
|
541
|
+
raise MutexError, "Can not establish watcher channel connection!" unless watching?
|
463
542
|
fiber = Fiber.current
|
464
543
|
::EM.next_tick { fiber.resume }
|
465
544
|
Fiber.yield
|
466
545
|
end
|
467
546
|
end
|
468
547
|
|
548
|
+
# EM sleep helper
|
469
549
|
def sleep(seconds)
|
470
550
|
fiber = Fiber.current
|
471
551
|
::EM::Timer.new(secs) { fiber.resume }
|
@@ -480,7 +560,7 @@ class Redis
|
|
480
560
|
# Pass `true` to forcefully stop it. This might instead cause
|
481
561
|
# MutexError to be raised in waiting fibers.
|
482
562
|
def stop_watcher(force = false)
|
483
|
-
return unless
|
563
|
+
return unless watching?
|
484
564
|
raise MutexError, "call #{self.class}::setup first" unless @@redis_watcher
|
485
565
|
unless @@signal_queue.empty? || force
|
486
566
|
raise MutexError, "can't stop: active signal queue handlers"
|
@@ -504,14 +584,16 @@ class Redis
|
|
504
584
|
end
|
505
585
|
|
506
586
|
# Attempts to grab the lock and waits if it isn’t available.
|
507
|
-
# Raises MutexError if mutex was locked by the current owner
|
587
|
+
# Raises MutexError if mutex was locked by the current owner
|
588
|
+
# or if used before Mutex.setup.
|
589
|
+
# Raises ArgumentError on invalid options.
|
508
590
|
# Returns instance of Redis::EM::Mutex if lock was successfully obtained.
|
509
591
|
# Returns `nil` if lock wasn't available within `:block` seconds.
|
510
592
|
#
|
511
|
-
# Redis::EM::Mutex.lock(*names,
|
593
|
+
# Redis::EM::Mutex.lock(*names, options = {})
|
512
594
|
#
|
513
595
|
# - *names = lock identifiers - if none they are auto generated
|
514
|
-
# -
|
596
|
+
# - options = hash:
|
515
597
|
# - :name - same as name (in case *names arguments were omitted)
|
516
598
|
# - :block - block timeout
|
517
599
|
# - :expire - expire timeout (see: Mutex#lock and Mutex#try_lock)
|
@@ -520,13 +602,14 @@ class Redis
|
|
520
602
|
mutex = new(*args)
|
521
603
|
mutex if mutex.lock
|
522
604
|
end
|
605
|
+
|
523
606
|
# Execute block of code protected with named semaphore.
|
524
607
|
# Returns result of code block.
|
525
608
|
#
|
526
|
-
# Redis::EM::Mutex.synchronize(*names,
|
609
|
+
# Redis::EM::Mutex.synchronize(*names, options = {}, &block)
|
527
610
|
#
|
528
611
|
# - *names = lock identifiers - if none they are auto generated
|
529
|
-
# -
|
612
|
+
# - options = hash:
|
530
613
|
# - :name - same as name (in case *names arguments were omitted)
|
531
614
|
# - :block - block timeout
|
532
615
|
# - :expire - expire timeout (see: Mutex#lock and Mutex#try_lock)
|
@@ -534,13 +617,13 @@ class Redis
|
|
534
617
|
#
|
535
618
|
# If `:block` is set and lock isn't obtained within `:block` seconds this method raises
|
536
619
|
# MutexTimeout.
|
620
|
+
# Raises MutexError if used before Mutex.setup.
|
621
|
+
# Raises ArgumentError on invalid options.
|
537
622
|
def synchronize(*args, &block)
|
538
623
|
new(*args).synchronize(&block)
|
539
624
|
end
|
540
625
|
end
|
541
626
|
|
542
|
-
private
|
543
|
-
|
544
627
|
def owner_ident(lock_id = nil)
|
545
628
|
if lock_id
|
546
629
|
"#@@uuid$#$$@#{Fiber.current.__id__} #{lock_id}"
|