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.
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