redis-em-mutex 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.rdoc CHANGED
@@ -1,3 +1,10 @@
1
+ 0.2.2
2
+ - fixed: uuid must be set only once
3
+ - fixed: forking process or setup before reactor is running would hang
4
+ current fiber forever on first failed lock
5
+ - added: new setup option :redis_factory
6
+ - fixed: doc format errors
7
+
1
8
  0.2.1
2
9
  - fixed: sleep
3
10
  - fixed: possible deadlock after #unlock! with multiple names
@@ -1,14 +1,17 @@
1
- = redis-em-mutex
1
+ redis-em-mutex
2
+ ==============
2
3
 
3
- Author:: Rafał Michalski (mailto:rafal@yeondir.com)
4
+ Author: Rafał Michalski (mailto:rafal@yeondir.com)
4
5
 
5
6
  * http://github.com/royaltm/redis-em-mutex
6
7
 
7
- == DESCRIPTION
8
+ DESCRIPTION
9
+ -----------
8
10
 
9
- *redis-em-mutex* is the cross server-process-fiber EventMachine + Redis based semaphore.
11
+ __redis-em-mutex__ is the cross server-process-fiber EventMachine + Redis based semaphore.
10
12
 
11
- == FEATURES
13
+ FEATURES
14
+ --------
12
15
 
13
16
  * only for EventMachine
14
17
  * no CPU-intensive sleep/polling while waiting for lock to become available
@@ -24,78 +27,90 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
24
27
  * compatible with Synchrony::Thread::ConditionVariable
25
28
  * extendable (beyond fibers) mutex ownership
26
29
 
27
- == BUGS/LIMITATIONS
30
+ BUGS/LIMITATIONS
31
+ ----------------
28
32
 
29
33
  * only for EventMachine
30
34
  * NOT thread-safe
31
35
  * locking order between concurrent processes is undetermined (no FIFO)
32
36
  * it's not nifty, rather somewhat complicated
33
37
 
34
- == REQUIREMENTS
38
+ REQUIREMENTS
39
+ ------------
35
40
 
36
- * ruby >= 1.9 (tested: 1.9.3-p194, 1.9.2-p320, 1.9.1-p378)
41
+ * ruby >= 1.9 (tested: ruby 1.9.3p374, 1.9.3-p194, 1.9.2-p320, 1.9.1-p378)
37
42
  * http://github.com/redis/redis-rb ~> 3.0.1
38
43
  * http://rubyeventmachine.com ~> 1.0.0
39
44
  * (optional) http://github.com/igrigorik/em-synchrony
40
45
 
41
- == INSTALL
46
+ INSTALL
47
+ -------
42
48
 
43
- $ [sudo] gem install redis-em-mutex
49
+ ```
50
+ $ [sudo] gem install redis-em-mutex
51
+ ```
44
52
 
45
- ==== Gemfile
53
+ #### Gemfile
46
54
 
47
- gem "redis-em-mutex", "~> 0.2.1"
55
+ ```ruby
56
+ gem "redis-em-mutex", "~> 0.2.2"
57
+ ```
48
58
 
49
- ==== Github
59
+ #### Github
50
60
 
51
- git clone git://github.com/royaltm/redis-em-mutex.git
61
+ ```
62
+ git clone git://github.com/royaltm/redis-em-mutex.git
63
+ ```
52
64
 
53
- == USAGE
65
+ USAGE
66
+ -----
54
67
 
55
- require 'em-synchrony'
56
- require 'em-redis-mutex'
68
+ ```ruby
69
+ require 'em-synchrony'
70
+ require 'redis-em-mutex'
57
71
 
58
- Redis::EM::Mutex.setup(size: 10, url: 'redis:///1', expire: 600)
72
+ Redis::EM::Mutex.setup(size: 10, url: 'redis:///1', expire: 600)
59
73
 
60
- # or
74
+ # or
61
75
 
62
- Redis::EM::Mutex.setup do |opts|
63
- opts.size = 10
64
- opts.url = 'redis:///1'
65
- ...
66
- end
76
+ Redis::EM::Mutex.setup do |opts|
77
+ opts.size = 10
78
+ opts.url = 'redis:///1'
79
+ end
67
80
 
68
81
 
69
- EM.synchrony do
70
- Redis::EM::Mutex.synchronize('resource.lock') do
71
- ... do something with resource
72
- end
82
+ EM.synchrony do
83
+ Redis::EM::Mutex.synchronize('resource.lock') do
84
+ # ... do something with resource
85
+ end
73
86
 
74
- # or
87
+ # or
75
88
 
76
- mutex = Redis::EM::Mutex.new('resource.lock')
77
- mutex.synchronize do
78
- ... do something with resource
79
- end
89
+ mutex = Redis::EM::Mutex.new('resource.lock')
90
+ mutex.synchronize do
91
+ # ... do something with resource
92
+ end
80
93
 
81
- # or
94
+ # or
82
95
 
83
- begin
84
- mutex.lock
85
- ... do something with resource
86
- ensure
87
- mutex.unlock
88
- end
96
+ begin
97
+ mutex.lock
98
+ # ... do something with resource
99
+ ensure
100
+ mutex.unlock
101
+ end
89
102
 
90
- ...
103
+ # ...
91
104
 
92
- Redis::EM::Mutex.stop_watcher
93
- EM.stop
94
- end
105
+ Redis::EM::Mutex.stop_watcher
106
+ EM.stop
107
+ end
108
+ ```
95
109
 
