quack_concurrency 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/quack_concurrency.rb +6 -13
- data/lib/quack_concurrency/condition_variable.rb +91 -85
- data/lib/quack_concurrency/condition_variable/waitable.rb +108 -0
- data/lib/quack_concurrency/error.rb +1 -1
- data/lib/quack_concurrency/future.rb +31 -30
- data/lib/quack_concurrency/future/canceled.rb +1 -0
- data/lib/quack_concurrency/future/complete.rb +1 -0
- data/lib/quack_concurrency/mutex.rb +140 -38
- data/lib/quack_concurrency/queue.rb +32 -28
- data/lib/quack_concurrency/reentrant_mutex.rb +64 -76
- data/lib/quack_concurrency/safe_condition_variable.rb +23 -0
- data/lib/quack_concurrency/safe_condition_variable/waitable.rb +21 -0
- data/lib/quack_concurrency/safe_sleeper.rb +80 -0
- data/lib/quack_concurrency/sleeper.rb +100 -0
- data/lib/quack_concurrency/waiter.rb +32 -23
- data/spec/condition_variable_spec.rb +216 -0
- data/spec/future_spec.rb +145 -79
- data/spec/mutex_spec.rb +441 -0
- data/spec/queue_spec.rb +217 -77
- data/spec/reentrant_mutex_spec.rb +394 -99
- data/spec/safe_condition_variable_spec.rb +115 -0
- data/spec/safe_sleeper_spec.rb +197 -0
- data/spec/sleeper.rb +197 -0
- data/spec/waiter_spec.rb +181 -0
- metadata +16 -14
- data/lib/quack_concurrency/queue/error.rb +0 -6
- data/lib/quack_concurrency/reentrant_mutex/error.rb +0 -6
- data/lib/quack_concurrency/semaphore.rb +0 -139
- data/lib/quack_concurrency/semaphore/error.rb +0 -6
- data/lib/quack_concurrency/uninterruptible_condition_variable.rb +0 -94
- data/lib/quack_concurrency/uninterruptible_sleeper.rb +0 -81
- data/lib/quack_concurrency/yielder.rb +0 -35
- data/spec/semaphore_spec.rb +0 -244
data/spec/queue_spec.rb
CHANGED
@@ -1,130 +1,270 @@
|
|
1
1
|
require 'quack_concurrency'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
describe "
|
6
|
-
|
7
|
-
context "
|
8
|
-
it "should
|
3
|
+
describe QuackConcurrency::Queue do
|
4
|
+
|
5
|
+
describe "::new" do
|
6
|
+
|
7
|
+
context "called with no arguments" do
|
8
|
+
it "should return a Queue" do
|
9
9
|
queue = QuackConcurrency::Queue.new
|
10
|
-
expect
|
11
|
-
expect{ queue.push(2) }.not_to raise_error
|
10
|
+
expect(queue).to be_a(QuackConcurrency::Queue)
|
12
11
|
end
|
13
12
|
end
|
14
|
-
|
13
|
+
|
15
14
|
end
|
16
|
-
|
17
|
-
describe "#
|
18
|
-
|
19
|
-
context "when
|
20
|
-
it "should
|
15
|
+
|
16
|
+
describe "#clear" do
|
17
|
+
|
18
|
+
context "when called with some items in the Queue" do
|
19
|
+
it "should remove all the items" do
|
21
20
|
queue = QuackConcurrency::Queue.new
|
22
|
-
|
21
|
+
queue.push(1)
|
22
|
+
queue.push(2)
|
23
|
+
queue.clear
|
24
|
+
expect(queue.empty?).to be true
|
23
25
|
end
|
24
26
|
end
|
25
|
-
|
27
|
+
|
26
28
|
end
|
27
|
-
|
28
|
-
describe "#close, #push" do
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
describe "#close" do
|
31
|
+
|
32
|
+
context "when called" do
|
33
|
+
it "should close the Queue" do
|
32
34
|
queue = QuackConcurrency::Queue.new
|
33
35
|
queue.close
|
34
|
-
expect
|
36
|
+
expect(queue.closed?).to be true
|
37
|
+
end
|
38
|
+
it "should resume all threads waiting on the Queue" do
|
39
|
+
queue = QuackConcurrency::Queue.new
|
40
|
+
thread1 = Thread.new { queue.pop }
|
41
|
+
thread2 = Thread.new { queue.pop }
|
42
|
+
sleep 1
|
43
|
+
queue.close
|
44
|
+
sleep 1
|
45
|
+
expect(thread1.alive?).to be false
|
46
|
+
expect(thread2.alive?).to be false
|
35
47
|
end
|
36
48
|
end
|
37
|
-
|
49
|
+
|
38
50
|
end
|
39
|
-
|
40
|
-
describe "#
|
41
|
-
|
42
|
-
context "when
|
43
|
-
it "should
|
51
|
+
|
52
|
+
describe "#closed?" do
|
53
|
+
|
54
|
+
context "when called on non closed Queue" do
|
55
|
+
it "should return false" do
|
56
|
+
queue = QuackConcurrency::Queue.new
|
57
|
+
expect(queue.closed?).to be false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when called on a closed Queue" do
|
62
|
+
it "should return true" do
|
44
63
|
queue = QuackConcurrency::Queue.new
|
45
64
|
queue.close
|
46
|
-
expect(queue.
|
65
|
+
expect(queue.closed?).to be true
|
47
66
|
end
|
48
67
|
end
|
49
|
-
|
68
|
+
|
50
69
|
end
|
51
|
-
|
52
|
-
describe "#
|
53
|
-
|
54
|
-
context "when
|
55
|
-
it "should
|
70
|
+
|
71
|
+
describe "#empty?" do
|
72
|
+
|
73
|
+
context "when called on a empty Queue" do
|
74
|
+
it "should return false" do
|
56
75
|
queue = QuackConcurrency::Queue.new
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
76
|
+
expect(queue.empty?).to be true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when called on a non empty Queue" do
|
81
|
+
it "should return false" do
|
82
|
+
queue = QuackConcurrency::Queue.new
|
83
|
+
queue.push(1)
|
84
|
+
expect(queue.empty?).to be false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#length" do
|
91
|
+
|
92
|
+
context "when called on a empty Queue" do
|
93
|
+
it "should return 1" do
|
94
|
+
queue = QuackConcurrency::Queue.new
|
95
|
+
expect(queue.length).to be 0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "when called on a Queue with one item" do
|
100
|
+
it "should return 1" do
|
101
|
+
queue = QuackConcurrency::Queue.new
|
102
|
+
queue.push(1)
|
103
|
+
expect(queue.length).to be 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when called on a Queue with two items" do
|
108
|
+
it "should return 2" do
|
109
|
+
queue = QuackConcurrency::Queue.new
|
110
|
+
queue.push(1)
|
111
|
+
queue.push(3)
|
112
|
+
expect(queue.length).to be 2
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#num_waiting" do
|
119
|
+
|
120
|
+
context "when called on a Queue with no thread waiting on it" do
|
121
|
+
it "should return 0" do
|
122
|
+
queue = QuackConcurrency::Queue.new
|
123
|
+
expect(queue.num_waiting).to be 0
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when called on a Queue with a thread waiting on it" do
|
128
|
+
it "should return 1" do
|
129
|
+
queue = QuackConcurrency::Queue.new
|
130
|
+
thread = Thread.new { queue.pop }
|
131
|
+
sleep 1
|
132
|
+
expect(queue.num_waiting).to be 1
|
133
|
+
queue.close
|
65
134
|
thread.join
|
66
|
-
expect(duration).to be > 0.5
|
67
135
|
end
|
68
136
|
end
|
69
|
-
|
137
|
+
|
138
|
+
context "when called on a Queue with two threads waiting on it" do
|
139
|
+
it "should return 2" do
|
140
|
+
queue = QuackConcurrency::Queue.new
|
141
|
+
thread1 = Thread.new { queue.pop }
|
142
|
+
thread2 = Thread.new { queue.pop }
|
143
|
+
sleep 1
|
144
|
+
expect(queue.num_waiting).to be 2
|
145
|
+
queue.close
|
146
|
+
thread1.join
|
147
|
+
thread2.join
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
70
151
|
end
|
71
|
-
|
72
|
-
describe "#pop
|
73
|
-
|
74
|
-
context "when
|
152
|
+
|
153
|
+
describe "#pop" do
|
154
|
+
|
155
|
+
context "when called on queue with one item" do
|
75
156
|
it "should reutrn item" do
|
157
|
+
queue = QuackConcurrency::Queue.new
|
158
|
+
queue.push(1)
|
159
|
+
expect(queue.pop).to eql 1
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when called before #close on empty queue" do
|
164
|
+
it "should wait for #close then reutrn nil" do
|
165
|
+
queue = QuackConcurrency::Queue.new
|
166
|
+
value = :not_set
|
167
|
+
thread = Thread.new { value = queue.pop }
|
168
|
+
sleep 1
|
169
|
+
expect(thread.alive?).to be true
|
170
|
+
queue.close
|
171
|
+
sleep 1
|
172
|
+
expect(value).to be nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "when called after #close on queue with one item" do
|
177
|
+
it "should reutrn item immediately" do
|
76
178
|
queue = QuackConcurrency::Queue.new
|
77
179
|
queue.push(1)
|
78
180
|
queue.close
|
79
181
|
expect(queue.pop).to eql 1
|
80
182
|
end
|
81
183
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
184
|
+
|
185
|
+
context "when called on empty Queue with non_block set to true" do
|
186
|
+
it "should raise Error" do
|
187
|
+
queue = QuackConcurrency::Queue.new
|
188
|
+
expect{ queue.pop(true) }.to raise_error(ThreadError)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "when called after #close on empty queue" do
|
193
|
+
it "should reutrn nil immediately" do
|
194
|
+
queue = QuackConcurrency::Queue.new
|
195
|
+
queue.close
|
196
|
+
expect(queue.pop).to eql nil
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context "when called Queue with some items" do
|
201
|
+
it "should reutrn oldest item in the Queue" do
|
202
|
+
queue = QuackConcurrency::Queue.new
|
203
|
+
queue.push(1)
|
204
|
+
queue.push(3)
|
205
|
+
queue.push(2)
|
206
|
+
expect(queue.pop).to be 1
|
207
|
+
expect(queue.pop).to be 3
|
208
|
+
expect(queue.pop).to be 2
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context "when called with other threads waiting on Queue" do
|
213
|
+
it "should reutrn items in the chronological order of calls to #pop" do
|
214
|
+
queue = QuackConcurrency::Queue.new
|
215
|
+
values = []
|
216
|
+
Thread.new { sleep 1; values << queue.pop }
|
217
|
+
Thread.new { sleep 2; values << queue.pop }
|
218
|
+
Thread.new { sleep 3; values << queue.pop }
|
219
|
+
sleep 4
|
220
|
+
queue.push(1)
|
221
|
+
queue.push(2)
|
222
|
+
queue.push(3)
|
223
|
+
sleep 1
|
224
|
+
expect(values).to eql [1, 2, 3]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context "when called before #push" do
|
88
229
|
it "should wait until #push is called" do
|
89
|
-
|
230
|
+
values = []
|
90
231
|
queue = QuackConcurrency::Queue.new
|
91
232
|
thread = Thread.new do
|
92
233
|
queue.pop
|
93
|
-
|
234
|
+
values << 1
|
94
235
|
sleep 1
|
95
236
|
queue.push
|
96
237
|
end
|
97
238
|
sleep 1
|
98
239
|
queue.push
|
99
240
|
queue.pop
|
100
|
-
|
241
|
+
values << 2
|
101
242
|
thread.join
|
102
|
-
expect(
|
243
|
+
expect(values).to eql [1, 2]
|
103
244
|
end
|
104
245
|
end
|
105
|
-
|
246
|
+
|
106
247
|
end
|
107
|
-
|
108
|
-
describe "#
|
109
|
-
|
110
|
-
context "when
|
111
|
-
it "should
|
248
|
+
|
249
|
+
describe "#push" do
|
250
|
+
|
251
|
+
context "when called on non closed Queue" do
|
252
|
+
it "should add items to Queue" do
|
112
253
|
queue = QuackConcurrency::Queue.new
|
113
254
|
queue.push(1)
|
114
|
-
queue.
|
115
|
-
|
116
|
-
sleep 1
|
117
|
-
queue.push(2)
|
118
|
-
end
|
119
|
-
start_time = Time.now
|
120
|
-
expect(queue.pop).to eql 2
|
121
|
-
end_time = Time.now
|
122
|
-
duration = end_time - start_time
|
123
|
-
thread.join
|
124
|
-
expect(duration).to be > 0.5
|
255
|
+
queue.push(3)
|
256
|
+
expect(queue.length).to be 2
|
125
257
|
end
|
126
258
|
end
|
127
|
-
|
259
|
+
|
260
|
+
context "when called on closed Queue" do
|
261
|
+
it "should raise ClosedQueueError" do
|
262
|
+
queue = QuackConcurrency::Queue.new
|
263
|
+
queue.close
|
264
|
+
expect{ queue.push(1) }.to raise_error(ClosedQueueError)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
128
268
|
end
|
129
|
-
|
269
|
+
|
130
270
|
end
|
@@ -1,167 +1,462 @@
|
|
1
1
|
require 'quack_concurrency'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
describe QuackConcurrency::ReentrantMutex do
|
4
|
+
|
5
|
+
it "should inherit Mutex" do
|
6
|
+
expect(described_class).to be < QuackConcurrency::Mutex
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "::new" do
|
10
|
+
|
11
|
+
context "when called with no arguments" do
|
12
|
+
it "should return a ReentrantMutex" do
|
13
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
14
|
+
expect(mutex).to be_a(QuackConcurrency::ReentrantMutex)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
5
20
|
describe "#lock" do
|
6
|
-
|
7
|
-
context "when called
|
21
|
+
|
22
|
+
context "when called when mutex is not locked" do
|
8
23
|
it "should not raise error" do
|
9
24
|
mutex = QuackConcurrency::ReentrantMutex.new
|
10
25
|
expect { mutex.lock }.not_to raise_error
|
11
26
|
end
|
12
27
|
end
|
13
|
-
|
14
|
-
context "when called
|
28
|
+
|
29
|
+
context "when called when mutex is locked by this thread" do
|
15
30
|
it "should not raise error" do
|
16
31
|
mutex = QuackConcurrency::ReentrantMutex.new
|
17
32
|
mutex.lock
|
18
33
|
expect { mutex.lock }.not_to raise_error
|
19
34
|
end
|
20
35
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
describe "#lock, #unlock" do
|
25
|
-
|
26
|
-
context "when #lock called on non owning thread" do
|
27
|
-
it "should wait for #unlock" do
|
36
|
+
|
37
|
+
context "when called when mutex is locked by another thread" do
|
38
|
+
it "should block until lock is available" do
|
28
39
|
mutex = QuackConcurrency::ReentrantMutex.new
|
29
|
-
thread = Thread.new
|
40
|
+
thread = Thread.new { sleep 1; mutex.lock }
|
41
|
+
mutex.lock
|
42
|
+
sleep 2
|
43
|
+
expect(thread.alive?).to be true
|
44
|
+
mutex.unlock
|
45
|
+
sleep 1
|
46
|
+
expect(thread.alive?).to be false
|
47
|
+
end
|
48
|
+
context "and another thread waiting on the mutex" do
|
49
|
+
it "should wake the threads in order of calling #lock" do
|
50
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
30
51
|
mutex.lock
|
31
|
-
|
52
|
+
values = []
|
53
|
+
thread1 = Thread.new { mutex.lock { values << 1 } }
|
54
|
+
thread2 = Thread.new { sleep 1; mutex.lock { values << 2 } }
|
55
|
+
thread3 = Thread.new { sleep 2; mutex.lock { values << 3 } }
|
56
|
+
sleep 3
|
32
57
|
mutex.unlock
|
58
|
+
sleep 1
|
59
|
+
expect(values).to eq [1, 2, 3]
|
33
60
|
end
|
34
|
-
sleep 1
|
35
|
-
start_time = Time.now
|
36
|
-
mutex.lock
|
37
|
-
end_time = Time.now
|
38
|
-
duration = end_time - start_time
|
39
|
-
thread.join
|
40
|
-
expect(duration).to be > 0.5
|
41
61
|
end
|
42
62
|
end
|
43
|
-
|
44
|
-
context "when
|
45
|
-
it "should
|
63
|
+
|
64
|
+
context "when called with a block" do
|
65
|
+
it "should run block and return it's value" do
|
46
66
|
mutex = QuackConcurrency::ReentrantMutex.new
|
47
|
-
mutex.lock
|
48
|
-
|
67
|
+
expect(mutex.lock { :a }).to be :a
|
68
|
+
end
|
69
|
+
it "should pass up any error raised in the block" do
|
70
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
71
|
+
e = Class.new(StandardError)
|
72
|
+
expect{ mutex.lock { raise e } }.to raise_error(e)
|
73
|
+
end
|
74
|
+
it "should hold lock while block is executed" do
|
75
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
76
|
+
hold_thread = Thread.new { mutex.lock { sleep 3 } }
|
77
|
+
lock_thread = Thread.new { sleep 1; mutex.lock }
|
78
|
+
sleep 2
|
79
|
+
expect(lock_thread.alive?).to be true
|
80
|
+
hold_thread.join
|
81
|
+
lock_thread.join
|
82
|
+
end
|
83
|
+
it "should release the lock after the block has returned" do
|
84
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
85
|
+
Thread.new { mutex.lock { sleep 2 } }
|
86
|
+
lock_thread = Thread.new { sleep 1; mutex.lock }
|
87
|
+
sleep 3
|
88
|
+
expect(lock_thread.alive?).to be false
|
89
|
+
end
|
90
|
+
context "that raises an error" do
|
91
|
+
it "should release the lock after the block has returned" do
|
92
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
93
|
+
Thread.new do
|
94
|
+
mutex.lock { sleep 2; raise } rescue nil
|
95
|
+
end
|
96
|
+
lock_thread = Thread.new { sleep 1; mutex.lock }
|
97
|
+
sleep 3
|
98
|
+
expect(lock_thread.alive?).to be false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
context "that locks the mutex" do
|
102
|
+
it "should raise ThreadError" do
|
103
|
+
mutex = QuackConcurrency::Mutex.new
|
104
|
+
expect{ mutex.lock { mutex.lock } }.to raise_error(ThreadError)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
context "that unlocks the mutex" do
|
108
|
+
it "should raise ThreadError" do
|
109
|
+
mutex = QuackConcurrency::Mutex.new
|
110
|
+
expect{ mutex.lock { mutex.unlock } }.to raise_error(ThreadError)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
context "that fully unlocks the mutex" do
|
114
|
+
it "should raise ThreadError" do
|
115
|
+
mutex = QuackConcurrency::Mutex.new
|
116
|
+
mutex.lock
|
117
|
+
expect{ mutex.lock { mutex.unlock; mutex.unlock } }.to raise_error(ThreadError)
|
118
|
+
end
|
49
119
|
end
|
50
120
|
end
|
51
|
-
|
52
|
-
context "when
|
53
|
-
it "should
|
121
|
+
|
122
|
+
context "when called twice when mutex is not locked" do
|
123
|
+
it "should need to unlock twice before mutex is released" do
|
54
124
|
mutex = QuackConcurrency::ReentrantMutex.new
|
125
|
+
thread = Thread.new { sleep 1; mutex.lock }
|
55
126
|
mutex.lock
|
56
127
|
mutex.lock
|
57
|
-
|
128
|
+
sleep 2
|
129
|
+
expect(thread.alive?).to be true
|
130
|
+
mutex.unlock
|
131
|
+
sleep 1
|
132
|
+
expect(thread.alive?).to be true
|
133
|
+
mutex.unlock
|
134
|
+
sleep 1
|
135
|
+
expect(thread.alive?).to be false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#sleep" do
|
142
|
+
|
143
|
+
context "when called while not locking the mutex" do
|
144
|
+
it "should raise ThreadError" do
|
145
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
146
|
+
expect{ mutex.sleep }.to raise_error(ThreadError)
|
58
147
|
end
|
59
148
|
end
|
60
|
-
|
61
|
-
context "when
|
62
|
-
it "should
|
149
|
+
|
150
|
+
context "when called" do
|
151
|
+
it "should relock the mutex after thread is woken" do
|
63
152
|
mutex = QuackConcurrency::ReentrantMutex.new
|
64
|
-
mutex.lock
|
65
|
-
|
66
|
-
|
153
|
+
thread = Thread.new { mutex.lock { mutex.sleep; sleep 2 } }
|
154
|
+
sleep 1
|
155
|
+
thread.run
|
156
|
+
sleep 1
|
157
|
+
expect(mutex.locked?).to be true
|
158
|
+
thread.join
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "when called with no timeout" do
|
163
|
+
it "should return only after Thread#run is called" do
|
164
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
165
|
+
thread = Thread.new { mutex.lock { mutex.sleep } }
|
166
|
+
sleep 1
|
167
|
+
expect(thread.alive?).to be true
|
168
|
+
thread.run
|
169
|
+
sleep 1
|
170
|
+
expect(thread.alive?).to be false
|
67
171
|
end
|
68
172
|
end
|
69
|
-
|
173
|
+
|
174
|
+
context "when called with a timeout" do
|
175
|
+
it "should return timeout reached" do
|
176
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
177
|
+
thread = Thread.new { mutex.lock { mutex.sleep(1) } }
|
178
|
+
sleep 2
|
179
|
+
expect(thread.alive?).to be false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "when called while holding two locks" do
|
184
|
+
it "should acquire two locks after thread is woken" do
|
185
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
186
|
+
thread = Thread.new do
|
187
|
+
mutex.lock
|
188
|
+
mutex.lock
|
189
|
+
mutex.sleep(2)
|
190
|
+
sleep 2
|
191
|
+
mutex.unlock
|
192
|
+
sleep 2
|
193
|
+
mutex.unlock
|
194
|
+
sleep 2
|
195
|
+
end
|
196
|
+
sleep 1
|
197
|
+
expect(mutex.locked?).to be false
|
198
|
+
sleep 2
|
199
|
+
expect(mutex.locked?).to be true
|
200
|
+
sleep 2
|
201
|
+
expect(mutex.locked?).to be true
|
202
|
+
sleep 2
|
203
|
+
expect(mutex.locked?).to be false
|
204
|
+
thread.join
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
70
208
|
end
|
71
|
-
|
72
|
-
describe "#
|
73
|
-
|
74
|
-
context "when
|
209
|
+
|
210
|
+
describe "#try_lock" do
|
211
|
+
|
212
|
+
context "when called when mutex is not locked by any thread" do
|
75
213
|
it "should reutrn true" do
|
76
214
|
mutex = QuackConcurrency::ReentrantMutex.new
|
77
215
|
expect(mutex.try_lock).to eql true
|
78
216
|
end
|
79
217
|
end
|
80
|
-
|
81
|
-
context "when
|
218
|
+
|
219
|
+
context "when called when mutex is locked by another thread" do
|
82
220
|
it "should reutrn false" do
|
83
221
|
mutex = QuackConcurrency::ReentrantMutex.new
|
84
|
-
thread = Thread.new { mutex.lock }
|
222
|
+
thread = Thread.new { mutex.lock; sleep 2 }
|
85
223
|
sleep 1
|
86
224
|
expect(mutex.try_lock).to eql false
|
87
225
|
end
|
88
226
|
end
|
89
|
-
|
227
|
+
|
228
|
+
context "when called when mutex is locked by this thread" do
|
229
|
+
it "should reutrn true" do
|
230
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
231
|
+
mutex.lock
|
232
|
+
expect(mutex.try_lock).to eql true
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
90
236
|
end
|
91
|
-
|
92
|
-
describe "#
|
93
|
-
|
94
|
-
context "when
|
95
|
-
it "should
|
237
|
+
|
238
|
+
describe "#unlock" do
|
239
|
+
|
240
|
+
context "when called when this thread does not hold a lock" do
|
241
|
+
it "should raise ThreadError" do
|
96
242
|
mutex = QuackConcurrency::ReentrantMutex.new
|
97
|
-
|
98
|
-
|
243
|
+
expect { mutex.unlock }.to raise_error(ThreadError)
|
244
|
+
end
|
245
|
+
context "after locking and unlocking" do
|
246
|
+
it "should raise ThreadError" do
|
247
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
248
|
+
mutex.lock
|
249
|
+
mutex.lock
|
250
|
+
mutex.unlock
|
251
|
+
mutex.unlock
|
252
|
+
expect { mutex.unlock }.to raise_error(ThreadError)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context "when called when this thread holds one lock" do
|
258
|
+
context "and with a thread waiting on the mutex" do
|
259
|
+
it "should wake the thread" do
|
260
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
261
|
+
mutex.lock
|
262
|
+
thread = Thread.new { mutex.lock }
|
263
|
+
sleep 1
|
264
|
+
mutex.unlock
|
265
|
+
sleep 1
|
266
|
+
expect(thread.alive?).to eql false
|
267
|
+
thread.join
|
268
|
+
end
|
269
|
+
end
|
270
|
+
context "and with no thread waiting on the mutex" do
|
271
|
+
it "should let another thread lock the mutex in the future" do
|
272
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
273
|
+
mutex.lock
|
274
|
+
thread = Thread.new { sleep 1; mutex.lock }
|
275
|
+
mutex.unlock
|
99
276
|
sleep 2
|
277
|
+
expect(thread.alive?).to eql false
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
context "when called when this thread holds two locks" do
|
283
|
+
context "and with a thread waiting on the mutex" do
|
284
|
+
it "should not wake the thread until a second unlock" do
|
285
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
286
|
+
mutex.lock
|
287
|
+
mutex.lock
|
288
|
+
thread = Thread.new { mutex.lock }
|
289
|
+
sleep 1
|
290
|
+
mutex.unlock
|
291
|
+
sleep 1
|
292
|
+
expect(thread.alive?).to eql true
|
100
293
|
mutex.unlock
|
294
|
+
sleep 1
|
295
|
+
expect(thread.alive?).to eql false
|
296
|
+
thread.join
|
101
297
|
end
|
102
|
-
|
103
|
-
|
298
|
+
context "and with no thread waiting on the mutex" do
|
299
|
+
it "should not let another thread lock the mutex in the future" do
|
300
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
301
|
+
mutex.lock
|
302
|
+
mutex.lock
|
303
|
+
thread = Thread.new { sleep 1; mutex.lock }
|
304
|
+
mutex.unlock
|
305
|
+
sleep 2
|
306
|
+
expect(thread.alive?).to eql true
|
307
|
+
mutex.unlock
|
308
|
+
thread.join
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context "when called with a block" do
|
315
|
+
it "should run block and return it's value" do
|
316
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
317
|
+
mutex.lock
|
318
|
+
expect(mutex.unlock { :a }).to be :a
|
319
|
+
end
|
320
|
+
it "should pass up any error raised in the block" do
|
321
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
322
|
+
e = Class.new(StandardError)
|
104
323
|
mutex.lock
|
105
|
-
|
106
|
-
|
324
|
+
expect{ mutex.unlock { raise e } }.to raise_error(e)
|
325
|
+
end
|
326
|
+
it "should release a lock while block is executed" do
|
327
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
328
|
+
thread = Thread.new do
|
329
|
+
mutex.lock { mutex.unlock { sleep 2 } }
|
330
|
+
end
|
331
|
+
sleep 1
|
332
|
+
expect(mutex.locked?).to be false
|
107
333
|
thread.join
|
108
|
-
expect(duration).to be > 0.5
|
109
334
|
end
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
describe "#synchronize" do
|
115
|
-
|
116
|
-
context "when #synchronize called" do
|
117
|
-
it "should return last value from block" do
|
335
|
+
it "should reacquire the lock after the block has returned" do
|
118
336
|
mutex = QuackConcurrency::ReentrantMutex.new
|
119
|
-
|
120
|
-
|
337
|
+
thread = Thread.new do
|
338
|
+
mutex.lock { mutex.unlock {}; sleep 2 }
|
339
|
+
end
|
340
|
+
sleep 1
|
341
|
+
expect(mutex.locked?).to be true
|
342
|
+
thread.join
|
343
|
+
end
|
344
|
+
context "that raises an error when no other thread is locking the mutex" do
|
345
|
+
it "should reacquire the lock after the block has returned" do
|
346
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
347
|
+
thread = Thread.new do
|
348
|
+
mutex.lock do
|
349
|
+
mutex.unlock { raise } rescue nil
|
350
|
+
sleep 2
|
351
|
+
end
|
352
|
+
end
|
353
|
+
sleep 1
|
354
|
+
expect(mutex.locked?).to be true
|
355
|
+
thread.join
|
356
|
+
end
|
357
|
+
end
|
358
|
+
context "that raises an error when another thread is locking the mutex" do
|
359
|
+
it "should raise ThreadError after the block has returned" do
|
360
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
361
|
+
thread = Thread.new { sleep 1; mutex.lock; sleep 2 }
|
362
|
+
mutex.lock
|
363
|
+
expect{ mutex.unlock { sleep 2; raise } }.to raise_error(ThreadError)
|
364
|
+
thread.join
|
121
365
|
end
|
122
|
-
expect(value).to eql 1
|
123
366
|
end
|
124
367
|
end
|
125
|
-
|
368
|
+
|
126
369
|
end
|
127
|
-
|
128
|
-
describe "#
|
129
|
-
|
130
|
-
context "when
|
131
|
-
it "should
|
370
|
+
|
371
|
+
describe "#unlock!" do
|
372
|
+
|
373
|
+
context "when called without a block" do
|
374
|
+
it "should raise ArgumentError" do
|
132
375
|
mutex = QuackConcurrency::ReentrantMutex.new
|
133
|
-
|
134
|
-
|
135
|
-
#binding.pry
|
136
|
-
mutex.synchronize do
|
137
|
-
mutex.sleep(1)
|
138
|
-
end
|
139
|
-
end_time = Time.now
|
140
|
-
duration = end_time - start_time
|
141
|
-
expect(duration).to be > 0.5
|
376
|
+
mutex.lock
|
377
|
+
expect{ mutex.unlock! }.to raise_error(ArgumentError)
|
142
378
|
end
|
143
379
|
end
|
144
|
-
|
145
|
-
context "when
|
146
|
-
it "should
|
380
|
+
|
381
|
+
context "when called with a block" do
|
382
|
+
it "should run block and return it's value" do
|
383
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
384
|
+
mutex.lock
|
385
|
+
expect(mutex.unlock! { :a }).to be :a
|
386
|
+
end
|
387
|
+
it "should pass up any error raised in the block" do
|
388
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
389
|
+
e = Class.new(StandardError)
|
390
|
+
mutex.lock
|
391
|
+
expect{ mutex.unlock! { raise e } }.to raise_error(e)
|
392
|
+
end
|
393
|
+
it "should release a lock while block is executed" do
|
147
394
|
mutex = QuackConcurrency::ReentrantMutex.new
|
148
|
-
start_time = nil
|
149
|
-
end_time = nil
|
150
395
|
thread = Thread.new do
|
151
|
-
|
152
|
-
mutex.synchronize do
|
153
|
-
mutex.sleep
|
154
|
-
end
|
155
|
-
end_time = Time.now
|
396
|
+
mutex.lock { mutex.unlock! { sleep 2 } }
|
156
397
|
end
|
157
398
|
sleep 1
|
158
|
-
|
399
|
+
expect(mutex.locked?).to be false
|
400
|
+
thread.join
|
401
|
+
end
|
402
|
+
it "should reacquire the lock after the block has returned" do
|
403
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
404
|
+
thread = Thread.new do
|
405
|
+
mutex.lock { mutex.unlock! {}; sleep 2 }
|
406
|
+
end
|
407
|
+
sleep 1
|
408
|
+
expect(mutex.locked?).to be true
|
159
409
|
thread.join
|
160
|
-
|
161
|
-
|
410
|
+
end
|
411
|
+
context "while holding two locks" do
|
412
|
+
it "should acquire two locks after block reutrn" do
|
413
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
414
|
+
thread = Thread.new do
|
415
|
+
mutex.lock
|
416
|
+
mutex.lock
|
417
|
+
mutex.unlock! { sleep 2 }
|
418
|
+
sleep 2
|
419
|
+
mutex.unlock
|
420
|
+
sleep 2
|
421
|
+
mutex.unlock
|
422
|
+
sleep 2
|
423
|
+
end
|
424
|
+
sleep 1
|
425
|
+
expect(mutex.locked?).to be false
|
426
|
+
sleep 2
|
427
|
+
expect(mutex.locked?).to be true
|
428
|
+
sleep 2
|
429
|
+
expect(mutex.locked?).to be true
|
430
|
+
sleep 2
|
431
|
+
expect(mutex.locked?).to be false
|
432
|
+
thread.join
|
433
|
+
end
|
434
|
+
end
|
435
|
+
context "that raises an error when no other thread is locking the mutex" do
|
436
|
+
it "should reacquire the lock after the block has returned" do
|
437
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
438
|
+
thread = Thread.new do
|
439
|
+
mutex.lock do
|
440
|
+
mutex.unlock! { raise } rescue nil
|
441
|
+
sleep 2
|
442
|
+
end
|
443
|
+
end
|
444
|
+
sleep 1
|
445
|
+
expect(mutex.locked?).to be true
|
446
|
+
thread.join
|
447
|
+
end
|
448
|
+
end
|
449
|
+
context "that raises an error when another thread is locking the mutex" do
|
450
|
+
it "should raise ThreadError after the block has returned" do
|
451
|
+
mutex = QuackConcurrency::ReentrantMutex.new
|
452
|
+
thread = Thread.new { sleep 1; mutex.lock; sleep 2 }
|
453
|
+
mutex.lock
|
454
|
+
expect{ mutex.unlock! { sleep 2; raise } }.to raise_error(ThreadError)
|
455
|
+
thread.join
|
456
|
+
end
|
162
457
|
end
|
163
458
|
end
|
164
|
-
|
459
|
+
|
165
460
|
end
|
166
|
-
|
461
|
+
|
167
462
|
end
|