redis-em-mutex 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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}"
|