redis-em-mutex 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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