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
@@ -1,81 +0,0 @@
|
|
1
|
-
module QuackConcurrency
|
2
|
-
|
3
|
-
# An {UninterruptibleSleeper} can be used to safely sleep a `Thread`.
|
4
|
-
# Unlike simply calling `Thread#sleep`, {#stop_thread} will ensure that only
|
5
|
-
# calling {#run_thread} on this {UninterruptibleSleeper} will wake the `Thread`.
|
6
|
-
# Any call to `Thread#run` directly, will be ignored.
|
7
|
-
# `Thread`s can still be resumed if `Thread#raise` is called.
|
8
|
-
# A `ThreadError` will be raised if a the last running `Thread` is stopped.
|
9
|
-
class UninterruptibleSleeper
|
10
|
-
|
11
|
-
def self.for_current
|
12
|
-
new(Thread.current)
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(thread)
|
16
|
-
raise ArgumentError, "'thread' must be a Thread" unless thread.is_a?(Thread)
|
17
|
-
@thread = thread
|
18
|
-
@state = :running
|
19
|
-
@mutex = ::Mutex.new
|
20
|
-
@stop_called = false
|
21
|
-
@run_called = false
|
22
|
-
end
|
23
|
-
|
24
|
-
def run_thread
|
25
|
-
@mutex.synchronize do
|
26
|
-
raise '#run_thread has already been called once' if @run_called
|
27
|
-
@run_called = true
|
28
|
-
return if @state == :running
|
29
|
-
Thread.pass until @state = :running || @thread.status == 'sleep'
|
30
|
-
@state = :running
|
31
|
-
@thread.run
|
32
|
-
end
|
33
|
-
nil
|
34
|
-
end
|
35
|
-
|
36
|
-
def sleep_thread(duration)
|
37
|
-
start_time = Time.now
|
38
|
-
stop_thread(timeout: duration)
|
39
|
-
time_elapsed = Time.now - start_time
|
40
|
-
end
|
41
|
-
|
42
|
-
def stop_thread(timeout: nil)
|
43
|
-
raise 'can only stop current Thread' unless Thread.current == @thread
|
44
|
-
raise "'timeout' argument must be nil or a Numeric" if timeout != nil && !timeout.is_a?(Numeric)
|
45
|
-
raise '#stop_thread has already been called once' if @stop_called
|
46
|
-
@stop_called = true
|
47
|
-
target_end_time = Time.now + timeout if timeout
|
48
|
-
@mutex.synchronize do
|
49
|
-
return if @run_called
|
50
|
-
@state = :sleeping
|
51
|
-
@mutex.unlock
|
52
|
-
loop do
|
53
|
-
if timeout
|
54
|
-
time_left = target_end_time - Time.now
|
55
|
-
Kernel.sleep(time_left) if time_left > 0
|
56
|
-
else
|
57
|
-
Thread.stop # may raise ThreadError if this is last running Thread
|
58
|
-
end
|
59
|
-
break if @state == :running || Time.now >= target_time
|
60
|
-
end
|
61
|
-
ensure
|
62
|
-
@state = :running
|
63
|
-
|
64
|
-
# we relock the mutex to ensure #run_thread has finshed before #stop_thread
|
65
|
-
# if Thread#run is called by another part of the code at the same time as
|
66
|
-
# #run_thread is being called, we dont want the call to #run_thread
|
67
|
-
# to call Thread#run on a Thread has already resumed and stopped again
|
68
|
-
@mutex.lock
|
69
|
-
end
|
70
|
-
nil
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
# @api private
|
76
|
-
def current?
|
77
|
-
Thread.current == @thread
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# not ready yet
|
2
|
-
|
3
|
-
module Threadesque
|
4
|
-
class SafeYielder
|
5
|
-
|
6
|
-
def self.for_current
|
7
|
-
new(Thread.current)
|
8
|
-
end
|
9
|
-
|
10
|
-
def initialize(thread)
|
11
|
-
raise 'not ready yet'
|
12
|
-
@thread = thread
|
13
|
-
@state = :running
|
14
|
-
end
|
15
|
-
|
16
|
-
def resume
|
17
|
-
raise 'Thread is not sleeping' unless @state == :sleeping
|
18
|
-
:wait until @thread.status == 'sleep'
|
19
|
-
@state = :running
|
20
|
-
@thread.run
|
21
|
-
nil
|
22
|
-
end
|
23
|
-
|
24
|
-
def yield
|
25
|
-
raise 'can only stop current Thread' unless Thread.current == @thread
|
26
|
-
@state = :sleeping
|
27
|
-
loop do
|
28
|
-
Thread.stop
|
29
|
-
redo if @state == :sleeping
|
30
|
-
end
|
31
|
-
nil
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
end
|
data/spec/semaphore_spec.rb
DELETED
@@ -1,244 +0,0 @@
|
|
1
|
-
#require 'quack_concurrency'
|
2
|
-
|
3
|
-
#RSpec.describe QuackConcurrency::Semaphore do
|
4
|
-
|
5
|
-
#describe "#release" do
|
6
|
-
|
7
|
-
#context "when called for the first time with many permits available" do
|
8
|
-
#it "should not raise error" do
|
9
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
10
|
-
|
11
|
-
#expect{ semaphore.release }.not_to raise_error
|
12
|
-
#end
|
13
|
-
#end
|
14
|
-
|
15
|
-
#context "when called a second time with one permit available" do
|
16
|
-
#it "should not raise error" do
|
17
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
18
|
-
#semaphore.release
|
19
|
-
#expect{ semaphore.release }.not_to raise_error
|
20
|
-
#end
|
21
|
-
#end
|
22
|
-
|
23
|
-
#end
|
24
|
-
|
25
|
-
#describe "#release, #reacquire" do
|
26
|
-
|
27
|
-
#context "when #release called with no permits available" do
|
28
|
-
#it "should wait until #reacquire is called" do
|
29
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
30
|
-
#semaphore.release
|
31
|
-
#semaphore.release
|
32
|
-
#thread = Thread.new do
|
33
|
-
#sleep 1
|
34
|
-
#semaphore.reacquire
|
35
|
-
#end
|
36
|
-
#start_time = Time.now
|
37
|
-
#semaphore.release
|
38
|
-
#end_time = Time.now
|
39
|
-
#duration = end_time - start_time
|
40
|
-
#thread.join
|
41
|
-
#expect(duration).to be > 0.5
|
42
|
-
#end
|
43
|
-
#end
|
44
|
-
|
45
|
-
#context "when #reacquire called when all permits are available" do
|
46
|
-
#it "should raise QuackConcurrency::Semaphore::Error" do
|
47
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
48
|
-
#expect{ semaphore.reacquire }.to raise_error(QuackConcurrency::Semaphore::Error)
|
49
|
-
#end
|
50
|
-
#end
|
51
|
-
|
52
|
-
#end
|
53
|
-
|
54
|
-
#describe "#release, #reacquire, #permit_available?, #permits_available" do
|
55
|
-
|
56
|
-
#context "#permit_available? and #permits_available" do
|
57
|
-
#it "should work as expected" do
|
58
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
59
|
-
#expect(semaphore.permit_available?).to eql true
|
60
|
-
#expect(semaphore.permits_available).to eql 2
|
61
|
-
#semaphore.release
|
62
|
-
#expect(semaphore.permit_available?).to eql true
|
63
|
-
#expect(semaphore.permits_available).to eql 1
|
64
|
-
#semaphore.release
|
65
|
-
#expect(semaphore.permit_available?).to eql false
|
66
|
-
#expect(semaphore.permits_available).to eql 0
|
67
|
-
#semaphore.reacquire
|
68
|
-
#expect(semaphore.permit_available?).to eql true
|
69
|
-
#expect(semaphore.permits_available).to eql 1
|
70
|
-
#semaphore.reacquire
|
71
|
-
#expect(semaphore.permit_available?).to eql true
|
72
|
-
#expect(semaphore.permits_available).to eql 2
|
73
|
-
#end
|
74
|
-
#end
|
75
|
-
|
76
|
-
#end
|
77
|
-
|
78
|
-
#describe "#set_permit_count, #permits_available" do
|
79
|
-
|
80
|
-
#context "when #set_permit_count is called with more permits then currently exist" do
|
81
|
-
#it "should add permits" do
|
82
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
83
|
-
#expect(semaphore.permits_available).to eql 2
|
84
|
-
#semaphore.set_permit_count(3)
|
85
|
-
#expect(semaphore.permits_available).to eql 3
|
86
|
-
#end
|
87
|
-
#end
|
88
|
-
|
89
|
-
#context "when #set_permit_count is called with less permits then currently exist" do
|
90
|
-
#it "should remove permits" do
|
91
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
92
|
-
#expect(semaphore.permits_available).to eql 2
|
93
|
-
#semaphore.set_permit_count(1)
|
94
|
-
#expect(semaphore.permits_available).to eql 1
|
95
|
-
#end
|
96
|
-
#end
|
97
|
-
|
98
|
-
#end
|
99
|
-
|
100
|
-
#describe "#set_permit_count, #permits_available, #release" do
|
101
|
-
|
102
|
-
#context "when #set_permit_count is called with less permits then currently available" do
|
103
|
-
#it "should raise QuackConcurrency::Semaphore::Error" do
|
104
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
105
|
-
#expect(semaphore.permits_available).to eql 2
|
106
|
-
#semaphore.release
|
107
|
-
#semaphore.release
|
108
|
-
#expect{ semaphore.set_permit_count(1) }.to raise_error(QuackConcurrency::Semaphore::Error)
|
109
|
-
#end
|
110
|
-
#end
|
111
|
-
|
112
|
-
#end
|
113
|
-
|
114
|
-
#describe "#set_permit_count, #release" do
|
115
|
-
|
116
|
-
#context "when #set_permit_count is called with more permits when one thread is waiting on #release" do
|
117
|
-
#it "should resume the thread" do
|
118
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
119
|
-
#semaphore.release
|
120
|
-
#semaphore.release
|
121
|
-
#thread = Thread.new do
|
122
|
-
#sleep 1
|
123
|
-
#semaphore.set_permit_count(3)
|
124
|
-
#end
|
125
|
-
#start_time = Time.now
|
126
|
-
#semaphore.release
|
127
|
-
#end_time = Time.now
|
128
|
-
#duration = end_time - start_time
|
129
|
-
#thread.join
|
130
|
-
#expect(duration).to be > 0.5
|
131
|
-
#end
|
132
|
-
#end
|
133
|
-
|
134
|
-
#end
|
135
|
-
|
136
|
-
#describe "#set_permit_count!, #permits_available" do
|
137
|
-
|
138
|
-
#context "when #set_permit_count! is called with more permits then currently exist" do
|
139
|
-
#it "should add permits" do
|
140
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
141
|
-
#expect(semaphore.permits_available).to eql 2
|
142
|
-
#semaphore.set_permit_count!(3)
|
143
|
-
#expect(semaphore.permits_available).to eql 3
|
144
|
-
#end
|
145
|
-
#end
|
146
|
-
|
147
|
-
#context "when #set_permit_count! is called with less permits then currently exist" do
|
148
|
-
#it "should remove permits" do
|
149
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
150
|
-
#expect(semaphore.permits_available).to eql 2
|
151
|
-
#semaphore.set_permit_count!(1)
|
152
|
-
#expect(semaphore.permits_available).to eql 1
|
153
|
-
#end
|
154
|
-
#end
|
155
|
-
|
156
|
-
#end
|
157
|
-
|
158
|
-
#describe "#set_permit_count!, #permits_available, #release, #reacquire" do
|
159
|
-
|
160
|
-
#context "when #set_permit_count! is called with less permits then currently available" do
|
161
|
-
#it "should force new permit count" do
|
162
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
163
|
-
#expect(semaphore.permits_available).to eql 2
|
164
|
-
#semaphore.release
|
165
|
-
#semaphore.release
|
166
|
-
#semaphore.set_permit_count!(1)
|
167
|
-
#expect(semaphore.permit_available?).to eql false
|
168
|
-
#semaphore.reacquire
|
169
|
-
#expect(semaphore.permit_available?).to eql false
|
170
|
-
#semaphore.reacquire
|
171
|
-
#expect(semaphore.permit_available?).to eql true
|
172
|
-
#end
|
173
|
-
#end
|
174
|
-
|
175
|
-
#end
|
176
|
-
|
177
|
-
#describe "#set_permit_count!, #release" do
|
178
|
-
|
179
|
-
#context "when #set_permit_count! is called with more permits when one thread is waiting on #release" do
|
180
|
-
#it "should resume the thread" do
|
181
|
-
#semaphore = QuackConcurrency::Semaphore.new(2)
|
182
|
-
#semaphore.release
|
183
|
-
#semaphore.release
|
184
|
-
#thread = Thread.new do
|
185
|
-
#sleep 1
|
186
|
-
#semaphore.set_permit_count(3)
|
187
|
-
#end
|
188
|
-
#start_time = Time.now
|
189
|
-
#semaphore.release
|
190
|
-
#end_time = Time.now
|
191
|
-
#duration = end_time - start_time
|
192
|
-
#thread.join
|
193
|
-
#expect(duration).to be > 0.5
|
194
|
-
#end
|
195
|
-
#end
|
196
|
-
|
197
|
-
#end
|
198
|
-
|
199
|
-
#describe "#set_permit_count!, #release, #permit_available?" do
|
200
|
-
|
201
|
-
#context "when semaphore has no permits available, them #set_permit_count! is called to remove 2 permits, then called again to add 1 permit" do
|
202
|
-
#it "should not have any permits available" do
|
203
|
-
#semaphore = QuackConcurrency::Semaphore.new(3)
|
204
|
-
#semaphore.release
|
205
|
-
#semaphore.release
|
206
|
-
#expect(semaphore.permit_available?).to eql true
|
207
|
-
#semaphore.set_permit_count!(1)
|
208
|
-
#expect(semaphore.permit_available?).to eql false
|
209
|
-
#semaphore.set_permit_count!(2)
|
210
|
-
#expect(semaphore.permit_available?).to eql false
|
211
|
-
#end
|
212
|
-
#end
|
213
|
-
|
214
|
-
#end
|
215
|
-
|
216
|
-
#describe "#set_permit_count!, #release, #reacquire" do
|
217
|
-
|
218
|
-
#context "when semaphore has no permits available, them #set_permit_count! is called to remove 2 permits, then a thread starts waiting for #release, then #set_permit_count! is called again to add 1 permit" do
|
219
|
-
#it "thread should wait for #reacquire to be called" do
|
220
|
-
#semaphore = QuackConcurrency::Semaphore.new(3)
|
221
|
-
#semaphore.release
|
222
|
-
#semaphore.release
|
223
|
-
#semaphore.release
|
224
|
-
#semaphore.set_permit_count!(1)
|
225
|
-
#thread = Thread.new do
|
226
|
-
#sleep 1
|
227
|
-
#semaphore.set_permit_count!(2)
|
228
|
-
#sleep 1
|
229
|
-
#semaphore.reacquire
|
230
|
-
#sleep 1
|
231
|
-
#semaphore.reacquire
|
232
|
-
#end
|
233
|
-
#start_time = Time.now
|
234
|
-
#semaphore.release
|
235
|
-
#end_time = Time.now
|
236
|
-
#duration = end_time - start_time
|
237
|
-
#thread.join
|
238
|
-
#expect(duration).to be_between(2.5, 3.5)
|
239
|
-
#end
|
240
|
-
#end
|
241
|
-
|
242
|
-
#end
|
243
|
-
|
244
|
-
#end
|