96
- === Namespaces
110
+ ### Namespaces
97
111
 
98
- Redis::EM::Mutex.setup(ns: 'my_namespace', ....)
112
+ ```ruby
113
+ Redis::EM::Mutex.setup(ns: 'my_namespace')
99
114
 
100
115
  # or multiple namespaces:
101
116
 
@@ -103,35 +118,41 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
103
118
 
104
119
  EM.synchrony do
105
120
  ns.synchronize('foo') do
106
- .... do something with foo
121
+ # .... do something with foo
107
122
  end
108
- ...
123
+
124
+ # ...
109
125
  EM.stop
110
126
  end
127
+ ```
111
128
 
112
- === Multi-locks
129
+ ### Multi-locks
113
130
 
131
+ ```ruby
114
132
  EM.synchrony do
115
133
  Redis::EM::Mutex.synchronize('foo', 'bar', 'baz') do
116
- .... do something with foo, bar and baz
134
+ # .... do something with foo, bar and baz
117
135
  end
118
- ...
136
+
137
+ # ...
119
138
  EM.stop
120
139
  end
140
+ ```
121
141
 
122
- === Locking options
142
+ ### Locking options
123
143
 
144
+ ```ruby
124
145
  EM.synchrony do
125
146
  begin
126
147
  Redis::EM::Mutex.synchronize('foo', 'bar', block: 0.25) do
127
- .... do something with foo and bar
148
+ # .... do something with foo and bar
128
149
  end
129
150
  rescue Redis::EM::Mutex::MutexTimeout
130
- ... locking timed out
151
+ # ... locking timed out
131
152
  end
132
153
 
133
154
  Redis::EM::Mutex.synchronize('foo', 'bar', expire: 60) do |mutex|
134
- .... do something with foo and bar in less than 60 seconds
155
+ # .... do something with foo and bar in less than 60 seconds
135
156
  if mutex.refresh(120)
136
157
  # now we have additional 120 seconds until lock expires
137
158
  else
@@ -139,23 +160,26 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
139
160
  end
140
161
  end
141
162
 
142
- ...
163
+ # ...
143
164
  EM.stop
144
165
  end
166
+ ```
145
167
 
146
- === Macro-style definition
168
+ ### Macro-style definition
147
169
 
148
170
  Borrowed from http://github.com/kenn/redis-mutex.
149
171
  Redis::EM::Mutex::Macro is a mixin which protects selected instance methods of a class with a mutex.
150
172
  The locking scope will be Mutex global namespace + class name + method name.
151
173
 
174
+ ```ruby
152
175
  class TheClass
153
176
  include Redis::EM::Mutex::Macro
154
177
 
155
178
  auto_mutex
156
179
  def critical_run
157
- ... do some critical stuff
158
- ....only one fiber in one process on one machine is executing this instance method of any instance of defined class
180
+ # ... do some critical stuff
181
+ # ....only one fiber in one process on one machine is executing
182
+ # this instance method of any instance of defined class
159
183
  end
160
184
 
161
185
  auto_mutex expire: 100, ns: '**TheClass**'
@@ -163,39 +187,41 @@ The locking scope will be Mutex global namespace + class name + method name.
163
187
 
164
188
  auto_mutex # start and stop will be protected
165
189
  def start
166
- ...
190
+ # ...
167
191
  end
168
192
 
169
193
  def stop
170
- ...
194
+ # ...
171
195
  end
172
196
 
173
197
  no_auto_mutex
174
198
  def some_unprotected_method
175
- ...
199
+ # ...
176
200
  end
177
201
 
178
202
  auto_mutex :run_long, expire: 100000, block: 10, on_timeout: :cant_run
179
203
  def run_long
180
- ...
204
+ # ...
181
205
  end
182
206
 
183
207
  def cant_run
184
- ...
208
+ # ...
185
209
  end
186
210
 
187
211
  def foo
188
- ...
212
+ # ...
189
213
  end
190
214
  auto_mutex :foo, block: 0, on_timeout: proc { puts 'bar!' }
191
215
 
192
216
  end
217
+ ```
193
218
 
194
- === ConditionVariable
219
+ ### ConditionVariable
195
220
 
