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.
@@ -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
- attr_reader :names, :ns, :block_timeout
51
- alias_method :namespace, :ns
51
+ private
52
52
 
53
- def expire_timeout; @expire_timeout || @@default_expire; end
53
+ def signal_queue; @@signal_queue end
54
+ def uuid; @@uuid; end
55
+ def redis_pool; @@redis_pool end
54
56
 
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
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
- def block_timeout=(value)
61
- @block_timeout = value.nil? ? nil : value.to_f
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
- @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
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
- # 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
108
+ attr_reader :names, :ns, :block_timeout
109
+ alias_method :namespace, :ns
161
110
 
162
- # Attempts to obtain the lock and returns immediately.
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
- # Refreshes lock expiration timeout.
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.
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
- # 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
- # 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 wasnt locked by the current owner it is silently ignored.
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 wasnt locked by the current owner.
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::Synchrony::ConnectionPool
416
- # - :redis_factory - default is proc {|opts| Redis.new opts }
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-synchrony/connection_pool' unless defined?(::EM::Synchrony::ConnectionPool)
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::Synchrony::ConnectionPool - gem install em-synchrony"
288
+ raise ":connection_pool_class required; could not fall back to Redis::EM::ConnectionPool"
482
289
  end
483
- @connection_pool_class = ::EM::Synchrony::ConnectionPool
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
- handlers = {}
371
+ sig_match = {}
534
372
  Marshal.load(message).each do |name|
535
- handlers[@@signal_queue[name].first] = true if @@signal_queue.key?(name)
373
+ sig_match[@@signal_queue[name].first] = true if @@signal_queue.key?(name)
536
374
  end
537
- handlers.keys.each do |handler|
538
- handler.call if handler
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: active signal queue handlers"
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 isnt available.
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
@@ -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.1"
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.01).of(0.25)
19
- (Time.now - start).should be_within(0.01).of(0.25)
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-synchrony/connection_pool'
3
+ require 'redis/em-connection-pool'
4
4
  require 'redis-em-mutex'
5
5
 
6
- class TestDummyConnectionPool < EM::Synchrony::ConnectionPool; end
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::Synchrony::ConnectionPool
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::Synchrony::ConnectionPool.new(size: 10) { Redis.new url: 'redis://localhost/1' }
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.015).of(0.26)
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.015).of(0.26)
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.015).of(0.26)
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.refresh.should be true
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.2.3
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-23 00:00:00.000000000 Z
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.1
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.1
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
- - HISTORY.rdoc
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