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,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
@@ -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