196
221
  Redis::EM::Mutex may be used with EventMachine::Synchrony::Thread::ConditionVariable
197
222
  in place of EventMachine::Synchrony::Thread::Mutex.
198
223
 
224
+ ```ruby
199
225
  mutex = Redis::EM::Mutex.new('resource')
200
226
  resource = EM::Synchrony::Thread::ConditionVariable.new
201
227
  EM::Synchrony.next_tick do
@@ -211,10 +237,11 @@ in place of EventMachine::Synchrony::Thread::Mutex.
211
237
  resource.signal
212
238
  }
213
239
  end
240
+ ```
214
241
 
215
- === Advanced
242
+ ### Advanced
216
243
 
217
- ==== Customized owner
244
+ #### Customized owner
218
245
 
219
246
  In some cases you might want to extend the ownership of a locked semaphore beyond the current fiber.
220
247
  That is to be able to "share" a locked semaphore between group of fibers associated with some external resource.
@@ -234,6 +261,7 @@ This way we make sure that any other connection from the same ip address made to
234
261
  will have different ownership defined. At the same time all the fibers working with that Connection instance
235
262
  can access mutex's #refresh and #unlock methods concurrently.
236
263
 
264
+ ```ruby
237
265
  module MutexedConnection
238
266
  def post_init
239
267
  @buffer = ""
@@ -274,13 +302,16 @@ can access mutex's #refresh and #unlock methods concurrently.
274
302
  Redis::EM::Mutex.setup(size: 10, url: 'redis:///1', expire: 600)
275
303
  EM.start_server "0.0.0.0", 6969, MutexedConnection
276
304
  }
305
+ ```
277
306
 
278
- ==== Forking live
307
+ #### Forking live
279
308
 
280
309
  You may safely fork process while running event reactor and having locked semaphores.
281
310
  The locked semaphores in newly forked process will become unlocked while
282
311
  their locked status in parent process will be preserved.
283
312
 
313
+
314
+ ```ruby
284
315
  mutex = Redis::EM::Mutex.new('resource1', 'resource2', expire: 60)
285
316
 
286
317
  EM.synchrony do
@@ -294,9 +325,10 @@ their locked status in parent process will be preserved.
294
325
  mutex.locked? # true
295
326
  mutex.owned? # true
296
327
 
297
- ....
328
+ # ...
298
329
  end
299
- ...
330
+
331
+ # ...
300
332
  Redis::EM::Mutex.stop_watcher
301
333
  EM.stop
302
334
  end.resume
@@ -308,11 +340,33 @@ their locked status in parent process will be preserved.
308
340
  mutex.unlock
309
341
  mutex.owned? # false
310
342
 
311
- ...
343
+ # ...
312
344
  Redis::EM::Mutex.stop_watcher
313
345
  EM.stop
314
346
  end
347
+ ```
348
+
349
+ #### Redis factory
350
+
351
+ Want to use some non-standard redis options or customized client for semaphore watcher and/or redis pool?
352
+ Use `:redis_factory` option then.
353
+
354
+ ```ruby
355
+ require 'redis-sentinel'
356
+
357
+ Redis::EM::Mutex.setup do |opts|
358
+ opts.size = 10
359
+ opts.password = 'password'
360
+ opts.db = 11
361
+ opts.redis_factory = proc do |options|
362
+ Redis.new options.merge(
363
+ master_name: "my_master",
364
+ sentinels: [{host: "redis1", port: 6379}, {host: "redis2", port: 6380}])
365
+ end
366
+ end
367
+ ```
315
368
 
316
- == LICENCE
369
+ LICENCE
370
+ -------
317
371
 
318
372
  The MIT License - Copyright (c) 2012 Rafał Michalski
data/Rakefile CHANGED
@@ -33,5 +33,5 @@ end
33
33
 
34
34
  desc "Documentation"
35
35
  task :doc do
36
- sh "rdoc --encoding=UTF-8 --title=#$gem_name --main=README.rdoc README.rdoc lib/*.rb lib/*/*.rb"
36
+ sh "yardoc"
37
37
  end
@@ -1,7 +1,7 @@
1
1
  class Redis
2
2
  module EM
3
3
  class Mutex
4
- VERSION = '0.2.1'
4
+ VERSION = '0.2.2'
5
5
  end
6
6
  end
7
7
  end
@@ -32,19 +32,20 @@ class Redis
32
32
  include Errors
33
33
  extend Errors
34
34
 
35
- @@connection_pool_class = nil
36
- @@connection_retry_max = 10
37
- @@default_expire = 3600*24
38
- AUTO_NAME_SEED = '__@'
39
35
  SIGNAL_QUEUE_CHANNEL = "::#{self.name}::"
36
+ AUTO_NAME_SEED = '__@'
37
+ DEFAULT_RECONNECT_MAX_RETRIES = 10
38
+ @@default_expire = 3600*24
40
39
  @@name_index = AUTO_NAME_SEED
