redis-em-mutex 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +6 -0
- data/README.rdoc +53 -4
- data/Rakefile +3 -1
- data/lib/redis/em-mutex/macro.rb +123 -0
- data/lib/redis/em-mutex/version.rb +7 -0
- data/lib/redis/em-mutex.rb +9 -4
- data/lib/redis-em-mutex.rb +7 -3
- data/redis-em-mutex.gemspec +5 -5
- data/spec/redis-em-mutex-features.rb +65 -0
- data/spec/redis-em-mutex-macros.rb +232 -0
- data/spec/redis-em-mutex-namespaces.rb +1 -1
- data/spec/redis-em-mutex-semaphores.rb +2 -2
- metadata +23 -19
data/HISTORY.rdoc
CHANGED
data/README.rdoc
CHANGED
@@ -20,6 +20,7 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
|
|
20
20
|
* fiber-safe
|
21
21
|
* deadlock detection (only trivial cases: locking twice the same resource from the same fiber)
|
22
22
|
* mandatory lock expiration (with refreshing)
|
23
|
+
* macro-style definitions (Mutex::Macro mixin)
|
23
24
|
|
24
25
|
== BUGS/LIMITATIONS
|
25
26
|
|
@@ -31,8 +32,8 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
|
|
31
32
|
== REQUIREMENTS
|
32
33
|
|
33
34
|
* ruby >= 1.9 (tested: 1.9.3-p194, 1.9.2-p320, 1.9.1-p378)
|
34
|
-
* http://github.com/redis/redis-rb
|
35
|
-
* http://rubyeventmachine.com
|
35
|
+
* http://github.com/redis/redis-rb ~> 3.0.1
|
36
|
+
* http://rubyeventmachine.com ~> 1.0.0
|
36
37
|
* (optional) http://github.com/igrigorik/em-synchrony
|
37
38
|
|
38
39
|
== INSTALL
|
@@ -41,7 +42,7 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
|
|
41
42
|
|
42
43
|
==== Gemfile
|
43
44
|
|
44
|
-
gem "redis-em-mutex", "~> 0.1.
|
45
|
+
gem "redis-em-mutex", "~> 0.1.2"
|
45
46
|
|
46
47
|
==== Github
|
47
48
|
|
@@ -100,7 +101,7 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
|
|
100
101
|
|
101
102
|
EM.synchrony do
|
102
103
|
ns.synchronize('foo') do
|
103
|
-
.... do something with foo
|
104
|
+
.... do something with foo
|
104
105
|
end
|
105
106
|
...
|
106
107
|
EM.stop
|
@@ -140,6 +141,54 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
|
|
140
141
|
EM.stop
|
141
142
|
end
|
142
143
|
|
144
|
+
=== Macro-style definition
|
145
|
+
|
146
|
+
Borrowed from http://github.com/kenn/redis-mutex.
|
147
|
+
Redis::EM::Mutex::Macro is a mixin which protects selected instance methods of a class with a mutex.
|
148
|
+
The locking scope will be Mutex global namespace + class name + method name.
|
149
|
+
|
150
|
+
class TheClass
|
151
|
+
include Redis::EM::Mutex::Macro
|
152
|
+
|
153
|
+
auto_mutex
|
154
|
+
def critical_run
|
155
|
+
... do some critical stuff
|
156
|
+
....only one fiber in one process on one machine is executing this instance method of any instance of defined class
|
157
|
+
end
|
158
|
+
|
159
|
+
auto_mutex expire: 100, ns: '**TheClass**'
|
160
|
+
# all critical methods defined later will inherit above options unless overridden
|
161
|
+
|
162
|
+
auto_mutex # start and stop will be protected
|
163
|
+
def start
|
164
|
+
...
|
165
|
+
end
|
166
|
+
|
167
|
+
def stop
|
168
|
+
...
|
169
|
+
end
|
170
|
+
|
171
|
+
no_auto_mutex
|
172
|
+
def some_unprotected_method
|
173
|
+
...
|
174
|
+
end
|
175
|
+
|
176
|
+
auto_mutex :run_long, expire: 100000, block: 10, on_timeout: :cant_run
|
177
|
+
def run_long
|
178
|
+
...
|
179
|
+
end
|
180
|
+
|
181
|
+
def cant_run
|
182
|
+
...
|
183
|
+
end
|
184
|
+
|
185
|
+
def foo
|
186
|
+
...
|
187
|
+
end
|
188
|
+
auto_mutex :foo, block: 0, on_timeout: proc { puts 'bar!' }
|
189
|
+
|
190
|
+
end
|
191
|
+
|
143
192
|
=== Advanced
|
144
193
|
|
145
194
|
mutex = Redis::EM::Mutex.new('resource1', 'resource2', expire: 60)
|
data/Rakefile
CHANGED
@@ -0,0 +1,123 @@
|
|
1
|
+
class Redis
|
2
|
+
module EM
|
3
|
+
class Mutex
|
4
|
+
# Macro-style definition
|
5
|
+
#
|
6
|
+
# idea and some code borrowed from http://github.com/kenn/redis-mutex and enhanced
|
7
|
+
#
|
8
|
+
# class ClassWithCriticalMethods
|
9
|
+
# include Redis::EM::Mutex::Macro
|
10
|
+
#
|
11
|
+
# auto_mutex
|
12
|
+
# def critical
|
13
|
+
# ... do some critical stuff
|
14
|
+
# ....only one fiber in one process on one machine is executing this instance method of any instance of defined class
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
module Macro
|
19
|
+
def self.included(klass)
|
20
|
+
klass.extend ClassMethods
|
21
|
+
klass.class_eval do
|
22
|
+
class << self
|
23
|
+
attr_reader :auto_mutex_methods, :auto_mutex_options
|
24
|
+
attr_accessor :auto_mutex_enabled
|
25
|
+
end
|
26
|
+
@auto_mutex_methods = {}
|
27
|
+
@auto_mutex_options = {:ns => Redis::EM::Mutex.ns ? "#{Redis::EM::Mutex.ns}:#{klass.name}" : klass.name}
|
28
|
+
@auto_mutex_enabled = false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
# auto_mutex [*method_names], [options]
|
34
|
+
#
|
35
|
+
# options are:
|
36
|
+
# - :expire - see Mutex.new
|
37
|
+
# - :block - see Mutex.new
|
38
|
+
# - :ns - custom namespace, if not present name of the class that includes Macro is used
|
39
|
+
# - :on_timeout - if defined, this proc/method will be called instead of raising MutexTimeout error
|
40
|
+
#
|
41
|
+
# If method_names are provided (names of already defined methods or defined in the future)
|
42
|
+
# they become protected with mutex.
|
43
|
+
#
|
44
|
+
# If options are provided wihout method_names, they will become default options
|
45
|
+
# for subsequent auto_mutex calls.
|
46
|
+
#
|
47
|
+
# If auto_mutex is called without arguments then any method further defined will also be protected.
|
48
|
+
# To disable implicit auto_mutex use no_auto_mutex.
|
49
|
+
def auto_mutex(*args)
|
50
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
51
|
+
if args.each {|target|
|
52
|
+
self.auto_mutex_methods[target] = self.auto_mutex_options.merge(options)
|
53
|
+
auto_mutex_method_added(target) if method_defined? target
|
54
|
+
}.empty?
|
55
|
+
if options.empty?
|
56
|
+
self.auto_mutex_enabled = true
|
57
|
+
else
|
58
|
+
self.auto_mutex_options.update(options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Switch off implicit auto_mutex.
|
64
|
+
# After calling no_auto_mutex if any new method is defined it won't be protected
|
65
|
+
# unless explicitely specified with auto_mutex.
|
66
|
+
def no_auto_mutex
|
67
|
+
self.auto_mutex_enabled = false
|
68
|
+
end
|
69
|
+
|
70
|
+
def method_added(target)
|
71
|
+
return if target.to_s =~ /_(?:with|without|on_timeout)_auto_mutex$/
|
72
|
+
return unless self.auto_mutex_methods[target] || self.auto_mutex_enabled
|
73
|
+
auto_mutex_method_added(target)
|
74
|
+
end
|
75
|
+
|
76
|
+
def auto_mutex_method_added(target)
|
77
|
+
without_method = "#{target}_without_auto_mutex"
|
78
|
+
with_method = "#{target}_with_auto_mutex"
|
79
|
+
timeout_method = "#{target}_on_timeout_auto_mutex"
|
80
|
+
return if method_defined?(without_method)
|
81
|
+
|
82
|
+
options = self.auto_mutex_methods[target] || self.auto_mutex_options
|
83
|
+
mutex = nil
|
84
|
+
|
85
|
+
on_timeout = options[:on_timeout] || options[:after_failure]
|
86
|
+
|
87
|
+
if on_timeout.respond_to?(:call)
|
88
|
+
define_method(timeout_method, &on_timeout)
|
89
|
+
elsif on_timeout.is_a?(Symbol)
|
90
|
+
timeout_method = on_timeout
|
91
|
+
end
|
92
|
+
|
93
|
+
define_method(with_method) do |*args, &blk|
|
94
|
+
mutex||= Redis::EM::Mutex.new target, options
|
95
|
+
response = nil
|
96
|
+
|
97
|
+
begin
|
98
|
+
if mutex.refresh
|
99
|
+
response = __send__(without_method, *args, &blk)
|
100
|
+
else
|
101
|
+
mutex.synchronize do
|
102
|
+
response = __send__(without_method, *args, &blk)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
rescue Redis::EM::Mutex::MutexTimeout => e
|
106
|
+
if respond_to?(timeout_method)
|
107
|
+
response = __send__(timeout_method, *args, &blk)
|
108
|
+
else
|
109
|
+
raise e
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
response
|
114
|
+
end
|
115
|
+
|
116
|
+
alias_method without_method, target
|
117
|
+
alias_method target, with_method
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/redis/em-mutex.rb
CHANGED
@@ -3,6 +3,7 @@ require 'ostruct'
|
|
3
3
|
require 'securerandom'
|
4
4
|
require 'redis/connection/synchrony' unless defined? Redis::Connection::Synchrony
|
5
5
|
require 'redis'
|
6
|
+
require 'redis/em-mutex/version'
|
6
7
|
|
7
8
|
class Redis
|
8
9
|
module EM
|
@@ -13,12 +14,15 @@ class Redis
|
|
13
14
|
# Methods of this class are NOT thread-safe.
|
14
15
|
# They are machine/process/fiber-safe.
|
15
16
|
# All method calls must be invoked only from EventMachine's reactor thread.
|
17
|
+
# Wrap mutex calls in EventMachine.shedule from non-reactor threads.
|
16
18
|
#
|
17
19
|
# - The terms "lock" and "semaphore" used in documentation are synonims.
|
18
|
-
# - The term "owner" denotes a Ruby Fiber in
|
20
|
+
# - The term "owner" denotes a Ruby Fiber executing code in the scope of Machine/Process/Fiber
|
21
|
+
# possessing exclusively a named semaphore(s).
|
19
22
|
#
|
20
23
|
class Mutex
|
21
|
-
|
24
|
+
|
25
|
+
autoload :Macro, 'redis/em-mutex/macro'
|
22
26
|
|
23
27
|
module Errors
|
24
28
|
class MutexError < RuntimeError; end
|
@@ -125,7 +129,8 @@ class Redis
|
|
125
129
|
end
|
126
130
|
end
|
127
131
|
|
128
|
-
# Returns `true` if this semaphore (all the locked `names`) is currently being held by calling fiber
|
132
|
+
# Returns `true` if this semaphore (all the locked `names`) is currently being held by calling fiber
|
133
|
+
# (if executing fiber is the owner).
|
129
134
|
def owned?
|
130
135
|
!!if @locked_id
|
131
136
|
lock_full_ident = owner_ident(@locked_id)
|
@@ -360,7 +365,7 @@ class Redis
|
|
360
365
|
}.reject {|_k, v| v.nil?})
|
361
366
|
end
|
362
367
|
if (redis = opts.redis) && !opts.url
|
363
|
-
redis_updater.call redis
|
368
|
+
redis_updater.call redis.client
|
364
369
|
elsif opts.url
|
365
370
|
redis_options[:url] = opts.url
|
366
371
|
end
|
data/lib/redis-em-mutex.rb
CHANGED
data/redis-em-mutex.gemspec
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
$:.unshift "lib"
|
2
|
-
require 'redis/em-mutex'
|
2
|
+
require 'redis/em-mutex/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "redis-em-mutex"
|
@@ -18,11 +18,11 @@ Gem::Specification.new do |s|
|
|
18
18
|
"--main" << "README.rdoc"
|
19
19
|
s.has_rdoc = true
|
20
20
|
s.extra_rdoc_files = ["README.rdoc"]
|
21
|
-
s.requirements << "Redis server"
|
22
|
-
s.add_runtime_dependency "redis", ">= 3.0.
|
21
|
+
s.requirements << "Redis server 2.4+"
|
22
|
+
s.add_runtime_dependency "redis", ">= 3.0.1"
|
23
23
|
s.add_runtime_dependency "hiredis", "~> 0.4.5"
|
24
|
-
s.add_runtime_dependency "eventmachine", ">= 0.
|
24
|
+
s.add_runtime_dependency "eventmachine", ">= 1.0.0.beta.1"
|
25
25
|
s.add_development_dependency "rspec", "~> 2.8.0"
|
26
|
-
s.add_development_dependency "eventmachine", "
|
26
|
+
s.add_development_dependency "eventmachine", "~> 1.0.0"
|
27
27
|
s.add_development_dependency "em-synchrony", "~> 1.0.0"
|
28
28
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
$:.unshift "lib"
|
2
2
|
require 'em-synchrony'
|
3
|
+
require 'em-synchrony/connection_pool'
|
3
4
|
require 'redis-em-mutex'
|
4
5
|
|
5
6
|
describe Redis::EM::Mutex do
|
@@ -18,6 +19,67 @@ describe Redis::EM::Mutex do
|
|
18
19
|
}.to raise_error(described_class::MutexError, /Can not establish watcher channel connection!/)
|
19
20
|
end
|
20
21
|
|
22
|
+
it "should setup with redis connection pool" do
|
23
|
+
described_class.setup(redis: @redis_pool)
|
24
|
+
described_class.class_variable_get(:'@@redis_pool').should be @redis_pool
|
25
|
+
redis = described_class.class_variable_get(:'@@redis_watcher')
|
26
|
+
described_class.stop_watcher
|
27
|
+
redis.should be_an_instance_of Redis
|
28
|
+
redis.client.host.should eq 'localhost'
|
29
|
+
redis.client.db.should eq 1
|
30
|
+
redis.client.scheme.should eq 'redis'
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should setup with various options" do
|
34
|
+
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
|
38
|
+
cfg.url = 'redis://127.0.0.1/2'
|
39
|
+
cfg.size = 10
|
40
|
+
end
|
41
|
+
described_class.namespace.should eq 'redis rulez!'
|
42
|
+
described_class.default_expire.should eq 42
|
43
|
+
described_class.class_variable_get(:'@@connection_retry_max').should eq -1
|
44
|
+
redis_pool = described_class.class_variable_get(:'@@redis_pool')
|
45
|
+
redis_pool.should be_an_instance_of EM::Synchrony::ConnectionPool
|
46
|
+
redis_pool.should_not be @redis_pool
|
47
|
+
redis_pool.client.host.should eq '127.0.0.1'
|
48
|
+
redis_pool.client.db.should eq 2
|
49
|
+
redis_pool.client.port.should eq 6379
|
50
|
+
redis_pool.client.scheme.should eq 'redis'
|
51
|
+
redis = described_class.class_variable_get(:'@@redis_watcher')
|
52
|
+
described_class.stop_watcher
|
53
|
+
redis.should be_an_instance_of Redis
|
54
|
+
redis.client.host.should eq '127.0.0.1'
|
55
|
+
redis.client.db.should eq 2
|
56
|
+
redis.client.port.should eq 6379
|
57
|
+
redis.client.scheme.should eq 'redis'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should setup with separate redis options" do
|
61
|
+
described_class.setup do |cfg|
|
62
|
+
cfg.scheme = 'redis'
|
63
|
+
cfg.host = 'localhost'
|
64
|
+
cfg.port = 6379
|
65
|
+
cfg.db = 3
|
66
|
+
end
|
67
|
+
redis_pool = described_class.class_variable_get(:'@@redis_pool')
|
68
|
+
redis_pool.should be_an_instance_of EM::Synchrony::ConnectionPool
|
69
|
+
redis_pool.should_not be @redis_pool
|
70
|
+
redis_pool.client.host.should eq 'localhost'
|
71
|
+
redis_pool.client.db.should eq 3
|
72
|
+
redis_pool.client.port.should eq 6379
|
73
|
+
redis_pool.client.scheme.should eq 'redis'
|
74
|
+
redis = described_class.class_variable_get(:'@@redis_watcher')
|
75
|
+
redis.should be_an_instance_of Redis
|
76
|
+
described_class.stop_watcher
|
77
|
+
redis.client.host.should eq 'localhost'
|
78
|
+
redis.client.db.should eq 3
|
79
|
+
redis.client.port.should eq 6379
|
80
|
+
redis.client.scheme.should eq 'redis'
|
81
|
+
end
|
82
|
+
|
21
83
|
around(:each) do |testcase|
|
22
84
|
@after_em_stop = nil
|
23
85
|
::EM.synchrony do
|
@@ -30,4 +92,7 @@ describe Redis::EM::Mutex do
|
|
30
92
|
@after_em_stop.call if @after_em_stop
|
31
93
|
end
|
32
94
|
|
95
|
+
before(:all) do
|
96
|
+
@redis_pool = EM::Synchrony::ConnectionPool.new(size: 10) { Redis.new url: 'redis://localhost/1' }
|
97
|
+
end
|
33
98
|
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
require 'securerandom'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/fiber_iterator'
|
5
|
+
require 'redis-em-mutex'
|
6
|
+
|
7
|
+
class Test
|
8
|
+
include Redis::EM::Mutex::Macro
|
9
|
+
auto_mutex :method5
|
10
|
+
def method1; end
|
11
|
+
auto_mutex
|
12
|
+
def method2; end
|
13
|
+
no_auto_mutex
|
14
|
+
def method3; end
|
15
|
+
def method4; end
|
16
|
+
def method5; end
|
17
|
+
auto_mutex :method4
|
18
|
+
def method6; end
|
19
|
+
auto_mutex :method6, :method7, block: 10, on_timeout: proc { }
|
20
|
+
def method7; end
|
21
|
+
|
22
|
+
auto_mutex block: 20
|
23
|
+
|
24
|
+
auto_mutex
|
25
|
+
def test(redis, key, value)
|
26
|
+
redis.set key, value
|
27
|
+
::EM::Synchrony.sleep 0.1
|
28
|
+
redis.get key
|
29
|
+
end
|
30
|
+
|
31
|
+
def recursive(n=1, &blk)
|
32
|
+
if n < 10
|
33
|
+
recursive(n+1, &blk)
|
34
|
+
else
|
35
|
+
yield n
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
no_auto_mutex
|
40
|
+
def test_no_mutex(redis, key, value)
|
41
|
+
redis.set key, value
|
42
|
+
::EM::Synchrony.sleep 0.1
|
43
|
+
redis.get key
|
44
|
+
end
|
45
|
+
|
46
|
+
auto_mutex :may_timeout, block: 0, on_timeout: proc { false }
|
47
|
+
def may_timeout; yield; end
|
48
|
+
|
49
|
+
auto_mutex :may_timeout_no_fallback, block: 0
|
50
|
+
def may_timeout_no_fallback; yield; end
|
51
|
+
|
52
|
+
auto_mutex :may_timeout_method_fallback, block: 0, on_timeout: :may_timeout_timed_out
|
53
|
+
def may_timeout_method_fallback; yield; end
|
54
|
+
def may_timeout_timed_out; false; end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe Redis::EM::Mutex::Macro do
|
59
|
+
|
60
|
+
it "should define auto_mutex methods" do
|
61
|
+
Test.auto_mutex_methods.keys.should eq [:method5, :method4, :method6, :method7, :may_timeout,
|
62
|
+
:may_timeout_no_fallback, :may_timeout_method_fallback]
|
63
|
+
[:method5, :method2, :method4, :test, :recursive,
|
64
|
+
:may_timeout_no_fallback, :may_timeout_method_fallback].each do |name|
|
65
|
+
Test.method_defined?(name).should be_true
|
66
|
+
Test.method_defined?("#{name}_without_auto_mutex").should be_true
|
67
|
+
Test.method_defined?("#{name}_with_auto_mutex").should be_true
|
68
|
+
Test.method_defined?("#{name}_on_timeout_auto_mutex").should be_false
|
69
|
+
end
|
70
|
+
[:method6, :method7, :may_timeout].each do |name|
|
71
|
+
Test.method_defined?(name).should be_true
|
72
|
+
Test.method_defined?("#{name}_without_auto_mutex").should be_true
|
73
|
+
Test.method_defined?("#{name}_with_auto_mutex").should be_true
|
74
|
+
Test.method_defined?("#{name}_on_timeout_auto_mutex").should be_true
|
75
|
+
end
|
76
|
+
[:method1, :method3, :test_no_mutex, :may_timeout_timed_out].each do |name|
|
77
|
+
Test.method_defined?(name).should be_true
|
78
|
+
Test.method_defined?("#{name}_without_auto_mutex").should be_false
|
79
|
+
Test.method_defined?("#{name}_with_auto_mutex").should be_false
|
80
|
+
Test.method_defined?("#{name}_on_timeout_auto_mutex").should be_false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should method run unprotected" do
|
85
|
+
iterate = 10.times.map { Test.new }
|
86
|
+
test_key = @test_key
|
87
|
+
results = {}
|
88
|
+
::EM::Synchrony::FiberIterator.new(iterate, iterate.length).each do |test|
|
89
|
+
begin
|
90
|
+
redis = Redis.new @redis_options
|
91
|
+
value = test.__id__.to_s
|
92
|
+
results[value] = test.test_no_mutex(redis, test_key, value)
|
93
|
+
rescue Exception => e
|
94
|
+
@exception = e
|
95
|
+
end
|
96
|
+
end
|
97
|
+
results.length.should eq iterate.length
|
98
|
+
results.each_pair do |k, v|
|
99
|
+
v.should eq results.values.last
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should protect auto_mutex method" do
|
104
|
+
iterate = 10.times.map { Test.new }
|
105
|
+
test_key = @test_key
|
106
|
+
results = {}
|
107
|
+
::EM::Synchrony::FiberIterator.new(iterate, iterate.length).each do |test|
|
108
|
+
begin
|
109
|
+
redis = Redis.new @redis_options
|
110
|
+
value = test.__id__.to_s
|
111
|
+
results[value] = test.test(redis, test_key, value)
|
112
|
+
rescue Exception => e
|
113
|
+
@exception = e
|
114
|
+
end
|
115
|
+
end
|
116
|
+
results.length.should eq iterate.length
|
117
|
+
results.each_pair do |k, v|
|
118
|
+
k.should eq v
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should allow recursive calls to protected methods" do
|
123
|
+
iterate = 10.times.map { Test.new }
|
124
|
+
test_key = @test_key
|
125
|
+
results = {}
|
126
|
+
::EM::Synchrony::FiberIterator.new(iterate, iterate.length).each do |test|
|
127
|
+
begin
|
128
|
+
redis = Redis.new @redis_options
|
129
|
+
value = test.__id__.to_s
|
130
|
+
results[value] = test.recursive do |n|
|
131
|
+
redis.set test_key, value
|
132
|
+
::EM::Synchrony.sleep 0.1
|
133
|
+
[redis.get(test_key), n]
|
134
|
+
end
|
135
|
+
rescue Exception => e
|
136
|
+
@exception = e
|
137
|
+
end
|
138
|
+
end
|
139
|
+
results.length.should eq iterate.length
|
140
|
+
results.each_pair do |k, v|
|
141
|
+
v.should eq [k, 10]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should call on_timout lambda when locking times out" do
|
146
|
+
::EM::Synchrony.next_tick do
|
147
|
+
begin
|
148
|
+
Test.new.may_timeout do
|
149
|
+
::EM::Synchrony.sleep 0.2
|
150
|
+
true
|
151
|
+
end.should be_true
|
152
|
+
rescue Exception => e
|
153
|
+
@exception = e
|
154
|
+
end
|
155
|
+
end
|
156
|
+
::EM::Synchrony.sleep 0.1
|
157
|
+
Test.new.may_timeout do
|
158
|
+
true
|
159
|
+
end.should be_false
|
160
|
+
::EM::Synchrony.sleep 0.15
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should raise MutexTimeout when locking times out" do
|
164
|
+
::EM::Synchrony.next_tick do
|
165
|
+
begin
|
166
|
+
Test.new.may_timeout_no_fallback do
|
167
|
+
::EM::Synchrony.sleep 0.2
|
168
|
+
true
|
169
|
+
end.should be_true
|
170
|
+
rescue Exception => e
|
171
|
+
@exception = e
|
172
|
+
end
|
173
|
+
end
|
174
|
+
::EM::Synchrony.sleep 0.1
|
175
|
+
expect {
|
176
|
+
Test.new.may_timeout_no_fallback do
|
177
|
+
true
|
178
|
+
end
|
179
|
+
}.to raise_error(Redis::EM::Mutex::MutexTimeout)
|
180
|
+
::EM::Synchrony.sleep 0.15
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should call on_timout method when locking times out" do
|
184
|
+
::EM::Synchrony.next_tick do
|
185
|
+
begin
|
186
|
+
Test.new.may_timeout_method_fallback do
|
187
|
+
::EM::Synchrony.sleep 0.2
|
188
|
+
true
|
189
|
+
end.should be_true
|
190
|
+
rescue Exception => e
|
191
|
+
@exception = e
|
192
|
+
end
|
193
|
+
end
|
194
|
+
::EM::Synchrony.sleep 0.1
|
195
|
+
Test.new.may_timeout_method_fallback do
|
196
|
+
true
|
197
|
+
end.should be_false
|
198
|
+
::EM::Synchrony.sleep 0.15
|
199
|
+
end
|
200
|
+
|
201
|
+
around(:each) do |testcase|
|
202
|
+
@after_em_stop = nil
|
203
|
+
@exception = nil
|
204
|
+
::EM.synchrony do
|
205
|
+
begin
|
206
|
+
testcase.call
|
207
|
+
raise @exception if @exception
|
208
|
+
Redis::EM::Mutex.stop_watcher(false)
|
209
|
+
rescue => e
|
210
|
+
Redis::EM::Mutex.stop_watcher(true)
|
211
|
+
raise e
|
212
|
+
ensure
|
213
|
+
::EM.stop
|
214
|
+
end
|
215
|
+
end
|
216
|
+
@after_em_stop.call if @after_em_stop
|
217
|
+
end
|
218
|
+
|
219
|
+
before(:all) do
|
220
|
+
@redis_options = {:driver => :synchrony}
|
221
|
+
@test_key = SecureRandom.base64(24)
|
222
|
+
Redis::EM::Mutex.setup @redis_options.merge(size: 11, ns: SecureRandom.base64(15))
|
223
|
+
end
|
224
|
+
|
225
|
+
after(:all) do
|
226
|
+
::EM.synchrony do
|
227
|
+
Redis.new(@redis_options).del @test_key
|
228
|
+
EM.stop
|
229
|
+
end
|
230
|
+
# @lock_names
|
231
|
+
end
|
232
|
+
end
|
@@ -323,7 +323,7 @@ describe Redis::EM::Mutex do
|
|
323
323
|
begin
|
324
324
|
testcase.call
|
325
325
|
raise @exception if @exception
|
326
|
-
described_class.stop_watcher
|
326
|
+
described_class.stop_watcher
|
327
327
|
rescue => e
|
328
328
|
described_class.stop_watcher(true)
|
329
329
|
raise e
|
@@ -335,7 +335,7 @@ describe Redis::EM::Mutex do
|
|
335
335
|
end
|
336
336
|
|
337
337
|
before(:all) do
|
338
|
-
@redis_options = {}
|
338
|
+
@redis_options = {:driver => :synchrony}
|
339
339
|
described_class.setup @redis_options.merge(size: 11)
|
340
340
|
@lock_names = 10.times.map {
|
341
341
|
SecureRandom.random_bytes
|
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.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
16
|
-
requirement: &
|
16
|
+
requirement: &90657860 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 3.0.
|
21
|
+
version: 3.0.1
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *90657860
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: hiredis
|
27
|
-
requirement: &
|
27
|
+
requirement: &90657060 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,21 +32,21 @@ dependencies:
|
|
32
32
|
version: 0.4.5
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *90657060
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: eventmachine
|
38
|
-
requirement: &
|
38
|
+
requirement: &90656280 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.
|
43
|
+
version: 1.0.0.beta.1
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *90656280
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &90655560 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,21 +54,21 @@ dependencies:
|
|
54
54
|
version: 2.8.0
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *90655560
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: eventmachine
|
60
|
-
requirement: &
|
60
|
+
requirement: &90654360 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
|
-
- -
|
63
|
+
- - ~>
|
64
64
|
- !ruby/object:Gem::Version
|
65
|
-
version: 1.0.0
|
65
|
+
version: 1.0.0
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *90654360
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: em-synchrony
|
71
|
-
requirement: &
|
71
|
+
requirement: &90652800 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: 1.0.0
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *90652800
|
80
80
|
description: Cross server-process-fiber EventMachine + Redis based semaphore with
|
81
81
|
many features
|
82
82
|
email: rafal@yeondir.com
|
@@ -90,8 +90,11 @@ files:
|
|
90
90
|
- Rakefile
|
91
91
|
- lib/redis-em-mutex.rb
|
92
92
|
- lib/redis/em-mutex.rb
|
93
|
+
- lib/redis/em-mutex/macro.rb
|
94
|
+
- lib/redis/em-mutex/version.rb
|
93
95
|
- redis-em-mutex.gemspec
|
94
96
|
- spec/redis-em-mutex-features.rb
|
97
|
+
- spec/redis-em-mutex-macros.rb
|
95
98
|
- spec/redis-em-mutex-namespaces.rb
|
96
99
|
- spec/redis-em-mutex-semaphores.rb
|
97
100
|
homepage: http://github.com/royaltm/redis-em-mutex
|
@@ -117,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
120
|
- !ruby/object:Gem::Version
|
118
121
|
version: '0'
|
119
122
|
requirements:
|
120
|
-
- Redis server
|
123
|
+
- Redis server 2.4+
|
121
124
|
rubyforge_project:
|
122
125
|
rubygems_version: 1.8.17
|
123
126
|
signing_key:
|
@@ -127,3 +130,4 @@ test_files:
|
|
127
130
|
- spec/redis-em-mutex-namespaces.rb
|
128
131
|
- spec/redis-em-mutex-semaphores.rb
|
129
132
|
- spec/redis-em-mutex-features.rb
|
133
|
+
- spec/redis-em-mutex-macros.rb
|