redis-em-mutex 0.2.3 → 0.3.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/BENCHMARK.md +134 -0
- data/{HISTORY.rdoc → HISTORY.md} +10 -1
- data/README.md +89 -5
- data/Rakefile +23 -4
- data/benchmark_mutex.rb +99 -0
- data/lib/redis/em-connection-pool.rb +88 -0
- data/lib/redis/em-mutex/ns.rb +1 -1
- data/lib/redis/em-mutex/pure_handler.rb +245 -0
- data/lib/redis/em-mutex/script_handler.rb +414 -0
- data/lib/redis/em-mutex/version.rb +1 -1
- data/lib/redis/em-mutex.rb +79 -248
- data/redis-em-mutex.gemspec +2 -2
- data/spec/redis-em-mutex-condition.rb +3 -3
- data/spec/redis-em-mutex-features.rb +67 -5
- data/spec/redis-em-mutex-semaphores.rb +9 -4
- metadata +11 -5
data/lib/redis/em-mutex.rb
CHANGED
@@ -41,24 +41,34 @@ class Redis
|
|
41
41
|
@@watching = false
|
42
42
|
@@signal_queue = Hash.new {|h,k| h[k] = []}
|
43
43
|
@@ns = nil
|
44
|
+
@@handler = nil
|
44
45
|
@@uuid ||= if SecureRandom.respond_to?(:uuid)
|
45
46
|
SecureRandom.uuid
|
46
47
|
else
|
47
48
|
SecureRandom.base64(24)
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
-
alias_method :namespace, :ns
|
51
|
+
private
|
52
52
|
|
53
|
-
def
|
53
|
+
def signal_queue; @@signal_queue end
|
54
|
+
def uuid; @@uuid; end
|
55
|
+
def redis_pool; @@redis_pool end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
public
|
58
|
+
|
59
|
+
# Selected implementation handler module name
|
60
|
+
def self.handler; @@handler && @@handler.name end
|
61
|
+
|
62
|
+
# Whether selected implementation handler
|
63
|
+
# supports refreshing of already expired locks.
|
64
|
+
def self.can_refresh_expired?
|
65
|
+
@@handler.can_refresh_expired?
|
58
66
|
end
|
59
67
|
|
60
|
-
|
61
|
-
|
68
|
+
# Whether selected implementation handler
|
69
|
+
# supports refreshing of already expired locks.
|
70
|
+
def can_refresh_expired?
|
71
|
+
@@handler.can_refresh_expired?
|
62
72
|
end
|
63
73
|
|
64
74
|
# Creates a new cross machine/process/fiber semaphore
|
@@ -77,6 +87,7 @@ class Redis
|
|
77
87
|
# Raises ArgumentError on invalid options.
|
78
88
|
def initialize(*args)
|
79
89
|
raise MutexError, "call #{self.class}::setup first" unless @@redis_pool
|
90
|
+
self.class.setup_handler unless @@handler
|
80
91
|
|
81
92
|
opts = args.last.kind_of?(Hash) ? args.pop : {}
|
82
93
|
|
@@ -87,243 +98,35 @@ class Redis
|
|
87
98
|
@multi = !@names.one?
|
88
99
|
@ns = opts[:ns] || @@ns
|
89
100
|
@ns_names = @ns ? @names.map {|n| "#@ns:#{n}".freeze }.freeze : @names.map {|n| n.to_s.dup.freeze }.freeze
|
101
|
+
@marsh_names = Marshal.dump(@ns_names)
|
90
102
|
self.expire_timeout = opts[:expire] if opts.key?(:expire)
|
91
103
|
self.block_timeout = opts[:block] if opts.key?(:block)
|
92
|
-
|
93
|
-
|
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
|
102
|
-
end
|
103
|
-
|
104
|
-
# Returns `true` if this semaphore (at least one of locked `names`) is currently being held by some owner.
|
105
|
-
def locked?
|
106
|
-
if @multi
|
107
|
-
@@redis_pool.multi do |multi|
|
108
|
-
@ns_names.each {|n| multi.exists n}
|
109
|
-
end.any?
|
110
|
-
else
|
111
|
-
@@redis_pool.exists @ns_names.first
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
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.
|
117
|
-
def owned?
|
118
|
-
!!if @locked_id && owner_ident(@locked_id) == (lock_full_ident = @locked_owner_id)
|
119
|
-
@@redis_pool.mget(*@ns_names).all? {|v| v == lock_full_ident}
|
120
|
-
end
|
121
|
-
end
|
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
|
104
|
+
self.extend(@@handler)
|
105
|
+
post_init(opts)
|
142
106
|
end
|
143
107
|
|
144
|
-
|
145
|
-
|
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
|
108
|
+
attr_reader :names, :ns, :block_timeout
|
109
|
+
alias_method :namespace, :ns
|
161
110
|
|
162
|
-
|
163
|
-
# Returns `true` if the lock was granted.
|
164
|
-
# Use Mutex#expire_timeout= to set lock expiration time in secods.
|
165
|
-
# Otherwise global Mutex.default_expire is used.
|
166
|
-
#
|
167
|
-
# This method doesn't capture expired semaphores
|
168
|
-
# and therefore it should not be used under normal circumstances.
|
169
|
-
# Use Mutex#lock with block_timeout = 0 to obtain expired lock without blocking.
|
170
|
-
def try_lock
|
171
|
-
lock_id = (Time.now + expire_timeout).to_f.to_s
|
172
|
-
lock_full_ident = owner_ident(lock_id)
|
173
|
-
!!if @multi
|
174
|
-
if @@redis_pool.msetnx(*@ns_names.map {|k| [k, lock_full_ident]}.flatten)
|
175
|
-
@locked_id = lock_id
|
176
|
-
@locked_owner_id = lock_full_ident
|
177
|
-
end
|
178
|
-
elsif @@redis_pool.setnx(@ns_names.first, lock_full_ident)
|
179
|
-
@locked_id = lock_id
|
180
|
-
@locked_owner_id = lock_full_ident
|
181
|
-
end
|
182
|
-
end
|
111
|
+
def expire_timeout; @expire_timeout || @@default_expire; end
|
183
112
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
# and now it's got a new owner.
|
188
|
-
def refresh(expire_timeout=nil)
|
189
|
-
ret = false
|
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
|
192
|
-
new_lock_full_ident = owner_ident(new_lock_id)
|
193
|
-
@@redis_pool.execute(false) do |r|
|
194
|
-
r.watch(*@ns_names) do
|
195
|
-
if r.mget(*@ns_names).all? {|v| v == lock_full_ident}
|
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
|
200
|
-
end
|
201
|
-
else
|
202
|
-
r.unwatch
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
ret
|
113
|
+
def expire_timeout=(value)
|
114
|
+
raise ArgumentError, "#{self.class.name}\#expire_timeout value must be greater than 0" unless (value = value.to_f) > 0
|
115
|
+
@expire_timeout = value
|
208
116
|
end
|
209
117
|
|
210
|
-
|
211
|
-
|
212
|
-
# and now it's got a new owner.
|
213
|
-
# In case of unlocking multiple name semaphore this method returns self only when all
|
214
|
-
# of the names have been unlocked successfully.
|
215
|
-
def unlock!
|
216
|
-
sem_left = @ns_names.length
|
217
|
-
if @locked_id && owner_ident(@locked_id) == (lock_full_ident = @locked_owner_id)
|
218
|
-
@locked_owner_id = @locked_id = nil
|
219
|
-
@@redis_pool.execute(false) do |r|
|
220
|
-
@ns_names.each do |name|
|
221
|
-
r.watch(name) do
|
222
|
-
if r.get(name) == lock_full_ident
|
223
|
-
if (r.multi {|multi|
|
224
|
-
multi.del(name)
|
225
|
-
multi.publish SIGNAL_QUEUE_CHANNEL, Marshal.dump([name])
|
226
|
-
})
|
227
|
-
sem_left -= 1
|
228
|
-
end
|
229
|
-
else
|
230
|
-
r.unwatch
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
236
|
-
sem_left.zero? && self
|
118
|
+
def block_timeout=(value)
|
119
|
+
@block_timeout = value.nil? ? nil : value.to_f
|
237
120
|
end
|
238
121
|
|
239
122
|
# Releases the lock unconditionally.
|
240
|
-
# If the semaphore wasn
|
123
|
+
# If the semaphore wasn't locked by the current owner it is silently ignored.
|
241
124
|
# Returns self.
|
242
125
|
def unlock
|
243
126
|
unlock!
|
244
127
|
self
|
245
128
|
end
|
246
129
|
|
247
|
-
# Attempts to grab the lock and waits if it isn’t available.
|
248
|
-
# Raises MutexError if mutex was locked by the current owner.
|
249
|
-
# Returns `true` if lock was successfully obtained.
|
250
|
-
# Returns `false` if lock wasn't available within `block_timeout` seconds.
|
251
|
-
#
|
252
|
-
# If `block_timeout` is `nil` or omited this method uses Mutex#block_timeout.
|
253
|
-
# If also Mutex#block_timeout is nil this method returns only after lock
|
254
|
-
# has been granted.
|
255
|
-
#
|
256
|
-
# Use Mutex#expire_timeout= to set lock expiration timeout.
|
257
|
-
# Otherwise global Mutex.default_expire is used.
|
258
|
-
def lock(block_timeout = nil)
|
259
|
-
block_timeout||= self.block_timeout
|
260
|
-
names = @ns_names
|
261
|
-
timer = fiber = nil
|
262
|
-
try_again = false
|
263
|
-
handler = proc do
|
264
|
-
try_again = true
|
265
|
-
::EM.next_tick { fiber.resume if fiber } if fiber
|
266
|
-
end
|
267
|
-
begin
|
268
|
-
Mutex.start_watcher unless watching?
|
269
|
-
queues = names.map {|n| @@signal_queue[n] << handler }
|
270
|
-
ident_match = owner_ident
|
271
|
-
until try_lock
|
272
|
-
start_time = Time.now.to_f
|
273
|
-
expire_time = nil
|
274
|
-
@@redis_pool.execute(false) do |r|
|
275
|
-
r.watch(*names) do
|
276
|
-
expired_names = names.zip(r.mget(*names)).map do |name, lock_value|
|
277
|
-
if lock_value
|
278
|
-
owner, exp_id = lock_value.split ' '
|
279
|
-
exp_time = exp_id.to_f
|
280
|
-
expire_time = exp_time if expire_time.nil? || exp_time < expire_time
|
281
|
-
raise MutexError, "deadlock; recursive locking #{owner}" if owner == ident_match
|
282
|
-
if exp_time < start_time
|
283
|
-
name
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
|
-
if expire_time && expire_time < start_time
|
288
|
-
r.multi do |multi|
|
289
|
-
expired_names = expired_names.compact
|
290
|
-
multi.del(*expired_names)
|
291
|
-
multi.publish SIGNAL_QUEUE_CHANNEL, Marshal.dump(expired_names)
|
292
|
-
end
|
293
|
-
else
|
294
|
-
r.unwatch
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
298
|
-
timeout = (expire_time = expire_time.to_f) - start_time
|
299
|
-
timeout = block_timeout if block_timeout && block_timeout < timeout
|
300
|
-
|
301
|
-
if !try_again && timeout > 0
|
302
|
-
timer = ::EM::Timer.new(timeout) do
|
303
|
-
timer = nil
|
304
|
-
::EM.next_tick { fiber.resume if fiber } if fiber
|
305
|
-
end
|
306
|
-
fiber = Fiber.current
|
307
|
-
Fiber.yield
|
308
|
-
fiber = nil
|
309
|
-
end
|
310
|
-
finish_time = Time.now.to_f
|
311
|
-
if try_again || finish_time > expire_time
|
312
|
-
block_timeout-= finish_time - start_time if block_timeout
|
313
|
-
try_again = false
|
314
|
-
else
|
315
|
-
return false
|
316
|
-
end
|
317
|
-
end
|
318
|
-
true
|
319
|
-
ensure
|
320
|
-
timer.cancel if timer
|
321
|
-
timer = nil
|
322
|
-
queues.each {|q| q.delete handler }
|
323
|
-
names.each {|n| @@signal_queue.delete(n) if @@signal_queue[n].empty? }
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
130
|
# Wakes up currently sleeping fiber on a mutex.
|
328
131
|
def wakeup(fiber)
|
329
132
|
fiber.resume if @slept.delete(fiber)
|
@@ -333,7 +136,7 @@ class Redis
|
|
333
136
|
alias_method :_wakeup, :wakeup
|
334
137
|
|
335
138
|
# Releases the lock and sleeps `timeout` seconds if it is given and non-nil or forever.
|
336
|
-
# Raises MutexError if mutex wasn
|
139
|
+
# Raises MutexError if mutex wasn't locked by the current owner.
|
337
140
|
# Raises MutexTimeout if #block_timeout= was set and timeout
|
338
141
|
# occured while locking after sleep.
|
339
142
|
# If code block is provided it is executed after waking up, just before grabbing a lock.
|
@@ -405,15 +208,19 @@ class Redis
|
|
405
208
|
@@default_expire = value
|
406
209
|
end
|
407
210
|
|
408
|
-
# Setup redis database and other defaults
|
211
|
+
# Setup redis database and other defaults.
|
409
212
|
# MUST BE called once before any semaphore is created.
|
410
213
|
#
|
411
214
|
# opts = options Hash:
|
412
215
|
#
|
413
216
|
# global options:
|
414
217
|
#
|
415
|
-
# - :connection_pool_class - default is ::EM::
|
416
|
-
# - :redis_factory - default is proc {|
|
218
|
+
# - :connection_pool_class - default is Redis::EM::ConnectionPool
|
219
|
+
# - :redis_factory - default is proc {|redis_opts| Redis.new redis_opts }
|
220
|
+
# - :handler - the default value is taken from envronment variable: REDIS_EM_MUTEX_HANDLER or :auto
|
221
|
+
# :pure - optimistic locking commands based (redis-server >= 2.4)
|
222
|
+
# :script - server scripting based (redis-server >= 2.6)
|
223
|
+
# :auto - autodetect and choose best available handler
|
417
224
|
# - :expire - sets global Mutex.default_expire
|
418
225
|
# - :ns - sets global Mutex.namespace
|
419
226
|
# - :reconnect_max - maximum num. of attempts to re-establish
|
@@ -476,11 +283,11 @@ class Redis
|
|
476
283
|
unless (@@redis_pool = redis)
|
477
284
|
unless @connection_pool_class
|
478
285
|
begin
|
479
|
-
require 'em-
|
286
|
+
require 'redis/em-connection-pool' unless defined?(Redis::EM::ConnectionPool)
|
480
287
|
rescue LoadError
|
481
|
-
raise ":connection_pool_class required; could not fall back to EM::
|
288
|
+
raise ":connection_pool_class required; could not fall back to Redis::EM::ConnectionPool"
|
482
289
|
end
|
483
|
-
@connection_pool_class = ::EM::
|
290
|
+
@connection_pool_class = Redis::EM::ConnectionPool
|
484
291
|
end
|
485
292
|
@@redis_pool = @connection_pool_class.new(size: pool_size) do
|
486
293
|
@redis_factory[redis_options]
|
@@ -488,6 +295,37 @@ class Redis
|
|
488
295
|
end
|
489
296
|
@redis_watcher = @redis_factory[redis_options]
|
490
297
|
start_watcher if ::EM.reactor_running?
|
298
|
+
|
299
|
+
case handler = opts.handler || @@handler
|
300
|
+
when Module
|
301
|
+
@@handler = handler
|
302
|
+
when nil, Symbol, String
|
303
|
+
setup_handler(handler)
|
304
|
+
else
|
305
|
+
raise TypeError, 'handler must be Symbol or Module'
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def setup_handler(handler = nil)
|
310
|
+
handler = (handler || ENV['REDIS_EM_MUTEX_HANDLER'] || :auto).to_sym.downcase
|
311
|
+
if handler == :auto
|
312
|
+
return unless ::EM.reactor_running?
|
313
|
+
handler = :script
|
314
|
+
begin
|
315
|
+
@@redis_pool.script(:exists)
|
316
|
+
rescue Redis::CommandError
|
317
|
+
handler = :pure
|
318
|
+
end
|
319
|
+
end
|
320
|
+
const_name = "#{handler.to_s.capitalize}HandlerMixin"
|
321
|
+
begin
|
322
|
+
unless self.const_defined?(const_name)
|
323
|
+
require "redis/em-mutex/#{handler}_handler"
|
324
|
+
end
|
325
|
+
@@handler = self.const_get(const_name)
|
326
|
+
rescue LoadError, NameError
|
327
|
+
raise "handler: #{handler} not found"
|
328
|
+
end
|
491
329
|
end
|
492
330
|
|
493
331
|
def ready?
|
@@ -530,12 +368,12 @@ class Redis
|
|
530
368
|
end
|
531
369
|
on.message do |channel, message|
|
532
370
|
if channel == SIGNAL_QUEUE_CHANNEL
|
533
|
-
|
371
|
+
sig_match = {}
|
534
372
|
Marshal.load(message).each do |name|
|
535
|
-
|
373
|
+
sig_match[@@signal_queue[name].first] = true if @@signal_queue.key?(name)
|
536
374
|
end
|
537
|
-
|
538
|
-
|
375
|
+
sig_match.keys.each do |sig_proc|
|
376
|
+
sig_proc.call if sig_proc
|
539
377
|
end
|
540
378
|
end
|
541
379
|
end
|
@@ -581,7 +419,7 @@ class Redis
|
|
581
419
|
return unless watching?
|
582
420
|
raise MutexError, "call #{self.class}::setup first" unless @redis_watcher
|
583
421
|
unless @@signal_queue.empty? || force
|
584
|
-
raise MutexError, "can't stop:
|
422
|
+
raise MutexError, "can't stop: semaphores in queue"
|
585
423
|
end
|
586
424
|
@@watching = false
|
587
425
|
if @watcher_subscribed
|
@@ -601,7 +439,7 @@ class Redis
|
|
601
439
|
raise NotImplementedError
|
602
440
|
end
|
603
441
|
|
604
|
-
# Attempts to grab the lock and waits if it isn
|
442
|
+
# Attempts to grab the lock and waits if it isn't available.
|
605
443
|
# Raises MutexError if mutex was locked by the current owner
|
606
444
|
# or if used before Mutex.setup.
|
607
445
|
# Raises ArgumentError on invalid options.
|
@@ -640,14 +478,7 @@ class Redis
|
|
640
478
|
def synchronize(*args, &block)
|
641
479
|
new(*args).synchronize(&block)
|
642
480
|
end
|
643
|
-
end
|
644
481
|
|
645
|
-
def owner_ident(lock_id = nil)
|
646
|
-
if lock_id
|
647
|
-
"#@@uuid$#$$@#{Fiber.current.__id__} #{lock_id}"
|
648
|
-
else
|
649
|
-
"#@@uuid$#$$@#{Fiber.current.__id__}"
|
650
|
-
end
|
651
482
|
end
|
652
483
|
|
653
484
|
end
|
data/redis-em-mutex.gemspec
CHANGED
@@ -17,9 +17,9 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.rdoc_options << "--title" << "redis-em-mutex" <<
|
18
18
|
"--main" << "README.md"
|
19
19
|
s.has_rdoc = true
|
20
|
-
s.extra_rdoc_files = ["README.md"]
|
20
|
+
s.extra_rdoc_files = ["README.md", "BENCHMARK.md"]
|
21
21
|
s.requirements << "Redis server 2.4+"
|
22
|
-
s.add_runtime_dependency "redis", ">= 3.0.
|
22
|
+
s.add_runtime_dependency "redis", ">= 3.0.2"
|
23
23
|
s.add_runtime_dependency "hiredis", "~> 0.4.5"
|
24
24
|
s.add_runtime_dependency "eventmachine", ">= 1.0.0.beta.1"
|
25
25
|
s.add_development_dependency "rspec", "~> 2.8.0"
|
@@ -15,8 +15,8 @@ describe Redis::EM::Mutex do
|
|
15
15
|
::EM.add_timer(0.25) do
|
16
16
|
mutex.wakeup(fiber)
|
17
17
|
end
|
18
|
-
mutex.sleep.should be_within(0.
|
19
|
-
(Time.now - start).should be_within(0.
|
18
|
+
mutex.sleep.should be_within(0.015).of(0.25)
|
19
|
+
(Time.now - start).should be_within(0.015).of(0.25)
|
20
20
|
mutex.owned?.should be true
|
21
21
|
mutex.unlock!.should be_true
|
22
22
|
ensure
|
@@ -71,7 +71,7 @@ describe Redis::EM::Mutex do
|
|
71
71
|
::EM::Synchrony.next_tick do
|
72
72
|
begin
|
73
73
|
mutex.owned?.should be false
|
74
|
-
mutex.lock.should be true
|
74
|
+
mutex.lock(0.001).should be true
|
75
75
|
mutex.owned?.should be true
|
76
76
|
mutex.wakeup(fiber)
|
77
77
|
::EM::Synchrony.sleep(0.2)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
$:.unshift "lib"
|
2
2
|
require 'em-synchrony'
|
3
|
-
require 'em-
|
3
|
+
require 'redis/em-connection-pool'
|
4
4
|
require 'redis-em-mutex'
|
5
5
|
|
6
|
-
class TestDummyConnectionPool < EM::
|
6
|
+
class TestDummyConnectionPool < Redis::EM::ConnectionPool; end
|
7
7
|
|
8
8
|
describe Redis::EM::Mutex do
|
9
9
|
|
@@ -47,7 +47,7 @@ describe Redis::EM::Mutex do
|
|
47
47
|
described_class.reconnect_max_retries = 0
|
48
48
|
described_class.reconnect_forever?.should be false
|
49
49
|
redis_pool = described_class.class_variable_get(:'@@redis_pool')
|
50
|
-
redis_pool.should be_an_instance_of EM::
|
50
|
+
redis_pool.should be_an_instance_of Redis::EM::ConnectionPool
|
51
51
|
redis_pool.should_not be @redis_pool
|
52
52
|
redis_pool.client.host.should eq '127.0.0.1'
|
53
53
|
redis_pool.client.db.should eq 2
|
@@ -107,7 +107,6 @@ describe Redis::EM::Mutex do
|
|
107
107
|
counter.should eq 6
|
108
108
|
end
|
109
109
|
|
110
|
-
|
111
110
|
it "should be able to sleep" do
|
112
111
|
t = Time.now
|
113
112
|
described_class.sleep 0.11
|
@@ -119,6 +118,69 @@ describe Redis::EM::Mutex do
|
|
119
118
|
described_class.class_variable_get(:@@uuid).should eq @uuid
|
120
119
|
end
|
121
120
|
|
121
|
+
it "owner_ident should begin with uuid and end with owner id" do
|
122
|
+
ident = described_class.new(:dummy).owner_ident
|
123
|
+
ident.should start_with(@uuid)
|
124
|
+
ident.should end_with(Fiber.current.__id__.to_s)
|
125
|
+
ident = described_class.new(:dummy, owner:'__me__').owner_ident
|
126
|
+
ident.should start_with(@uuid)
|
127
|
+
ident.should end_with('__me__')
|
128
|
+
end
|
129
|
+
|
130
|
+
context "Mutex implementation handlers:" do
|
131
|
+
it "should select desired handler" do
|
132
|
+
described_class.setup
|
133
|
+
expected_name = case ENV['REDIS_EM_MUTEX_HANDLER']
|
134
|
+
when /pure/i
|
135
|
+
described_class.handler.should eq 'Redis::EM::Mutex::PureHandlerMixin'
|
136
|
+
when /script/i
|
137
|
+
described_class.handler.should eq 'Redis::EM::Mutex::ScriptHandlerMixin'
|
138
|
+
end
|
139
|
+
described_class.setup(handler: :pUrE)
|
140
|
+
described_class.handler.should eq Redis::EM::Mutex::PureHandlerMixin.name
|
141
|
+
described_class.setup
|
142
|
+
described_class.handler.should eq Redis::EM::Mutex::PureHandlerMixin.name
|
143
|
+
described_class.setup {|opts| opts.handler = 'ScRiPt'}
|
144
|
+
described_class.handler.should eq Redis::EM::Mutex::ScriptHandlerMixin.name
|
145
|
+
described_class.setup
|
146
|
+
described_class.handler.should eq Redis::EM::Mutex::ScriptHandlerMixin.name
|
147
|
+
described_class.setup {|opts| opts.handler = Redis::EM::Mutex::PureHandlerMixin}
|
148
|
+
described_class.handler.should eq Redis::EM::Mutex::PureHandlerMixin.name
|
149
|
+
described_class.setup(handler: Redis::EM::Mutex::ScriptHandlerMixin)
|
150
|
+
described_class.handler.should eq Redis::EM::Mutex::ScriptHandlerMixin.name
|
151
|
+
described_class.setup {|opts| opts.handler = :auto}
|
152
|
+
other_handler, other_name = case described_class.handler
|
153
|
+
when Redis::EM::Mutex::ScriptHandlerMixin.name
|
154
|
+
[:pure, Redis::EM::Mutex::PureHandlerMixin.name]
|
155
|
+
when Redis::EM::Mutex::PureHandlerMixin.name
|
156
|
+
[:script, Redis::EM::Mutex::ScriptHandlerMixin.name]
|
157
|
+
end
|
158
|
+
other_handler.should_not be_nil
|
159
|
+
other_name.should_not be_nil
|
160
|
+
described_class.setup(handler: other_handler)
|
161
|
+
described_class.handler.should eq other_name
|
162
|
+
described_class.setup(handler: :auto)
|
163
|
+
described_class.handler.should_not eq other_name
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should select pure handler with SCRIPT unsupporting redis-server" do
|
167
|
+
pool = Redis::EM::ConnectionPool.new(size: 1) { Redis.new }
|
168
|
+
pool.should_receive(:script).with(:exists).
|
169
|
+
and_raise(Redis::CommandError)
|
170
|
+
described_class.setup(redis: pool, handler: :auto)
|
171
|
+
described_class.handler.should eq Redis::EM::Mutex::PureHandlerMixin.name
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should select script handler with SCRIPT aware redis-server" do
|
175
|
+
pool = Redis::EM::ConnectionPool.new(size: 1) { Redis.new }
|
176
|
+
pool.should_receive(:script).with(:exists).
|
177
|
+
and_return(false)
|
178
|
+
described_class.setup(redis: pool, handler: :auto)
|
179
|
+
described_class.handler.should eq Redis::EM::Mutex::ScriptHandlerMixin.name
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
122
184
|
around(:each) do |testcase|
|
123
185
|
@after_em_stop = nil
|
124
186
|
@exception = nil
|
@@ -138,7 +200,7 @@ describe Redis::EM::Mutex do
|
|
138
200
|
end
|
139
201
|
|
140
202
|
before(:all) do
|
141
|
-
@redis_pool = EM::
|
203
|
+
@redis_pool = Redis::EM::ConnectionPool.new(size: 10) { Redis.new url: 'redis://localhost/1' }
|
142
204
|
@uuid = described_class.class_variable_get(:@@uuid)
|
143
205
|
end
|
144
206
|
end
|
@@ -65,7 +65,7 @@ describe Redis::EM::Mutex do
|
|
65
65
|
mutex.owned?.should be false
|
66
66
|
start = Time.now
|
67
67
|
mutex.synchronize do
|
68
|
-
(Time.now - start).should be_within(0.
|
68
|
+
(Time.now - start).should be_within(0.02).of(0.26)
|
69
69
|
locked.should be false
|
70
70
|
locked = nil
|
71
71
|
end
|
@@ -96,7 +96,7 @@ describe Redis::EM::Mutex do
|
|
96
96
|
start = Time.now
|
97
97
|
mutex.synchronize do
|
98
98
|
mutex.owned?.should be true
|
99
|
-
(Time.now - start).should be_within(0.
|
99
|
+
(Time.now - start).should be_within(0.02).of(0.26)
|
100
100
|
locked.should be false
|
101
101
|
end
|
102
102
|
mutex.owned?.should be false
|
@@ -105,7 +105,7 @@ describe Redis::EM::Mutex do
|
|
105
105
|
begin
|
106
106
|
locked.should be true
|
107
107
|
described_class.new(name).synchronize do
|
108
|
-
(Time.now - start).should be_within(0.
|
108
|
+
(Time.now - start).should be_within(0.02).of(0.26)
|
109
109
|
locked.should be_an_instance_of Fixnum
|
110
110
|
locked-= 1
|
111
111
|
end
|
@@ -372,7 +372,12 @@ describe Redis::EM::Mutex do
|
|
372
372
|
mutex.expires_at.should eq Time.at(mutex.expiration_timestamp)
|
373
373
|
mutex.expires_at.should be < Time.now
|
374
374
|
start = Time.now
|
375
|
-
mutex.
|
375
|
+
if mutex.can_refresh_expired?
|
376
|
+
mutex.refresh.should be true
|
377
|
+
else
|
378
|
+
mutex.refresh.should be false
|
379
|
+
mutex.lock.should be true
|
380
|
+
end
|
376
381
|
now = Time.now
|
377
382
|
mutex.expires_in.should be_within(0.01).of(mutex.expire_timeout - (now - start))
|
378
383
|
EM::Synchrony.sleep mutex.expires_in + 0.001
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-em-mutex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 3.0.
|
21
|
+
version: 3.0.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 3.0.
|
29
|
+
version: 3.0.2
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: hiredis
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,14 +114,20 @@ executables: []
|
|
114
114
|
extensions: []
|
115
115
|
extra_rdoc_files:
|
116
116
|
- README.md
|
117
|
+
- BENCHMARK.md
|
117
118
|
files:
|
118
|
-
-
|
119
|
+
- BENCHMARK.md
|
120
|
+
- HISTORY.md
|
119
121
|
- README.md
|
120
122
|
- Rakefile
|
123
|
+
- benchmark_mutex.rb
|
121
124
|
- lib/redis-em-mutex.rb
|
125
|
+
- lib/redis/em-connection-pool.rb
|
122
126
|
- lib/redis/em-mutex.rb
|
123
127
|
- lib/redis/em-mutex/macro.rb
|
124
128
|
- lib/redis/em-mutex/ns.rb
|
129
|
+
- lib/redis/em-mutex/pure_handler.rb
|
130
|
+
- lib/redis/em-mutex/script_handler.rb
|
125
131
|
- lib/redis/em-mutex/version.rb
|
126
132
|
- redis-em-mutex.gemspec
|
127
133
|
- spec/redis-em-mutex-condition.rb
|