41
40
  @@redis_pool = nil
42
- @@redis_watcher = nil
43
41
  @@watching = false
44
- @@watcher_subscribed = false
45
42
  @@signal_queue = Hash.new {|h,k| h[k] = []}
46
43
  @@ns = nil
47
- @@uuid = nil
44
+ @@uuid ||= if SecureRandom.respond_to?(:uuid)
45
+ SecureRandom.uuid
46
+ else
47
+ SecureRandom.base64(24)
48
+ end
48
49
 
49
50
  attr_reader :names, :ns, :block_timeout
50
51
  alias_method :namespace, :ns
@@ -264,10 +265,10 @@ class Redis
264
265
  ::EM.next_tick { fiber.resume if fiber } if fiber
265
266
  end
266
267
  begin
268
+ Mutex.start_watcher unless watching?
267
269
  queues = names.map {|n| @@signal_queue[n] << handler }
268
270
  ident_match = owner_ident
269
271
  until try_lock
270
- Mutex.start_watcher unless watching?
271
272
  start_time = Time.now.to_f
272
273
  expire_time = nil
273
274
  @@redis_pool.execute(false) do |r|
@@ -382,10 +383,17 @@ class Redis
382
383
  def self.watching?; @@watching == $$; end
383
384
 
384
385
  class << self
386
+ attr_reader :reconnect_max_retries
387
+ def reconnect_forever?
388
+ @reconnect_max_retries < 0
389
+ end
390
+ def reconnect_max_retries=(max)
391
+ @reconnect_max_retries = max == :forever ? -1 : max.to_i
392
+ end
385
393
  def ns; @@ns; end
386
394
  def ns=(namespace); @@ns = namespace; end
387
395
  alias_method :namespace, :ns
388
- alias_method :'namespace=', :'ns='
396
+ alias_method :namespace=, :ns=
389
397
 
390
398
  # Default value of expiration timeout in seconds.
391
399
  def default_expire; @@default_expire; end
@@ -405,18 +413,19 @@ class Redis
405
413
  # global options:
406
414
  #
407
415
  # - :connection_pool_class - default is ::EM::Synchrony::ConnectionPool
416
+ # - :redis_factory - default is proc {|opts| Redis.new opts }
408
417
  # - :expire - sets global Mutex.default_expire
409
418
  # - :ns - sets global Mutex.namespace
410
419
  # - :reconnect_max - maximum num. of attempts to re-establish
411
420
  # connection to redis server;
412
421
  # default is 10; set to 0 to disable re-connecting;
413
- # set to -1 to attempt forever
422
+ # set to -1 or :forever to attempt forever
414
423
  #
415
424
  # redis connection options:
416
425
  #
417
426
  # - :size - redis connection pool size
418
427
  #
419
- # passed directly to Redis.new:
428
+ # passed directly to redis_factory:
420
429
  #
421
430
  # - :url - redis server url
422
431
  #
@@ -434,10 +443,10 @@ class Redis
434
443
  # - :redis - initialized ConnectionPool of Redis clients.
435
444
  def setup(opts = {})
436
445
  stop_watcher
446
+ @watcher_subscribed = nil
437
447
  opts = OpenStruct.new(opts)
438
448
  yield opts if block_given?
439
- @@connection_pool_class = opts.connection_pool_class if opts.connection_pool_class.kind_of?(Class)
440
- @redis_options = redis_options = {:driver => :synchrony}
449
+ redis_options = {:driver => :synchrony}
441
450
  redis_updater = proc do |redis|
