atomic-ruby 0.11.1 → 0.13.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/CHANGELOG.md +9 -0
- data/README.md +125 -30
- data/lib/atomic-ruby/atomic_condition_variable.rb +171 -0
- data/lib/atomic-ruby/atomic_thread_pool.rb +34 -30
- data/lib/atomic-ruby/version.rb +1 -1
- data/lib/atomic-ruby.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 540189a4554f5e046af0b5305063a96be9fa58cfac1740e5e6076d6b1c48cd40
|
|
4
|
+
data.tar.gz: 528fa3c2e94c0d403ec8792076a773d3e7ce800852f1472dbde8e9ce7bbfb6f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8ad39592c8615f88aa0da987d1de06d87496c6c82481a9678c907937de65d308f4595e54008b0e357d99605fc660d8b35b986397ad0573e4b81dbf5de8926695
|
|
7
|
+
data.tar.gz: 46794d17727ab6b835210c7fc8e901d8c7daf37ff319c16b0efdb87cdc8bd9c95b6fb2dad171f96ca8d7b52b3fb8bd08cfde002f264cbe2155e6041310e74827
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.13.0] - 2026-06-12
|
|
4
|
+
|
|
5
|
+
- Replace `sleep` with `AtomicConditionVariable` in idle `AtomicThreadPool` workers
|
|
6
|
+
- Add `AtomicConditionVariable`
|
|
7
|
+
|
|
8
|
+
## [0.12.0] - 2026-05-18
|
|
9
|
+
|
|
10
|
+
- Wake idle `AtomicThreadPool` workers on enqueue
|
|
11
|
+
|
|
3
12
|
## [0.11.1] - 2026-05-17
|
|
4
13
|
|
|
5
14
|
- Replace `Thread.pass` busy-wait with `sleep` in idle `AtomicThreadPool` workers
|
data/README.md
CHANGED
|
@@ -49,6 +49,27 @@ atom.toggle
|
|
|
49
49
|
p atom.false? #=> true
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
`AtomicConditionVariable`:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
require "atomic-ruby"
|
|
56
|
+
|
|
57
|
+
condvar = AtomicConditionVariable.new
|
|
58
|
+
ready = AtomicBoolean.new(false)
|
|
59
|
+
p condvar.waiter_count #=> 0
|
|
60
|
+
|
|
61
|
+
waiter = Thread.new do
|
|
62
|
+
condvar.wait { ready.true? }
|
|
63
|
+
end
|
|
64
|
+
Thread.pass until condvar.waiter_count == 1
|
|
65
|
+
p condvar.waiter_count #=> 1
|
|
66
|
+
|
|
67
|
+
ready.make_true
|
|
68
|
+
p condvar.signal #=> true
|
|
69
|
+
waiter.join
|
|
70
|
+
p condvar.waiter_count #=> 0
|
|
71
|
+
```
|
|
72
|
+
|
|
52
73
|
`AtomicThreadPool`:
|
|
53
74
|
|
|
54
75
|
```ruby
|
|
@@ -98,7 +119,7 @@ p latch.count #=> 0
|
|
|
98
119
|
```
|
|
99
120
|
|
|
100
121
|
> [!NOTE]
|
|
101
|
-
> `Atom`, `AtomicBoolean`, and `AtomicCountDownLatch` are Ractor-safe in Ruby 4.0+.
|
|
122
|
+
> `Atom`, `AtomicBoolean`, and `AtomicCountDownLatch` are Ractor-safe in Ruby 4.0+. `AtomicConditionVariable` is not, since it parks `Thread` references which cannot be shared across ractors.
|
|
102
123
|
|
|
103
124
|
## Benchmarks
|
|
104
125
|
|
|
@@ -208,11 +229,11 @@ puts "Atomic Ruby Atomic Bank Account: #{results[2].real.round(6)} seconds"
|
|
|
208
229
|
```
|
|
209
230
|
|
|
210
231
|
```
|
|
211
|
-
> bundle exec rake compile && bundle exec ruby examples/atom_benchmark.rb
|
|
232
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atom_benchmark.rb
|
|
212
233
|
|
|
213
|
-
ruby version: ruby 4.0.
|
|
234
|
+
ruby version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
|
|
214
235
|
concurrent-ruby version: 1.3.6
|
|
215
|
-
atomic-ruby version: 0.
|
|
236
|
+
atomic-ruby version: 0.13.0
|
|
216
237
|
|
|
217
238
|
Balances:
|
|
218
239
|
Synchronized Bank Account Balance: 975
|
|
@@ -220,9 +241,9 @@ Concurrent Ruby Atomic Bank Account Balance: 975
|
|
|
220
241
|
Atomic Ruby Atomic Bank Account Balance: 975
|
|
221
242
|
|
|
222
243
|
Benchmark Results:
|
|
223
|
-
Synchronized Bank Account: 5.
|
|
224
|
-
Concurrent Ruby Atomic Bank Account: 5.
|
|
225
|
-
Atomic Ruby Atomic Bank Account: 5.
|
|
244
|
+
Synchronized Bank Account: 5.109724 seconds
|
|
245
|
+
Concurrent Ruby Atomic Bank Account: 5.137749 seconds
|
|
246
|
+
Atomic Ruby Atomic Bank Account: 5.101637 seconds
|
|
226
247
|
```
|
|
227
248
|
|
|
228
249
|
</details>
|
|
@@ -299,31 +320,105 @@ end
|
|
|
299
320
|
```
|
|
300
321
|
|
|
301
322
|
```
|
|
302
|
-
> bundle exec rake compile && bundle exec ruby examples/atomic_boolean_benchmark.rb
|
|
323
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_boolean_benchmark.rb
|
|
303
324
|
|
|
304
|
-
ruby version: ruby 4.0.
|
|
325
|
+
ruby version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
|
|
305
326
|
concurrent-ruby version: 1.3.6
|
|
306
|
-
atomic-ruby version: 0.
|
|
327
|
+
atomic-ruby version: 0.13.0
|
|
328
|
+
|
|
329
|
+
Warming up --------------------------------------
|
|
330
|
+
Synchronized Boolean Toggle 167.000 i/100ms
|
|
331
|
+
Concurrent Ruby Atomic Boolean Toggle 132.000 i/100ms
|
|
332
|
+
Atomic Ruby Atomic Boolean Toggle 117.000 i/100ms
|
|
333
|
+
Calculating -------------------------------------
|
|
334
|
+
Synchronized Boolean Toggle 1.653k (± 2.0%) i/s (605.04 μs/i) - 8.350k in 5.052096s
|
|
335
|
+
Concurrent Ruby Atomic Boolean Toggle 1.313k (± 2.1%) i/s (761.45 μs/i) - 6.600k in 5.025579s
|
|
336
|
+
Atomic Ruby Atomic Boolean Toggle 1.230k (± 4.8%) i/s (812.86 μs/i) - 6.201k in 5.040557s
|
|
337
|
+
|
|
338
|
+
Comparison:
|
|
339
|
+
Synchronized Boolean Toggle: 1652.8 i/s
|
|
340
|
+
Concurrent Ruby Atomic Boolean Toggle: 1313.3 i/s - 1.26x slower
|
|
341
|
+
Atomic Ruby Atomic Boolean Toggle: 1230.2 i/s - 1.34x slower
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
</details>
|
|
345
|
+
|
|
346
|
+
<details>
|
|
347
|
+
|
|
348
|
+
<summary>AtomicConditionVariable</summary>
|
|
349
|
+
|
|
350
|
+
```ruby
|
|
351
|
+
# frozen_string_literal: true
|
|
352
|
+
|
|
353
|
+
require "benchmark/ips"
|
|
354
|
+
require_relative "../lib/atomic-ruby"
|
|
355
|
+
|
|
356
|
+
module Benchmark
|
|
357
|
+
module IPS
|
|
358
|
+
class Job
|
|
359
|
+
class StreamReport
|
|
360
|
+
def start_warming
|
|
361
|
+
@out.puts "\n"
|
|
362
|
+
@out.puts "ruby version: #{RUBY_DESCRIPTION}"
|
|
363
|
+
@out.puts "atomic-ruby version: #{AtomicRuby::VERSION}"
|
|
364
|
+
@out.puts "\n"
|
|
365
|
+
@out.puts "Warming up --------------------------------------"
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
Benchmark.ips do |x|
|
|
373
|
+
x.report("Synchronized Condition Variable Wait/Signal") do
|
|
374
|
+
flag = false
|
|
375
|
+
mutex = Mutex.new
|
|
376
|
+
condvar = ConditionVariable.new
|
|
377
|
+
|
|
378
|
+
waiter = Thread.new do
|
|
379
|
+
mutex.synchronize do
|
|
380
|
+
condvar.wait(mutex) until flag
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
mutex.synchronize do
|
|
385
|
+
flag = true
|
|
386
|
+
condvar.signal
|
|
387
|
+
end
|
|
388
|
+
waiter.join
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
x.report("Atomic Ruby Atomic Condition Variable Wait/Signal") do
|
|
392
|
+
flag = AtomicBoolean.new(false)
|
|
393
|
+
condvar = AtomicConditionVariable.new
|
|
394
|
+
|
|
395
|
+
waiter = Thread.new { condvar.wait { flag.true? } }
|
|
396
|
+
|
|
397
|
+
flag.make_true
|
|
398
|
+
condvar.signal
|
|
399
|
+
waiter.join
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
x.compare!
|
|
403
|
+
end
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
```
|
|
407
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_condition_variable_benchmark.rb
|
|
408
|
+
|
|
409
|
+
ruby version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
|
|
410
|
+
atomic-ruby version: 0.13.0
|
|
307
411
|
|
|
308
412
|
Warming up --------------------------------------
|
|
309
|
-
Synchronized
|
|
310
|
-
|
|
311
|
-
Concurrent Ruby Atomic Boolean Toggle
|
|
312
|
-
117.000 i/100ms
|
|
313
|
-
Atomic Ruby Atomic Boolean Toggle
|
|
314
|
-
101.000 i/100ms
|
|
413
|
+
Synchronized Condition Variable Wait/Signal 3.847k i/100ms
|
|
414
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal 3.704k i/100ms
|
|
315
415
|
Calculating -------------------------------------
|
|
316
|
-
Synchronized
|
|
317
|
-
|
|
318
|
-
Concurrent Ruby Atomic Boolean Toggle
|
|
319
|
-
1.149k (± 2.9%) i/s (870.53 μs/i) - 5.850k in 5.097019s
|
|
320
|
-
Atomic Ruby Atomic Boolean Toggle
|
|
321
|
-
1.046k (± 2.1%) i/s (955.57 μs/i) - 5.252k in 5.021118s
|
|
416
|
+
Synchronized Condition Variable Wait/Signal 38.661k (± 4.4%) i/s (25.87 μs/i) - 196.197k in 5.074793s
|
|
417
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal 37.895k (± 4.7%) i/s (26.39 μs/i) - 192.608k in 5.082648s
|
|
322
418
|
|
|
323
419
|
Comparison:
|
|
324
|
-
Synchronized
|
|
325
|
-
|
|
326
|
-
Atomic Ruby Atomic Boolean Toggle: 1046.5 i/s - 1.35x slower
|
|
420
|
+
Synchronized Condition Variable Wait/Signal: 38661.1 i/s
|
|
421
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal: 37895.2 i/s - same-ish: difference falls within error
|
|
327
422
|
```
|
|
328
423
|
|
|
329
424
|
</details>
|
|
@@ -377,15 +472,15 @@ puts "Atomic Ruby Atomic Thread Pool: #{results[1].real.round(6)} seconds"
|
|
|
377
472
|
```
|
|
378
473
|
|
|
379
474
|
```
|
|
380
|
-
> bundle exec rake compile && bundle exec ruby examples/atomic_thread_pool_benchmark.rb
|
|
475
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_thread_pool_benchmark.rb
|
|
381
476
|
|
|
382
|
-
ruby version: ruby 4.0.
|
|
477
|
+
ruby version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
|
|
383
478
|
concurrent-ruby version: 1.3.6
|
|
384
|
-
atomic-ruby version: 0.
|
|
479
|
+
atomic-ruby version: 0.13.0
|
|
385
480
|
|
|
386
481
|
Benchmark Results:
|
|
387
|
-
Concurrent Ruby Thread Pool: 5.
|
|
388
|
-
Atomic Ruby Atomic Thread Pool: 5.
|
|
482
|
+
Concurrent Ruby Thread Pool: 5.792203 seconds
|
|
483
|
+
Atomic Ruby Atomic Thread Pool: 5.504388 seconds
|
|
389
484
|
```
|
|
390
485
|
|
|
391
486
|
</details>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "atom"
|
|
5
|
+
|
|
6
|
+
module AtomicRuby
|
|
7
|
+
# Provides lock-free wait/signal coordination using atomic operations.
|
|
8
|
+
#
|
|
9
|
+
# AtomicConditionVariable lets one or more threads park until another
|
|
10
|
+
# thread signals them, without the paired `Mutex` that Ruby's
|
|
11
|
+
# `ConditionVariable` requires. Coordination is done by publishing the
|
|
12
|
+
# set of parked threads through an {Atom}, so all in-process
|
|
13
|
+
# synchronisation stays on the gem's CAS path. Parking still uses
|
|
14
|
+
# `Thread.stop` and `Thread#wakeup`, which are the standard kernel-level
|
|
15
|
+
# primitives Ruby exposes for sleeping a thread.
|
|
16
|
+
#
|
|
17
|
+
# The lost-wakeup race in a naive `check-then-park` consumer is avoided
|
|
18
|
+
# by the {#wait} contract: a waiter registers itself before re-evaluating
|
|
19
|
+
# the predicate, so any signal that fires after the producer makes the
|
|
20
|
+
# predicate true is guaranteed to see the waiter and wake it. Ruby also
|
|
21
|
+
# remembers pending wakeups across `Thread.stop`, so a wakeup that
|
|
22
|
+
# arrives between the predicate check and the actual park is not lost.
|
|
23
|
+
#
|
|
24
|
+
# @example Basic usage
|
|
25
|
+
# condvar = AtomicConditionVariable.new
|
|
26
|
+
# ready = AtomicBoolean.new(false)
|
|
27
|
+
#
|
|
28
|
+
# waiter = Thread.new do
|
|
29
|
+
# condvar.wait { ready.true? }
|
|
30
|
+
# puts "ready"
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# ready.make_true
|
|
34
|
+
# condvar.signal
|
|
35
|
+
#
|
|
36
|
+
# @example Worker loop draining an atomic queue
|
|
37
|
+
# condvar.wait do
|
|
38
|
+
# work = nil
|
|
39
|
+
# queue.swap do |q|
|
|
40
|
+
# q.empty? ? q : (work = q.first; q.drop(1).freeze)
|
|
41
|
+
# end
|
|
42
|
+
# work
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# @note This class is NOT Ractor-safe as it parks `Thread` references,
|
|
46
|
+
# which cannot be shared across ractors.
|
|
47
|
+
class AtomicConditionVariable
|
|
48
|
+
# Creates a new condition variable with no parked threads.
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# condvar = AtomicConditionVariable.new
|
|
52
|
+
#
|
|
53
|
+
# @rbs () -> void
|
|
54
|
+
def initialize
|
|
55
|
+
@waiters = Atom.new([].freeze)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the number of currently parked waiters.
|
|
59
|
+
#
|
|
60
|
+
# This operation is atomic and thread-safe. The returned value reflects
|
|
61
|
+
# the state at the time of the call, but may change immediately after
|
|
62
|
+
# in concurrent environments.
|
|
63
|
+
#
|
|
64
|
+
# @return [Integer] The number of currently parked waiters
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# condvar = AtomicConditionVariable.new
|
|
68
|
+
# puts condvar.waiter_count #=> 0
|
|
69
|
+
#
|
|
70
|
+
# @rbs () -> Integer
|
|
71
|
+
def waiter_count
|
|
72
|
+
@waiters.value.size
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Wakes one parked waiter, or no-ops if none are parked.
|
|
76
|
+
#
|
|
77
|
+
# If a waiter has registered itself but is not yet inside `Thread.stop`,
|
|
78
|
+
# Ruby remembers the wakeup and the next `Thread.stop` returns
|
|
79
|
+
# immediately.
|
|
80
|
+
#
|
|
81
|
+
# @return [true, false] true if a waiter was signalled, false otherwise
|
|
82
|
+
#
|
|
83
|
+
# @example
|
|
84
|
+
# condvar = AtomicConditionVariable.new
|
|
85
|
+
# condvar.signal #=> false
|
|
86
|
+
#
|
|
87
|
+
# @rbs () -> bool
|
|
88
|
+
def signal
|
|
89
|
+
target = nil
|
|
90
|
+
@waiters.swap do |waiters|
|
|
91
|
+
if waiters.empty?
|
|
92
|
+
waiters
|
|
93
|
+
else
|
|
94
|
+
target = waiters.first
|
|
95
|
+
waiters.drop(1).freeze
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
return false unless target
|
|
99
|
+
|
|
100
|
+
target.wakeup rescue nil
|
|
101
|
+
true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Wakes every parked waiter.
|
|
105
|
+
#
|
|
106
|
+
# Each woken thread observes the wake the same way as with {#signal}.
|
|
107
|
+
#
|
|
108
|
+
# @return [Integer] The number of waiters signalled
|
|
109
|
+
#
|
|
110
|
+
# @example
|
|
111
|
+
# condvar = AtomicConditionVariable.new
|
|
112
|
+
# condvar.broadcast #=> 0
|
|
113
|
+
#
|
|
114
|
+
# @rbs () -> Integer
|
|
115
|
+
def broadcast
|
|
116
|
+
targets = nil
|
|
117
|
+
@waiters.swap do |waiters|
|
|
118
|
+
targets = waiters
|
|
119
|
+
[].freeze
|
|
120
|
+
end
|
|
121
|
+
targets.each { |thread| thread.wakeup rescue nil }
|
|
122
|
+
targets.size
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Blocks until the given block returns a truthy value, then returns that
|
|
126
|
+
# value.
|
|
127
|
+
#
|
|
128
|
+
# The block is evaluated optimistically first. If it returns truthy on
|
|
129
|
+
# that pass, no waiter is registered and the call returns immediately.
|
|
130
|
+
# Otherwise the calling thread registers itself, re-evaluates the
|
|
131
|
+
# block, and parks via `Thread.stop` until a {#signal} or {#broadcast}
|
|
132
|
+
# wakes it. The block may run more than once and may run concurrently
|
|
133
|
+
# with a signalling thread.
|
|
134
|
+
#
|
|
135
|
+
# @yieldreturn [untyped] Truthy to wake, falsy to keep waiting
|
|
136
|
+
# @return [untyped] The first truthy value returned by the block
|
|
137
|
+
#
|
|
138
|
+
# @example Simple wait
|
|
139
|
+
# condvar = AtomicConditionVariable.new
|
|
140
|
+
# ready = AtomicBoolean.new(false)
|
|
141
|
+
#
|
|
142
|
+
# Thread.new do
|
|
143
|
+
# sleep(1)
|
|
144
|
+
# ready.make_true
|
|
145
|
+
# condvar.signal
|
|
146
|
+
# end
|
|
147
|
+
#
|
|
148
|
+
# condvar.wait { ready.true? } #=> true
|
|
149
|
+
#
|
|
150
|
+
# @rbs () { () -> untyped } -> untyped
|
|
151
|
+
def wait
|
|
152
|
+
result = yield
|
|
153
|
+
return result if result
|
|
154
|
+
|
|
155
|
+
self_thread = Thread.current
|
|
156
|
+
loop do
|
|
157
|
+
@waiters.swap { |waiters| (waiters + [self_thread]).freeze }
|
|
158
|
+
result = yield
|
|
159
|
+
if result
|
|
160
|
+
@waiters.swap { |waiters| (waiters - [self_thread]).freeze }
|
|
161
|
+
return result
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
Thread.stop
|
|
165
|
+
@waiters.swap { |waiters| (waiters - [self_thread]).freeze }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
AtomicConditionVariable = AtomicRuby::AtomicConditionVariable
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require_relative "atom"
|
|
5
|
+
require_relative "atomic_condition_variable"
|
|
5
6
|
|
|
6
7
|
module AtomicRuby
|
|
7
8
|
# Provides a fixed-size thread pool using atomic operations for work queuing.
|
|
@@ -86,6 +87,7 @@ module AtomicRuby
|
|
|
86
87
|
@on_error = on_error
|
|
87
88
|
|
|
88
89
|
@state = Atom.new(in: nil, out: nil, count: 0, shutdown: false)
|
|
90
|
+
@work_available = AtomicConditionVariable.new
|
|
89
91
|
@started_thread_count = Atom.new(0)
|
|
90
92
|
@active_thread_count = Atom.new(0)
|
|
91
93
|
@threads = []
|
|
@@ -126,6 +128,8 @@ module AtomicRuby
|
|
|
126
128
|
end
|
|
127
129
|
end
|
|
128
130
|
raise EnqueuedWorkAfterShutdownError if state[:shutdown]
|
|
131
|
+
|
|
132
|
+
@work_available.signal
|
|
129
133
|
end
|
|
130
134
|
|
|
131
135
|
# Returns the number of currently alive worker threads.
|
|
@@ -234,6 +238,7 @@ module AtomicRuby
|
|
|
234
238
|
end
|
|
235
239
|
return if already_shutdown
|
|
236
240
|
|
|
241
|
+
@work_available.broadcast
|
|
237
242
|
@threads.each(&:join)
|
|
238
243
|
end
|
|
239
244
|
|
|
@@ -261,40 +266,39 @@ module AtomicRuby
|
|
|
261
266
|
work = nil
|
|
262
267
|
should_shutdown = false
|
|
263
268
|
|
|
264
|
-
@
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
269
|
+
@work_available.wait do
|
|
270
|
+
@state.swap do |current_state|
|
|
271
|
+
if current_state[:shutdown] && current_state[:in].nil? && current_state[:out].nil?
|
|
272
|
+
should_shutdown = true
|
|
273
|
+
current_state
|
|
274
|
+
elsif current_state[:out]
|
|
275
|
+
work = current_state[:out][:value]
|
|
276
|
+
current_state.merge(out: current_state[:out][:next], count: current_state[:count] - 1)
|
|
277
|
+
elsif current_state[:in]
|
|
278
|
+
new_out = reverse_list(current_state[:in])
|
|
279
|
+
work = new_out[:value]
|
|
280
|
+
current_state.merge(in: nil, out: new_out[:next], count: current_state[:count] - 1)
|
|
281
|
+
else
|
|
282
|
+
current_state
|
|
283
|
+
end
|
|
277
284
|
end
|
|
285
|
+
work || should_shutdown
|
|
278
286
|
end
|
|
279
287
|
|
|
280
|
-
if should_shutdown
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
warn err.full_message
|
|
292
|
-
end
|
|
293
|
-
ensure
|
|
294
|
-
@active_thread_count.swap { |current_count| current_count - 1 }
|
|
288
|
+
break if should_shutdown
|
|
289
|
+
|
|
290
|
+
@active_thread_count.swap { |current_count| current_count + 1 }
|
|
291
|
+
begin
|
|
292
|
+
work.call
|
|
293
|
+
rescue => err
|
|
294
|
+
if @on_error
|
|
295
|
+
@on_error.call(err)
|
|
296
|
+
else
|
|
297
|
+
warn "#{thread_name} rescued:"
|
|
298
|
+
warn err.full_message
|
|
295
299
|
end
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
ensure
|
|
301
|
+
@active_thread_count.swap { |current_count| current_count - 1 }
|
|
298
302
|
end
|
|
299
303
|
end
|
|
300
304
|
end
|
data/lib/atomic-ruby/version.rb
CHANGED
data/lib/atomic-ruby.rb
CHANGED
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
require_relative "atomic-ruby/version"
|
|
4
4
|
require_relative "atomic-ruby/atom"
|
|
5
5
|
require_relative "atomic-ruby/atomic_boolean"
|
|
6
|
+
require_relative "atomic-ruby/atomic_condition_variable"
|
|
6
7
|
require_relative "atomic-ruby/atomic_thread_pool"
|
|
7
8
|
require_relative "atomic-ruby/atomic_count_down_latch"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: atomic-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Young
|
|
@@ -27,6 +27,7 @@ files:
|
|
|
27
27
|
- lib/atomic-ruby.rb
|
|
28
28
|
- lib/atomic-ruby/atom.rb
|
|
29
29
|
- lib/atomic-ruby/atomic_boolean.rb
|
|
30
|
+
- lib/atomic-ruby/atomic_condition_variable.rb
|
|
30
31
|
- lib/atomic-ruby/atomic_count_down_latch.rb
|
|
31
32
|
- lib/atomic-ruby/atomic_thread_pool.rb
|
|
32
33
|
- lib/atomic-ruby/version.rb
|