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