442
451
  redis_options.update({
443
452
  :scheme => redis.scheme,
@@ -454,33 +463,37 @@ class Redis
454
463
  redis_options[:url] = opts.url
455
464
  end
456
465
  redis_updater.call opts
457
- namespace = opts.ns
458
466
  pool_size = (opts.size.to_i.nonzero? || 1).abs
459
467
  self.default_expire = opts.expire if opts.expire
460
- @@connection_retry_max = opts.reconnect_max.to_i if opts.reconnect_max
461
- @@ns = namespace if namespace
462
- @@uuid = if SecureRandom.respond_to?(:uuid)
463
- SecureRandom.uuid
464
- else
465
- SecureRandom.base64(24)
466
- end
468
+ self.reconnect_max_retries = opts.reconnect_max if opts.reconnect_max
469
+ @connection_pool_class = opts.connection_pool_class if opts.connection_pool_class.kind_of?(Class)
470
+ @redis_options = redis_options
471
+ @reconnect_max_retries ||= DEFAULT_RECONNECT_MAX_RETRIES
472
+ @redis_factory = opts.redis_factory if opts.redis_factory
473
+ @redis_factory ||= proc {|opts| Redis.new opts }
474
+ raise TypeError, "redis_factory should respond to [] method" unless @redis_factory.respond_to?(:[])
475
+ @@ns = opts.ns if opts.ns
467
476
  unless (@@redis_pool = redis)
468
- unless @@connection_pool_class
477
+ unless @connection_pool_class
469
478
  begin
470
479
  require 'em-synchrony/connection_pool' unless defined?(::EM::Synchrony::ConnectionPool)
471
480
  rescue LoadError
472
481
  raise ":connection_pool_class required; could not fall back to EM::Synchrony::ConnectionPool - gem install em-synchrony"
473
482
  end
474
- @@connection_pool_class = ::EM::Synchrony::ConnectionPool
483
+ @connection_pool_class = ::EM::Synchrony::ConnectionPool
475
484
  end
476
- @@redis_pool = @@connection_pool_class.new(size: pool_size) do
477
- Redis.new redis_options
485
+ @@redis_pool = @connection_pool_class.new(size: pool_size) do
486
+ @redis_factory[redis_options]
478
487
  end
479
488
  end
480
- @@redis_watcher = Redis.new redis_options
489
+ @redis_watcher = @redis_factory[redis_options]
481
490
  start_watcher if ::EM.reactor_running?
482
491
  end
483
492
 
493
+ def ready?
494
+ !!@@redis_pool
495
+ end
496
+
484
497
  # resets Mutex's automatic name generator
485
498
  def reset_autoname
486
499
  @@name_index = AUTO_NAME_SEED
@@ -497,20 +510,20 @@ class Redis
497
510
  # If EventMachine is to be re-started (or after EM.fork_reactor) this method may be used instead of
498
511
  # Mutex.setup for "lightweight" startup procedure.
499
512
  def start_watcher
500
- raise MutexError, "call #{self.class}::setup first" unless @@redis_watcher
513
+ raise MutexError, "call #{self.class}::setup first" unless @redis_watcher
501
514
  return if watching?
502
515
  if @@watching # Process id changed, we've been forked alive!
503
- @@redis_watcher = Redis.new @redis_options
516
+ @redis_watcher = @redis_factory[@redis_options]
504
517
  @@signal_queue.clear
505
518
  end
506
519
  @@watching = $$
507
520
  retries = 0
508
521
  Fiber.new do
509
522
  begin
510
- @@redis_watcher.subscribe(SIGNAL_QUEUE_CHANNEL) do |on|
523
+ @redis_watcher.subscribe(SIGNAL_QUEUE_CHANNEL) do |on|
511
524
  on.subscribe do |channel,|
512
525
  if channel == SIGNAL_QUEUE_CHANNEL
513
- @@watcher_subscribed = true
526
+ @watcher_subscribed = true
514
527
  retries = 0
515
528
  wakeup_queue_all
516
529
  end
@@ -527,22 +540,22 @@ class Redis
527
540
  end
528
541
  end
529
542
  on.unsubscribe do |channel,|
530
- @@watcher_subscribed = false if channel == SIGNAL_QUEUE_CHANNEL
543
+ @watcher_subscribed = false if channel == SIGNAL_QUEUE_CHANNEL
531
544
  end
532
545
  end
533
546
  break
534
547
  rescue Redis::BaseConnectionError, EventMachine::ConnectionError => e
535
- @@watcher_subscribed = false
548
+ @watcher_subscribed = false
536
549
  warn e.message
537
550
  retries+= 1
538
- if retries > @@connection_retry_max && @@connection_retry_max >= 0
551
+ if retries > reconnect_max_retries && reconnect_max_retries >= 0
539
552
  @@watching = false
540
553
  else
541
554
  sleep retries > 1 ? 1 : 0.1
542
555
  end
543
556
  end while watching?
544
557
  end.resume
545
- until @@watcher_subscribed
558
+ until @watcher_subscribed
546
559
  raise MutexError, "Can not establish watcher channel connection!" unless watching?
547
560
  fiber = Fiber.current
548
561
  ::EM.next_tick { fiber.resume }
@@ -558,22 +571,22 @@ class Redis
558
571
  end
559
572
 
560
573
  # Stops the watcher of the "unlock" channel.
561
- # It should be called before stoping EvenMachine otherwise
574
+ # It should be called before stopping EvenMachine otherwise
562
575
  # EM might wait forever for channel connection to be closed.
563
576
  #
564
- # Raises MutexError if there are still some fibers waiting for a lock.
577
+ # Raises MutexError if there are still some fibers waiting for lock.
565
578
  # Pass `true` to forcefully stop it. This might instead cause
566
579
  # MutexError to be raised in waiting fibers.
567
580
  def stop_watcher(force = false)
568
581
  return unless watching?
569
- raise MutexError, "call #{self.class}::setup first" unless @@redis_watcher
582
+ raise MutexError, "call #{self.class}::setup first" unless @redis_watcher
570
583
  unless @@signal_queue.empty? || force
571
584
  raise MutexError, "can't stop: active signal queue handlers"
572
585
  end
573
586
  @@watching = false
574
- if @@watcher_subscribed
575
- @@redis_watcher.unsubscribe SIGNAL_QUEUE_CHANNEL
576
- while @@watcher_subscribed
587
+ if @watcher_subscribed
588
+ @redis_watcher.unsubscribe SIGNAL_QUEUE_CHANNEL
589
+ while @watcher_subscribed
577
590
  fiber = Fiber.current
578
591
  ::EM.next_tick { fiber.resume }
579
592
  Fiber.yield
@@ -15,9 +15,9 @@ Gem::Specification.new do |s|
15
15
  s.files = `git ls-files`.split("\n") - ['.gitignore']
16
16
  s.test_files = Dir.glob("spec/**/*")
17
17
  s.rdoc_options << "--title" << "redis-em-mutex" <<
18
- "--main" << "README.rdoc"
18
+ "--main" << "README.md"
19
19
  s.has_rdoc = true
20
- s.extra_rdoc_files = ["README.rdoc"]
20
+ s.extra_rdoc_files = ["README.md"]
21
21
  s.requirements << "Redis server 2.4+"
22
22
  s.add_runtime_dependency "redis", ">= 3.0.1"
23
23
  s.add_runtime_dependency "hiredis", "~> 0.4.5"
@@ -106,14 +106,19 @@ describe Redis::EM::Mutex do
106
106
  mutex = described_class.new(*@lock_names)
107
107
  resource = ::EM::Synchrony::Thread::ConditionVariable.new
108
108
  signal = nil
109
+ delta = nil
109
110
  fiber = Fiber.current
110
111
  ::EM::Synchrony.next_tick do
111
112
  mutex.synchronize {
113
+ signal = true
112
114
  resource.wait(mutex)
113
115
  fiber.resume Time.now
114
116
  }
115
117
  end
116
118
  ::EM::Synchrony.next_tick do
119
+ t1 = Time.now
120
+ ::EM::Synchrony.sleep(0.01) while signal.nil?
121
+ delta = Time.now - t1
117
122
  mutex.synchronize {
118
123
  ::EM::Synchrony.sleep(0.2)
119
124
  resource.signal
@@ -123,7 +128,7 @@ describe Redis::EM::Mutex do
123
128
  start = Time.now
124
129
  now = Fiber.yield
125
130
  (now - signal).should be_within(0.002).of(0.001)
126
- (now - start).should be_within(0.01).of(0.2)
131
+ (now - start).should be_within(0.01).of(0.2 + delta)
127
132
  mutex.synchronize do
128
133
  signal = nil
129
134
  end
@@ -3,6 +3,8 @@ require 'em-synchrony'
3
3
  require 'em-synchrony/connection_pool'
4
4
  require 'redis-em-mutex'
5
5
 
6
+ class TestDummyConnectionPool < EM::Synchrony::ConnectionPool; end
7
+
6
8
  describe Redis::EM::Mutex do
7
9
 
8
10
  it "should raise MutexError while redis server not found on setup" do
@@ -22,7 +24,7 @@ describe Redis::EM::Mutex do
22
24
  it "should setup with redis connection pool" do
23
25
  described_class.setup(redis: @redis_pool)
24
26
  described_class.class_variable_get(:'@@redis_pool').should be @redis_pool
25
- redis = described_class.class_variable_get(:'@@redis_watcher')
27
+ redis = described_class.instance_variable_get(:'@redis_watcher')
26
28
  described_class.stop_watcher
27
29
  redis.should be_an_instance_of Redis
28
30
  redis.client.host.should eq 'localhost'
@@ -32,15 +34,18 @@ describe Redis::EM::Mutex do
32
34
 
33
35
  it "should setup with various options" do
34
36
  described_class.setup do |cfg|
35
- cfg.expire = 42 # - sets global Mutex.default_expire
36
- cfg.ns = 'redis rulez!' # - sets global Mutex.namespace
37
- cfg.reconnect_max = -1 # - maximum num. of attempts to re-establish
37
+ cfg.expire = 42 # - sets global Mutex.default_expire
38
+ cfg.ns = 'redis rulez!' # - sets global Mutex.namespace
39
+ cfg.reconnect_max = :forever # - maximum num. of attempts to re-establish
38
40
  cfg.url = 'redis://127.0.0.1/2'
39
41
  cfg.size = 10
40
42
  end
41
43
  described_class.namespace.should eq 'redis rulez!'
42
44
  described_class.default_expire.should eq 42
43
- described_class.class_variable_get(:'@@connection_retry_max').should eq -1
45
+ described_class.reconnect_forever?.should be true
46
+ described_class.instance_variable_get(:'@reconnect_max_retries').should eq -1
47
+ described_class.reconnect_max_retries = 0
48
+ described_class.reconnect_forever?.should be false
44
49
  redis_pool = described_class.class_variable_get(:'@@redis_pool')
45
50
  redis_pool.should be_an_instance_of EM::Synchrony::ConnectionPool
46
51
  redis_pool.should_not be @redis_pool
@@ -48,7 +53,7 @@ describe Redis::EM::Mutex do
48
53
  redis_pool.client.db.should eq 2
49
54
  redis_pool.client.port.should eq 6379
50
55
  redis_pool.client.scheme.should eq 'redis'
51
- redis = described_class.class_variable_get(:'@@redis_watcher')
56
+ redis = described_class.instance_variable_get(:'@redis_watcher')
52
57
  described_class.stop_watcher
53
58
  redis.should be_an_instance_of Redis
54
59
  redis.client.host.should eq '127.0.0.1'
@@ -63,15 +68,16 @@ describe Redis::EM::Mutex do
63
68
  cfg.host = 'localhost'
64
69
  cfg.port = 6379
65
70
  cfg.db = 3
71
+ cfg.connection_pool_class = TestDummyConnectionPool
66
72
  end
67
73
  redis_pool = described_class.class_variable_get(:'@@redis_pool')
68
- redis_pool.should be_an_instance_of EM::Synchrony::ConnectionPool
74
+ redis_pool.should be_an_instance_of TestDummyConnectionPool
69
75
  redis_pool.should_not be @redis_pool
70
76
  redis_pool.client.host.should eq 'localhost'
71
77
  redis_pool.client.db.should eq 3
72
78
  redis_pool.client.port.should eq 6379
73
79
  redis_pool.client.scheme.should eq 'redis'
74
- redis = described_class.class_variable_get(:'@@redis_watcher')
80
+ redis = described_class.instance_variable_get(:'@redis_watcher')
75
81
  redis.should be_an_instance_of Redis
76
82
  described_class.stop_watcher
77
83
  redis.client.host.should eq 'localhost'
@@ -80,17 +86,50 @@ describe Redis::EM::Mutex do
80
86
  redis.client.scheme.should eq 'redis'
81
87
  end
82
88
 
89
+ it "should be able to setup with redis factory" do
90
+ counter = 0
91
+ described_class.setup do |cfg|
92
+ cfg.redis = @redis_pool
93
+ cfg.redis_factory = proc do |opts|
94
+ counter += 1
95
+ Redis.new opts
96
+ end
97
+ end
98
+ counter.should eq 1
99
+ counter = 0
100
+ described_class.setup do |cfg|
101
+ cfg.size = 5
102
+ cfg.redis_factory = proc do |opts|
103
+ counter += 1
104
+ Redis.new opts
105
+ end
106
+ end
107
+ counter.should eq 6
108
+ end
109
+
110
+
83
111
  it "should be able to sleep" do
84
112
  t = Time.now
85
113
  described_class.sleep 0.11
86
114
  (Time.now - t).should be_within(0.02).of(0.11)
87
115
  end
88
116
 
117
+ it "should not change uuid" do
118
+ @uuid.should be_an_instance_of String
119
+ described_class.class_variable_get(:@@uuid).should eq @uuid
120
+ end
121
+
89
122
  around(:each) do |testcase|
90
123
  @after_em_stop = nil
124
+ @exception = nil
91
125
  ::EM.synchrony do
92
126
  begin
93
127
  testcase.call
128
+ raise @exception if @exception
129
+ described_class.stop_watcher
130
+ rescue => e
131
+ described_class.stop_watcher(true)
132
+ raise e
94
133
  ensure
95
134
  ::EM.stop
96
135
  end
@@ -100,5 +139,6 @@ describe Redis::EM::Mutex do
100
139
 
101
140
  before(:all) do
102
141
  @redis_pool = EM::Synchrony::ConnectionPool.new(size: 10) { Redis.new url: 'redis://localhost/1' }
142
+ @uuid = described_class.class_variable_get(:@@uuid)
103
143
  end
104
144
  end
@@ -6,6 +6,10 @@ require 'redis-em-mutex'
6
6
 
7
7
  describe Redis::EM::Mutex do
8
8
 
9
+ it "should be ready" do
10
+ described_class.ready?.should be true
11
+ end
12
+
9
13
  it "should lock and prevent locking on the same semaphore" do
10
14
  described_class.new(@lock_names.first).owned?.should be false
11
15
  mutex = described_class.lock(@lock_names.first)
@@ -83,6 +87,7 @@ describe Redis::EM::Mutex do
83
87
  mutex.should be_an_instance_of described_class
84
88
  mutex.owned?.should be true
85
89
  locked = true
90
+ start = nil
86
91
  ::EM::Synchrony.next_tick do
87
92
  begin
88
93
  locked.should be true
@@ -96,7 +101,6 @@ describe Redis::EM::Mutex do
96
101
  end
97
102
  mutex.owned?.should be false
98
103
  ::EM::Synchrony.sleep 0.1
99
- start = Time.now
100
104
  ::EM::Synchrony::FiberIterator.new(@lock_names, @lock_names.length).each do |name|
101
105
  begin
102
106
  locked.should be true
@@ -122,6 +126,7 @@ describe Redis::EM::Mutex do
122
126
 
123
127
  locked = true
124
128
  mutex.lock.should be true
129
+ start = Time.now
125
130
  ::EM::Synchrony.sleep 0.25
126
131
  locked = 10
127
132
  mutex.unlock.should be_an_instance_of described_class
@@ -283,19 +288,24 @@ describe Redis::EM::Mutex do
283
288
  end
284
289
 
285
290
  it "should lock and the other process should acquire lock as soon as possible" do
291
+ described_class.watching?.should be false
286
292
  mutex = described_class.lock(*@lock_names)
293
+ described_class.watching?.should be true
287
294
  mutex.should be_an_instance_of described_class
288
295
  time_key1 = SecureRandom.random_bytes
289
296
  time_key2 = SecureRandom.random_bytes
290
297
  ::EM.fork_reactor do
298
+ described_class.watching?.should be false
291
299
  Fiber.new do
292
300
  begin
293
301
  redis = Redis.new @redis_options
294
302
  redis.set time_key1, Time.now.to_f.to_s
295
303
  mutex.synchronize do
304
+ described_class.watching?.should be true
296
305
  redis.set time_key2, Time.now.to_f.to_s
297
306
  end
298
307
  described_class.stop_watcher(false)
308
+ described_class.watching?.should be false
299
309
  # rescue => e
300
310
  # warn e.inspect
301
311
  ensure
@@ -303,7 +313,9 @@ describe Redis::EM::Mutex do
303
313
  end
304
314
  end.resume
305
315
  end
316
+ described_class.watching?.should be true
306
317
  EM::Synchrony.sleep 0.25
318
+ described_class.watching?.should be true
307
319
  mutex.owned?.should be true
308
320
  mutex.unlock.should be_an_instance_of described_class
309
321
  time = Time.now.to_f
@@ -314,7 +326,6 @@ describe Redis::EM::Mutex do
314
326
  t1.should be_an_instance_of String
315
327
  t1.to_f.should be < time - 0.25
316
328
  t2.should be_an_instance_of String
317
- t2.to_f.should be > time
318
329
  t2.to_f.should be_within(0.001).of(time)
319
330
  redis.del(time_key1, time_key2)
320
331
  end
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.1
4
+ version: 0.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-26 00:00:00.000000000 Z
12
+ date: 2013-02-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: &219325340 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: 3.0.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *219325340
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.1
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: hiredis
27
- requirement: &219340740 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 0.4.5
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *219340740
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.4.5
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: eventmachine
38
- requirement: &219339740 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: 1.0.0.beta.1
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *219339740
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0.beta.1
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: rspec
49
- requirement: &219338960 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ~>
@@ -54,10 +69,15 @@ dependencies:
54
69
  version: 2.8.0
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *219338960
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.8.0
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: eventmachine
60
- requirement: &219338220 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ~>
@@ -65,10 +85,15 @@ dependencies:
65
85
  version: 1.0.0
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *219338220
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.0.0
69
94
  - !ruby/object:Gem::Dependency
70
95
  name: em-synchrony
71
- requirement: &219337400 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
72
97
  none: false
73
98
  requirements:
74
99
  - - ~>
@@ -76,17 +101,22 @@ dependencies:
76
101
  version: 1.0.0
77
102
  type: :development
78
103
  prerelease: false
79
- version_requirements: *219337400
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 1.0.0
80
110
  description: Cross server-process-fiber EventMachine + Redis based semaphore with
81
111
  many features
82
112
  email: rafal@yeondir.com
83
113
  executables: []
84
114
  extensions: []
85
115
  extra_rdoc_files:
86
- - README.rdoc
116
+ - README.md
87
117
  files:
88
118
  - HISTORY.rdoc
89
- - README.rdoc
119
+ - README.md
90
120
  - Rakefile
91
121
  - lib/redis-em-mutex.rb
92
122
  - lib/redis/em-mutex.rb
@@ -107,7 +137,7 @@ rdoc_options:
107
137
  - --title
108
138
  - redis-em-mutex
109
139
  - --main
110
- - README.rdoc
140
+ - README.md
111
141
  require_paths:
112
142
  - lib
113
143
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -125,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
155
  requirements:
126
156
  - Redis server 2.4+
127
157
  rubyforge_project:
128
- rubygems_version: 1.8.17
158
+ rubygems_version: 1.8.25
129
159
  signing_key:
130
160
  specification_version: 3
131
161
  summary: Cross server-process-fiber EventMachine + Redis based semaphore