quack_concurrency 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/quack_concurrency.rb +6 -13
  4. data/lib/quack_concurrency/condition_variable.rb +91 -85
  5. data/lib/quack_concurrency/condition_variable/waitable.rb +108 -0
  6. data/lib/quack_concurrency/error.rb +1 -1
  7. data/lib/quack_concurrency/future.rb +31 -30
  8. data/lib/quack_concurrency/future/canceled.rb +1 -0
  9. data/lib/quack_concurrency/future/complete.rb +1 -0
  10. data/lib/quack_concurrency/mutex.rb +140 -38
  11. data/lib/quack_concurrency/queue.rb +32 -28
  12. data/lib/quack_concurrency/reentrant_mutex.rb +64 -76
  13. data/lib/quack_concurrency/safe_condition_variable.rb +23 -0
  14. data/lib/quack_concurrency/safe_condition_variable/waitable.rb +21 -0
  15. data/lib/quack_concurrency/safe_sleeper.rb +80 -0
  16. data/lib/quack_concurrency/sleeper.rb +100 -0
  17. data/lib/quack_concurrency/waiter.rb +32 -23
  18. data/spec/condition_variable_spec.rb +216 -0
  19. data/spec/future_spec.rb +145 -79
  20. data/spec/mutex_spec.rb +441 -0
  21. data/spec/queue_spec.rb +217 -77
  22. data/spec/reentrant_mutex_spec.rb +394 -99
  23. data/spec/safe_condition_variable_spec.rb +115 -0
  24. data/spec/safe_sleeper_spec.rb +197 -0
  25. data/spec/sleeper.rb +197 -0
  26. data/spec/waiter_spec.rb +181 -0
  27. metadata +16 -14
  28. data/lib/quack_concurrency/queue/error.rb +0 -6
  29. data/lib/quack_concurrency/reentrant_mutex/error.rb +0 -6
  30. data/lib/quack_concurrency/semaphore.rb +0 -139
  31. data/lib/quack_concurrency/semaphore/error.rb +0 -6
  32. data/lib/quack_concurrency/uninterruptible_condition_variable.rb +0 -94
  33. data/lib/quack_concurrency/uninterruptible_sleeper.rb +0 -81
  34. data/lib/quack_concurrency/yielder.rb +0 -35
  35. data/spec/semaphore_spec.rb +0 -244
@@ -1,130 +1,270 @@
1
1
  require 'quack_concurrency'
2
2
 
3
- RSpec.describe QuackConcurrency::Queue do
4
-
5
- describe "#push" do
6
-
7
- context "when called many times when queue is not closed" do
8
- it "should not raise error" do
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{ queue.push(1) }.not_to raise_error
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 "#pop" do
18
-
19
- context "when #pop is called with non_block set to true" do
20
- it "should raise Error" do
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
- expect{ queue.pop(true) }.to raise_error(ThreadError)
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
- context "when called when queue is closed" do
31
- it "should raise ClosedQueueError" do
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{ queue.push(1) }.to raise_error(ClosedQueueError)
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 "#pop, #close" do
41
-
42
- context "when #pop is called after #close on empty queue" do
43
- it "should reutrn nil" do
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.pop).to eql nil
65
+ expect(queue.closed?).to be true
47
66
  end
48
67
  end
49
-
68
+
50
69
  end
51
-
52
- describe "#pop, #close" do
53
-
54
- context "when #pop is called before #close on empty queue" do
55
- it "should wait for #close then reutrn nil" do
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
- thread = Thread.new do
58
- sleep 1
59
- queue.close
60
- end
61
- start_time = Time.now
62
- expect(queue.pop).to eql nil
63
- end_time = Time.now
64
- duration = end_time - start_time
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, #push, #close" do
73
-
74
- context "when #pop is called after #close on queue with one item" do
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
- end
84
-
85
- describe "#pop, #push" do
86
-
87
- context "when #pop is called before #push" do
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
- $test = []
230
+ values = []
90
231
  queue = QuackConcurrency::Queue.new
91
232
  thread = Thread.new do
92
233
  queue.pop
93
- $test << 1
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
- $test << 2
241
+ values << 2
101
242
  thread.join
102
- expect($test).to eql [1, 2]
243
+ expect(values).to eql [1, 2]
103
244
  end
104
245
  end
105
-
246
+
106
247
  end
107
-
108
- describe "#pop, #push, #clear" do
109
-
110
- context "when #pop is called after #push but before #clear" do
111
- it "should wait until #push is called again" do
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.clear
115
- thread = Thread.new do
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
- RSpec.describe QuackConcurrency::ReentrantMutex do
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 for first time" do
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 a second time" do
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
- end
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 do
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
- sleep 2
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 #unlock called after one #lock" do
45
- it "should not raise error" do
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
- expect { mutex.unlock }.not_to raise_error
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 #unlock called after two #locks" do
53
- it "should not raise error" do
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
- expect { mutex.unlock }.not_to raise_error
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 #unlock called twice after only one #lock" do
62
- it "should raise error" do
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
- mutex.unlock
66
- expect { mutex.unlock }.to raise_error(QuackConcurrency::ReentrantMutex::Error)
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 "#lock, #try_lock" do
73
-
74
- context "when #try_lock called" do
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 #try_lock called after #lock called from other Thread" do
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 "#lock, #try_lock, #unlock" do
93
-
94
- context "when #lock called after #try_lock called from other Thread" do
95
- it "should wait for #unlock" do
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
- thread = Thread.new do
98
- mutex.try_lock
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
- sleep 1
103
- start_time = Time.now
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
- end_time = Time.now
106
- duration = end_time - start_time
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
- end
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
- value = mutex.synchronize do
120
- 1
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 "#sleep" do
129
-
130
- context "when #sleep called with time argument" do
131
- it "should wait for that time" do
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
- start_time = Time.now
134
- #require 'pry'
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 #sleep called with no time argument" do
146
- it "should wait until Thread is resumed" do
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
- start_time = Time.now
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
- thread.run
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
- duration = end_time - start_time
161
- expect(duration).to be > 0.5
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