redis-em-mutex 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +9 -0
- data/README.rdoc +92 -2
- data/lib/redis/em-mutex/ns.rb +47 -0
- data/lib/redis/em-mutex/version.rb +1 -1
- data/lib/redis/em-mutex.rb +215 -132
- data/spec/redis-em-mutex-condition.rb +162 -0
- data/spec/redis-em-mutex-namespaces.rb +16 -16
- data/spec/redis-em-mutex-owners.rb +156 -0
- data/spec/redis-em-mutex-semaphores.rb +146 -76
- metadata +19 -14
@@ -0,0 +1,162 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
require 'securerandom'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/thread'
|
5
|
+
require 'redis-em-mutex'
|
6
|
+
|
7
|
+
describe Redis::EM::Mutex do
|
8
|
+
|
9
|
+
it "should lock and sleep forever until woken up" do
|
10
|
+
begin
|
11
|
+
mutex = described_class.lock(*@lock_names)
|
12
|
+
mutex.owned?.should be true
|
13
|
+
fiber = Fiber.current
|
14
|
+
start = Time.now
|
15
|
+
::EM.add_timer(0.25) do
|
16
|
+
mutex.wakeup(fiber)
|
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)
|
20
|
+
mutex.owned?.should be true
|
21
|
+
mutex.unlock!.should be_true
|
22
|
+
ensure
|
23
|
+
mutex.unlock if mutex
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should raise MutexError on sleep if unlocked" do
|
28
|
+
mutex = described_class.new(*@lock_names)
|
29
|
+
expect {
|
30
|
+
mutex.sleep
|
31
|
+
}.to raise_error(Redis::EM::Mutex::MutexError, /can't sleep #{described_class} wasn't locked/)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should lock and sleep with timeout" do
|
35
|
+
begin
|
36
|
+
mutex = described_class.lock(*@lock_names)
|
37
|
+
mutex.owned?.should be true
|
38
|
+
start = Time.now
|
39
|
+
mutex.sleep(0.25).should be_within(0.01).of(0.25)
|
40
|
+
(Time.now - start).should be_within(0.01).of(0.25)
|
41
|
+
mutex.owned?.should be true
|
42
|
+
mutex.unlock!.should be_true
|
43
|
+
ensure
|
44
|
+
mutex.unlock if mutex
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should lock and sleep with timeout but woken up in the middle of a sleep" do
|
49
|
+
begin
|
50
|
+
mutex = described_class.lock(*@lock_names)
|
51
|
+
mutex.owned?.should be true
|
52
|
+
fiber = Fiber.current
|
53
|
+
start = Time.now
|
54
|
+
::EM.add_timer(0.15) do
|
55
|
+
mutex.wakeup(fiber)
|
56
|
+
end
|
57
|
+
mutex.sleep(0.25).should be_within(0.001).of(0.15)
|
58
|
+
(Time.now - start).should be_within(0.001).of(0.15)
|
59
|
+
mutex.owned?.should be true
|
60
|
+
mutex.unlock!.should be_true
|
61
|
+
ensure
|
62
|
+
mutex.unlock if mutex
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should lock and sleep and raise MutexTimeout on wakeup" do
|
67
|
+
begin
|
68
|
+
mutex = described_class.lock(*@lock_names, block: 0)
|
69
|
+
mutex.owned?.should be true
|
70
|
+
fiber = Fiber.current
|
71
|
+
::EM::Synchrony.next_tick do
|
72
|
+
begin
|
73
|
+
mutex.owned?.should be false
|
74
|
+
mutex.lock.should be true
|
75
|
+
mutex.owned?.should be true
|
76
|
+
mutex.wakeup(fiber)
|
77
|
+
::EM::Synchrony.sleep(0.2)
|
78
|
+
mutex.unlock!.should be_true
|
79
|
+
rescue Exception => e
|
80
|
+
@exception = e
|
81
|
+
mutex.unlock
|
82
|
+
end
|
83
|
+
end
|
84
|
+
start = Time.now
|
85
|
+
expect {
|
86
|
+
mutex.sleep
|
87
|
+
}.to raise_error(Redis::EM::Mutex::MutexTimeout)
|
88
|
+
(Time.now - start).should be_within(0.002).of(0.003)
|
89
|
+
mutex.owned?.should be false
|
90
|
+
mutex.unlock!.should be false
|
91
|
+
mutex.block_timeout = nil
|
92
|
+
start = Time.now
|
93
|
+
mutex.lock.should be true
|
94
|
+
(Time.now - start).should be_within(0.01).of(0.2)
|
95
|
+
mutex.owned?.should be true
|
96
|
+
mutex.unlock!.should be_true
|
97
|
+
rescue Exception => e
|
98
|
+
::EM::Synchrony.sleep(0.3)
|
99
|
+
raise e
|
100
|
+
ensure
|
101
|
+
mutex.unlock
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should work with EM::Synchrony::Thread::ConditionVariable" do
|
106
|
+
mutex = described_class.new(*@lock_names)
|
107
|
+
resource = ::EM::Synchrony::Thread::ConditionVariable.new
|
108
|
+
signal = nil
|
109
|
+
fiber = Fiber.current
|
110
|
+
::EM::Synchrony.next_tick do
|
111
|
+
mutex.synchronize {
|
112
|
+
resource.wait(mutex)
|
113
|
+
fiber.resume Time.now
|
114
|
+
}
|
115
|
+
end
|
116
|
+
::EM::Synchrony.next_tick do
|
117
|
+
mutex.synchronize {
|
118
|
+
::EM::Synchrony.sleep(0.2)
|
119
|
+
resource.signal
|
120
|
+
signal = Time.now
|
121
|
+
}
|
122
|
+
end
|
123
|
+
start = Time.now
|
124
|
+
now = Fiber.yield
|
125
|
+
(now - signal).should be_within(0.001).of(0.001)
|
126
|
+
(now - start).should be_within(0.01).of(0.2)
|
127
|
+
mutex.synchronize do
|
128
|
+
signal = nil
|
129
|
+
end
|
130
|
+
signal.should be_nil
|
131
|
+
end
|
132
|
+
|
133
|
+
around(:each) do |testcase|
|
134
|
+
@after_em_stop = nil
|
135
|
+
@exception = nil
|
136
|
+
::EM.synchrony do
|
137
|
+
begin
|
138
|
+
testcase.call
|
139
|
+
raise @exception if @exception
|
140
|
+
described_class.stop_watcher
|
141
|
+
rescue => e
|
142
|
+
described_class.stop_watcher(true)
|
143
|
+
raise e
|
144
|
+
ensure
|
145
|
+
::EM.stop
|
146
|
+
end
|
147
|
+
end
|
148
|
+
@after_em_stop.call if @after_em_stop
|
149
|
+
end
|
150
|
+
|
151
|
+
before(:all) do
|
152
|
+
@redis_options = {:driver => :synchrony}
|
153
|
+
described_class.setup @redis_options.merge(size: 4)
|
154
|
+
@lock_names = 2.times.map {
|
155
|
+
SecureRandom.random_bytes
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
after(:all) do
|
160
|
+
# @lock_names
|
161
|
+
end
|
162
|
+
end
|
@@ -32,11 +32,11 @@ describe Redis::EM::Mutex do
|
|
32
32
|
it "should lock and allow locking on the same semaphore name with different namespace" do
|
33
33
|
begin
|
34
34
|
mutex = described_class.lock(*@lock_names)
|
35
|
-
mutex.locked?.should
|
36
|
-
mutex.owned?.should
|
35
|
+
mutex.locked?.should be true
|
36
|
+
mutex.owned?.should be true
|
37
37
|
ns_mutex = described_class.lock(*@lock_names, ns: :MutexLocalTEST)
|
38
|
-
ns_mutex.locked?.should
|
39
|
-
ns_mutex.owned?.should
|
38
|
+
ns_mutex.locked?.should be true
|
39
|
+
ns_mutex.owned?.should be true
|
40
40
|
expect {
|
41
41
|
mutex.lock
|
42
42
|
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
@@ -44,11 +44,11 @@ describe Redis::EM::Mutex do
|
|
44
44
|
ns_mutex.lock
|
45
45
|
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
46
46
|
mutex.unlock
|
47
|
-
mutex.locked?.should
|
48
|
-
mutex.owned?.should
|
47
|
+
mutex.locked?.should be false
|
48
|
+
mutex.owned?.should be false
|
49
49
|
ns_mutex.unlock
|
50
|
-
ns_mutex.locked?.should
|
51
|
-
ns_mutex.owned?.should
|
50
|
+
ns_mutex.locked?.should be false
|
51
|
+
ns_mutex.owned?.should be false
|
52
52
|
ensure
|
53
53
|
mutex.unlock if mutex
|
54
54
|
ns_mutex.unlock if ns_mutex
|
@@ -59,11 +59,11 @@ describe Redis::EM::Mutex do
|
|
59
59
|
begin
|
60
60
|
ns = described_class::NS.new(:MutexCustomTEST)
|
61
61
|
mutex1 = ns.lock(@lock_names.first)
|
62
|
-
mutex1.locked?.should
|
63
|
-
mutex1.owned?.should
|
62
|
+
mutex1.locked?.should be true
|
63
|
+
mutex1.owned?.should be true
|
64
64
|
mutex2 = ns.lock(@lock_names.last)
|
65
|
-
mutex2.locked?.should
|
66
|
-
mutex2.owned?.should
|
65
|
+
mutex2.locked?.should be true
|
66
|
+
mutex2.owned?.should be true
|
67
67
|
expect {
|
68
68
|
mutex1.lock
|
69
69
|
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
@@ -71,11 +71,11 @@ describe Redis::EM::Mutex do
|
|
71
71
|
mutex2.lock
|
72
72
|
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
73
73
|
mutex1.unlock
|
74
|
-
mutex1.locked?.should
|
75
|
-
mutex1.owned?.should
|
74
|
+
mutex1.locked?.should be false
|
75
|
+
mutex1.owned?.should be false
|
76
76
|
mutex2.unlock
|
77
|
-
mutex2.locked?.should
|
78
|
-
mutex2.owned?.should
|
77
|
+
mutex2.locked?.should be false
|
78
|
+
mutex2.owned?.should be false
|
79
79
|
ensure
|
80
80
|
mutex1.unlock if mutex1
|
81
81
|
mutex2.unlock if mutex2
|
@@ -0,0 +1,156 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
require 'securerandom'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/fiber_iterator'
|
5
|
+
require 'redis-em-mutex'
|
6
|
+
|
7
|
+
describe Redis::EM::Mutex do
|
8
|
+
|
9
|
+
it "should share a custom owner lock between fibers" do
|
10
|
+
begin
|
11
|
+
mutex = described_class.lock(*@lock_names, owner: 'my')
|
12
|
+
mutex.should be_an_instance_of described_class
|
13
|
+
mutex.names.should eq @lock_names
|
14
|
+
mutex.locked?.should be true
|
15
|
+
mutex.owned?.should be true
|
16
|
+
expect {
|
17
|
+
mutex.lock
|
18
|
+
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
19
|
+
fiber = Fiber.current
|
20
|
+
::EM::Synchrony.next_tick do
|
21
|
+
begin
|
22
|
+
mutex.try_lock.should be false
|
23
|
+
mutex.locked?.should be true
|
24
|
+
mutex.owned?.should be true
|
25
|
+
expect {
|
26
|
+
mutex.lock
|
27
|
+
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
28
|
+
mutex.refresh.should be true
|
29
|
+
rescue Exception => e
|
30
|
+
@exception = e
|
31
|
+
ensure
|
32
|
+
::EM.next_tick { fiber.resume }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
Fiber.yield
|
36
|
+
mutex.locked?.should be true
|
37
|
+
mutex.owned?.should be true
|
38
|
+
mutex.unlock.should be_an_instance_of described_class
|
39
|
+
::EM::Synchrony.next_tick do
|
40
|
+
begin
|
41
|
+
mutex.locked?.should be false
|
42
|
+
mutex.owned?.should be false
|
43
|
+
mutex.lock.should be true
|
44
|
+
mutex.locked?.should be true
|
45
|
+
mutex.owned?.should be true
|
46
|
+
expect {
|
47
|
+
mutex.lock
|
48
|
+
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
49
|
+
rescue Exception => e
|
50
|
+
@exception = e
|
51
|
+
ensure
|
52
|
+
::EM.next_tick { fiber.resume }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
Fiber.yield
|
56
|
+
mutex.locked?.should be true
|
57
|
+
mutex.owned?.should be true
|
58
|
+
expect {
|
59
|
+
mutex.lock
|
60
|
+
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
61
|
+
mutex.unlock.should be_an_instance_of described_class
|
62
|
+
mutex.locked?.should be false
|
63
|
+
mutex.owned?.should be false
|
64
|
+
mutex.try_lock.should be true
|
65
|
+
ensure
|
66
|
+
mutex.unlock if mutex
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should share custom owner locks concurrently between group of fibers" do
|
71
|
+
begin
|
72
|
+
mutex1 = described_class.new(*@lock_names, owner: 'my1', block: 0)
|
73
|
+
mutex2 = described_class.new(*@lock_names, owner: 'my2', block: 0)
|
74
|
+
[mutex1, mutex2].each do |mutex|
|
75
|
+
mutex.should be_an_instance_of described_class
|
76
|
+
mutex.names.should eq @lock_names
|
77
|
+
mutex.locked?.should be false
|
78
|
+
mutex.owned?.should be false
|
79
|
+
end
|
80
|
+
mutex1.lock.should be true
|
81
|
+
mutex2.lock.should be false
|
82
|
+
mutex1.locked?.should be true
|
83
|
+
mutex1.owned?.should be true
|
84
|
+
mutex2.locked?.should be true
|
85
|
+
mutex2.owned?.should be false
|
86
|
+
expect {
|
87
|
+
mutex1.lock
|
88
|
+
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
89
|
+
fiber = Fiber.current
|
90
|
+
::EM::Synchrony.next_tick do
|
91
|
+
begin
|
92
|
+
mutex1.locked?.should be true
|
93
|
+
mutex1.owned?.should be true
|
94
|
+
mutex2.locked?.should be true
|
95
|
+
mutex2.owned?.should be false
|
96
|
+
expect {
|
97
|
+
mutex1.lock
|
98
|
+
}.to raise_error(Redis::EM::Mutex::MutexError, /deadlock; recursive locking/)
|
99
|
+
mutex2.lock.should be false
|
100
|
+
mutex1.refresh.should be true
|
101
|
+
mutex2.refresh.should be false
|
102
|
+
mutex2.block_timeout = nil
|
103
|
+
::EM.next_tick { fiber.resume }
|
104
|
+
start = Time.now
|
105
|
+
mutex2.lock.should be true
|
106
|
+
(Time.now - start).should be_within(0.01).of(0.5)
|
107
|
+
rescue Exception => e
|
108
|
+
@exception = e
|
109
|
+
ensure
|
110
|
+
::EM.next_tick { fiber.resume }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
Fiber.yield
|
114
|
+
EM::Synchrony.sleep 0.5
|
115
|
+
mutex1.refresh.should be true
|
116
|
+
mutex2.refresh.should be false
|
117
|
+
mutex1.unlock.should be_an_instance_of described_class
|
118
|
+
Fiber.yield
|
119
|
+
mutex1.refresh.should be false
|
120
|
+
mutex2.refresh.should be true
|
121
|
+
ensure
|
122
|
+
mutex1.unlock if mutex1
|
123
|
+
mutex2.unlock if mutex2
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
around(:each) do |testcase|
|
128
|
+
@after_em_stop = nil
|
129
|
+
@exception = nil
|
130
|
+
::EM.synchrony do
|
131
|
+
begin
|
132
|
+
testcase.call
|
133
|
+
raise @exception if @exception
|
134
|
+
described_class.stop_watcher
|
135
|
+
rescue => e
|
136
|
+
described_class.stop_watcher(true)
|
137
|
+
raise e
|
138
|
+
ensure
|
139
|
+
::EM.stop
|
140
|
+
end
|
141
|
+
end
|
142
|
+
@after_em_stop.call if @after_em_stop
|
143
|
+
end
|
144
|
+
|
145
|
+
before(:all) do
|
146
|
+
@redis_options = {:driver => :synchrony}
|
147
|
+
described_class.setup @redis_options.merge(size: 11)
|
148
|
+
@lock_names = 10.times.map {
|
149
|
+
SecureRandom.random_bytes
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
after(:all) do
|
154
|
+
# @lock_names
|
155
|
+
end
|
156
|
+
end
|