atomic-ruby 0.12.0 → 0.14.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 +11 -0
- data/README.md +224 -30
- data/ext/atomic_ruby/atomic_ruby.c +422 -0
- data/lib/atomic-ruby/atomic_condition_variable.rb +157 -0
- data/lib/atomic-ruby/atomic_queue.rb +137 -0
- data/lib/atomic-ruby/atomic_thread_pool.rb +33 -76
- data/lib/atomic-ruby/version.rb +1 -1
- data/lib/atomic-ruby.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a0461b3ac6fd6dbc7674cf028f8fb3727cad76da32ad2c6054a92a2818fe25f
|
|
4
|
+
data.tar.gz: 6f20eabbb25f29f6c43c166cbe240c1ddf2a6def437317c392a53fe86699a6b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e29e25f0790c2c3f3417efae11440a5f0500e0e943ce4c4a903f7fc4180342e832319cfe82712284449c876c70aa29ea22899480668bb1f35cc02c9369f8a6c
|
|
7
|
+
data.tar.gz: f26bc0f694a0e00cc84c9988761a1cf56c50c89d499bd70eb88b3fb896749ba36465d53d16e03d5ad636c8e8cd59b5b1f179bca827cd85c9a59397951bcd6013
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.14.0] - 2026-08-15
|
|
4
|
+
|
|
5
|
+
- Replace the `AtomicConditionVariable` waiter list with a native doubly-linked list
|
|
6
|
+
- Replace the two-stack queue in `AtomicThreadPool` with `AtomicQueue`
|
|
7
|
+
- Add `AtomicQueue`
|
|
8
|
+
|
|
9
|
+
## [0.13.0] - 2026-06-12
|
|
10
|
+
|
|
11
|
+
- Replace `sleep` with `AtomicConditionVariable` in idle `AtomicThreadPool` workers
|
|
12
|
+
- Add `AtomicConditionVariable`
|
|
13
|
+
|
|
3
14
|
## [0.12.0] - 2026-05-18
|
|
4
15
|
|
|
5
16
|
- Wake idle `AtomicThreadPool` workers on enqueue
|
data/README.md
CHANGED
|
@@ -49,6 +49,48 @@ 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
|
+
|
|
73
|
+
`AtomicQueue`:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
require "atomic-ruby"
|
|
77
|
+
|
|
78
|
+
queue = AtomicQueue.new
|
|
79
|
+
p queue.empty? #=> true
|
|
80
|
+
p queue.size #=> 0
|
|
81
|
+
|
|
82
|
+
queue.push(1)
|
|
83
|
+
queue << 2
|
|
84
|
+
queue << 3
|
|
85
|
+
p queue.size #=> 3
|
|
86
|
+
|
|
87
|
+
p queue.pop #=> 1
|
|
88
|
+
p queue.pop #=> 2
|
|
89
|
+
p queue.pop #=> 3
|
|
90
|
+
p queue.pop #=> nil
|
|
91
|
+
p queue.empty? #=> true
|
|
92
|
+
```
|
|
93
|
+
|
|
52
94
|
`AtomicThreadPool`:
|
|
53
95
|
|
|
54
96
|
```ruby
|
|
@@ -98,7 +140,9 @@ p latch.count #=> 0
|
|
|
98
140
|
```
|
|
99
141
|
|
|
100
142
|
> [!NOTE]
|
|
101
|
-
> `Atom`, `AtomicBoolean`, and `AtomicCountDownLatch` are Ractor-safe in Ruby 4.0+.
|
|
143
|
+
> `Atom`, `AtomicBoolean`, and `AtomicCountDownLatch` are Ractor-safe in Ruby 4.0+. `AtomicConditionVariable` and
|
|
144
|
+
> `AtomicQueue` are not, since they hold mutable references (parked `Thread`s and queued values, respectively) which
|
|
145
|
+
> cannot be shared across ractors.
|
|
102
146
|
|
|
103
147
|
## Benchmarks
|
|
104
148
|
|
|
@@ -208,11 +252,11 @@ puts "Atomic Ruby Atomic Bank Account: #{results[2].real.round(6)} seconds"
|
|
|
208
252
|
```
|
|
209
253
|
|
|
210
254
|
```
|
|
211
|
-
> bundle exec rake compile && bundle exec ruby examples/atom_benchmark.rb
|
|
255
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atom_benchmark.rb
|
|
212
256
|
|
|
213
|
-
ruby version: ruby 4.0.
|
|
257
|
+
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
214
258
|
concurrent-ruby version: 1.3.6
|
|
215
|
-
atomic-ruby version: 0.
|
|
259
|
+
atomic-ruby version: 0.14.0
|
|
216
260
|
|
|
217
261
|
Balances:
|
|
218
262
|
Synchronized Bank Account Balance: 975
|
|
@@ -220,9 +264,9 @@ Concurrent Ruby Atomic Bank Account Balance: 975
|
|
|
220
264
|
Atomic Ruby Atomic Bank Account Balance: 975
|
|
221
265
|
|
|
222
266
|
Benchmark Results:
|
|
223
|
-
Synchronized Bank Account: 5.
|
|
224
|
-
Concurrent Ruby Atomic Bank Account: 5.
|
|
225
|
-
Atomic Ruby Atomic Bank Account: 5.
|
|
267
|
+
Synchronized Bank Account: 5.117072 seconds
|
|
268
|
+
Concurrent Ruby Atomic Bank Account: 5.128366 seconds
|
|
269
|
+
Atomic Ruby Atomic Bank Account: 5.110595 seconds
|
|
226
270
|
```
|
|
227
271
|
|
|
228
272
|
</details>
|
|
@@ -299,31 +343,181 @@ end
|
|
|
299
343
|
```
|
|
300
344
|
|
|
301
345
|
```
|
|
302
|
-
> bundle exec rake compile && bundle exec ruby examples/atomic_boolean_benchmark.rb
|
|
346
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_boolean_benchmark.rb
|
|
303
347
|
|
|
304
|
-
ruby version: ruby 4.0.
|
|
348
|
+
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
305
349
|
concurrent-ruby version: 1.3.6
|
|
306
|
-
atomic-ruby version: 0.
|
|
350
|
+
atomic-ruby version: 0.14.0
|
|
351
|
+
|
|
352
|
+
Warming up --------------------------------------
|
|
353
|
+
Synchronized Boolean Toggle 157.000 i/100ms
|
|
354
|
+
Concurrent Ruby Atomic Boolean Toggle 134.000 i/100ms
|
|
355
|
+
Atomic Ruby Atomic Boolean Toggle 120.000 i/100ms
|
|
356
|
+
Calculating -------------------------------------
|
|
357
|
+
Synchronized Boolean Toggle 1.629k (± 2.0%) i/s (613.87 μs/i) - 8.164k in 5.011636s
|
|
358
|
+
Concurrent Ruby Atomic Boolean Toggle 1.325k (± 2.0%) i/s (754.96 μs/i) - 6.700k in 5.058203s
|
|
359
|
+
Atomic Ruby Atomic Boolean Toggle 1.190k (± 2.6%) i/s (840.11 μs/i) - 6.000k in 5.040665s
|
|
360
|
+
|
|
361
|
+
Comparison:
|
|
362
|
+
Synchronized Boolean Toggle: 1629.0 i/s
|
|
363
|
+
Concurrent Ruby Atomic Boolean Toggle: 1324.6 i/s - 1.23x slower
|
|
364
|
+
Atomic Ruby Atomic Boolean Toggle: 1190.3 i/s - 1.37x slower
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
</details>
|
|
368
|
+
|
|
369
|
+
<details>
|
|
370
|
+
|
|
371
|
+
<summary>AtomicConditionVariable</summary>
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
# frozen_string_literal: true
|
|
375
|
+
|
|
376
|
+
require "benchmark/ips"
|
|
377
|
+
require_relative "../lib/atomic-ruby"
|
|
378
|
+
|
|
379
|
+
module Benchmark
|
|
380
|
+
module IPS
|
|
381
|
+
class Job
|
|
382
|
+
class StreamReport
|
|
383
|
+
def start_warming
|
|
384
|
+
@out.puts "\n"
|
|
385
|
+
@out.puts "ruby version: #{RUBY_DESCRIPTION}"
|
|
386
|
+
@out.puts "atomic-ruby version: #{AtomicRuby::VERSION}"
|
|
387
|
+
@out.puts "\n"
|
|
388
|
+
@out.puts "Warming up --------------------------------------"
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
Benchmark.ips do |x|
|
|
396
|
+
x.report("Synchronized Condition Variable Wait/Signal") do
|
|
397
|
+
flag = false
|
|
398
|
+
mutex = Mutex.new
|
|
399
|
+
condvar = ConditionVariable.new
|
|
400
|
+
|
|
401
|
+
waiter = Thread.new do
|
|
402
|
+
mutex.synchronize do
|
|
403
|
+
condvar.wait(mutex) until flag
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
mutex.synchronize do
|
|
408
|
+
flag = true
|
|
409
|
+
condvar.signal
|
|
410
|
+
end
|
|
411
|
+
waiter.join
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
x.report("Atomic Ruby Atomic Condition Variable Wait/Signal") do
|
|
415
|
+
flag = AtomicBoolean.new(false)
|
|
416
|
+
condvar = AtomicConditionVariable.new
|
|
417
|
+
|
|
418
|
+
waiter = Thread.new { condvar.wait { flag.true? } }
|
|
419
|
+
|
|
420
|
+
flag.make_true
|
|
421
|
+
condvar.signal
|
|
422
|
+
waiter.join
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
x.compare!
|
|
426
|
+
end
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_condition_variable_benchmark.rb
|
|
431
|
+
|
|
432
|
+
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
433
|
+
atomic-ruby version: 0.14.0
|
|
307
434
|
|
|
308
435
|
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
|
|
436
|
+
Synchronized Condition Variable Wait/Signal 3.977k i/100ms
|
|
437
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal 3.885k i/100ms
|
|
315
438
|
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
|
|
439
|
+
Synchronized Condition Variable Wait/Signal 39.458k (± 4.2%) i/s (25.34 μs/i) - 198.850k in 5.039596s
|
|
440
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal 39.046k (± 4.2%) i/s (25.61 μs/i) - 198.135k in 5.074411s
|
|
322
441
|
|
|
323
442
|
Comparison:
|
|
324
|
-
Synchronized
|
|
325
|
-
|
|
326
|
-
|
|
443
|
+
Synchronized Condition Variable Wait/Signal: 39457.5 i/s
|
|
444
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal: 39045.9 i/s - same-ish: difference falls within error
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
</details>
|
|
448
|
+
|
|
449
|
+
<details>
|
|
450
|
+
|
|
451
|
+
<summary>AtomicQueue</summary>
|
|
452
|
+
|
|
453
|
+
```ruby
|
|
454
|
+
# frozen_string_literal: true
|
|
455
|
+
|
|
456
|
+
require "benchmark/ips"
|
|
457
|
+
require_relative "../lib/atomic-ruby"
|
|
458
|
+
|
|
459
|
+
module Benchmark
|
|
460
|
+
module IPS
|
|
461
|
+
class Job
|
|
462
|
+
class StreamReport
|
|
463
|
+
def start_warming
|
|
464
|
+
@out.puts "\n"
|
|
465
|
+
@out.puts "ruby version: #{RUBY_DESCRIPTION}"
|
|
466
|
+
@out.puts "atomic-ruby version: #{AtomicRuby::VERSION}"
|
|
467
|
+
@out.puts "\n"
|
|
468
|
+
@out.puts "Warming up --------------------------------------"
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
Benchmark.ips do |x|
|
|
476
|
+
x.report("Synchronized Queue Push/Pop") do
|
|
477
|
+
queue = Thread::Queue.new
|
|
478
|
+
20.times.map do
|
|
479
|
+
Thread.new do
|
|
480
|
+
100.times do |i|
|
|
481
|
+
queue.push(i)
|
|
482
|
+
queue.pop(true) rescue nil
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end.each(&:join)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
x.report("Atomic Ruby Atomic Queue Push/Pop") do
|
|
489
|
+
queue = AtomicQueue.new
|
|
490
|
+
20.times.map do
|
|
491
|
+
Thread.new do
|
|
492
|
+
100.times do |i|
|
|
493
|
+
queue.push(i)
|
|
494
|
+
queue.pop
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end.each(&:join)
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
x.compare!
|
|
501
|
+
end
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_queue_benchmark.rb
|
|
506
|
+
|
|
507
|
+
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
508
|
+
atomic-ruby version: 0.14.0
|
|
509
|
+
|
|
510
|
+
Warming up --------------------------------------
|
|
511
|
+
Synchronized Queue Push/Pop 181.000 i/100ms
|
|
512
|
+
Atomic Ruby Atomic Queue Push/Pop 132.000 i/100ms
|
|
513
|
+
Calculating -------------------------------------
|
|
514
|
+
Synchronized Queue Push/Pop 1.845k (± 2.1%) i/s (541.91 μs/i) - 9.231k in 5.002394s
|
|
515
|
+
Atomic Ruby Atomic Queue Push/Pop 1.431k (± 6.8%) i/s (698.64 μs/i) - 7.260k in 5.072143s
|
|
516
|
+
|
|
517
|
+
Comparison:
|
|
518
|
+
Synchronized Queue Push/Pop: 1845.3 i/s
|
|
519
|
+
Atomic Ruby Atomic Queue Push/Pop: 1431.3 i/s - 1.29x slower
|
|
520
|
+
|
|
327
521
|
```
|
|
328
522
|
|
|
329
523
|
</details>
|
|
@@ -377,15 +571,15 @@ puts "Atomic Ruby Atomic Thread Pool: #{results[1].real.round(6)} seconds"
|
|
|
377
571
|
```
|
|
378
572
|
|
|
379
573
|
```
|
|
380
|
-
> bundle exec rake compile && bundle exec ruby examples/atomic_thread_pool_benchmark.rb
|
|
574
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_thread_pool_benchmark.rb
|
|
381
575
|
|
|
382
|
-
ruby version: ruby 4.0.
|
|
576
|
+
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
383
577
|
concurrent-ruby version: 1.3.6
|
|
384
|
-
atomic-ruby version: 0.
|
|
578
|
+
atomic-ruby version: 0.14.0
|
|
385
579
|
|
|
386
580
|
Benchmark Results:
|
|
387
|
-
Concurrent Ruby Thread Pool: 5.
|
|
388
|
-
Atomic Ruby Atomic Thread Pool:
|
|
581
|
+
Concurrent Ruby Thread Pool: 5.078227 seconds
|
|
582
|
+
Atomic Ruby Atomic Thread Pool: 4.824681 seconds
|
|
389
583
|
```
|
|
390
584
|
|
|
391
585
|
</details>
|
|
@@ -122,6 +122,405 @@ static VALUE rb_cAtom_initialized_ractor(VALUE self) {
|
|
|
122
122
|
}
|
|
123
123
|
#endif
|
|
124
124
|
|
|
125
|
+
typedef struct {
|
|
126
|
+
volatile VALUE value;
|
|
127
|
+
volatile VALUE next;
|
|
128
|
+
} atomic_ruby_queue_node_t;
|
|
129
|
+
|
|
130
|
+
static void atomic_ruby_queue_node_mark(void *ptr) {
|
|
131
|
+
atomic_ruby_queue_node_t *atomic_ruby_queue_node = (atomic_ruby_queue_node_t *)ptr;
|
|
132
|
+
rb_gc_mark_movable(atomic_ruby_queue_node->value);
|
|
133
|
+
rb_gc_mark_movable(atomic_ruby_queue_node->next);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static void atomic_ruby_queue_node_free(void *ptr) {
|
|
137
|
+
xfree(ptr);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static size_t atomic_ruby_queue_node_memsize(const void *ptr) {
|
|
141
|
+
return sizeof(atomic_ruby_queue_node_t);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static void atomic_ruby_queue_node_compact(void *ptr) {
|
|
145
|
+
atomic_ruby_queue_node_t *atomic_ruby_queue_node = (atomic_ruby_queue_node_t *)ptr;
|
|
146
|
+
atomic_ruby_queue_node->value = rb_gc_location(atomic_ruby_queue_node->value);
|
|
147
|
+
atomic_ruby_queue_node->next = rb_gc_location(atomic_ruby_queue_node->next);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static const rb_data_type_t atomic_ruby_queue_node_type = {
|
|
151
|
+
.wrap_struct_name = "AtomicRuby::AtomicQueue::Node",
|
|
152
|
+
.function = {
|
|
153
|
+
.dmark = atomic_ruby_queue_node_mark,
|
|
154
|
+
.dfree = atomic_ruby_queue_node_free,
|
|
155
|
+
.dsize = atomic_ruby_queue_node_memsize,
|
|
156
|
+
.dcompact = atomic_ruby_queue_node_compact
|
|
157
|
+
},
|
|
158
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
static VALUE rb_cAtomicQueueNode;
|
|
162
|
+
|
|
163
|
+
static VALUE atomic_ruby_queue_node_new(VALUE value, VALUE next) {
|
|
164
|
+
atomic_ruby_queue_node_t *atomic_ruby_queue_node;
|
|
165
|
+
VALUE obj = TypedData_Make_Struct(rb_cAtomicQueueNode, atomic_ruby_queue_node_t, &atomic_ruby_queue_node_type, atomic_ruby_queue_node);
|
|
166
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_queue_node->value, value);
|
|
167
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_queue_node->next, next);
|
|
168
|
+
return obj;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
typedef struct {
|
|
172
|
+
volatile VALUE head;
|
|
173
|
+
volatile VALUE tail;
|
|
174
|
+
volatile rb_atomic_t count;
|
|
175
|
+
} atomic_ruby_queue_t;
|
|
176
|
+
|
|
177
|
+
static void atomic_ruby_queue_mark(void *ptr) {
|
|
178
|
+
atomic_ruby_queue_t *atomic_ruby_queue = (atomic_ruby_queue_t *)ptr;
|
|
179
|
+
rb_gc_mark_movable(atomic_ruby_queue->head);
|
|
180
|
+
rb_gc_mark_movable(atomic_ruby_queue->tail);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static void atomic_ruby_queue_free(void *ptr) {
|
|
184
|
+
xfree(ptr);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static size_t atomic_ruby_queue_memsize(const void *ptr) {
|
|
188
|
+
return sizeof(atomic_ruby_queue_t);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static void atomic_ruby_queue_compact(void *ptr) {
|
|
192
|
+
atomic_ruby_queue_t *atomic_ruby_queue = (atomic_ruby_queue_t *)ptr;
|
|
193
|
+
atomic_ruby_queue->head = rb_gc_location(atomic_ruby_queue->head);
|
|
194
|
+
atomic_ruby_queue->tail = rb_gc_location(atomic_ruby_queue->tail);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static const rb_data_type_t atomic_ruby_queue_type = {
|
|
198
|
+
.wrap_struct_name = "AtomicRuby::AtomicQueue",
|
|
199
|
+
.function = {
|
|
200
|
+
.dmark = atomic_ruby_queue_mark,
|
|
201
|
+
.dfree = atomic_ruby_queue_free,
|
|
202
|
+
.dsize = atomic_ruby_queue_memsize,
|
|
203
|
+
.dcompact = atomic_ruby_queue_compact
|
|
204
|
+
},
|
|
205
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
static VALUE rb_cAtomicQueue_allocate(VALUE klass) {
|
|
209
|
+
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
210
|
+
VALUE obj = TypedData_Make_Struct(klass, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
211
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_queue->head, Qnil);
|
|
212
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_queue->tail, Qnil);
|
|
213
|
+
atomic_ruby_queue->count = 0;
|
|
214
|
+
return obj;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static VALUE rb_cAtomicQueue_initialize(VALUE self) {
|
|
218
|
+
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
219
|
+
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
220
|
+
|
|
221
|
+
VALUE sentinel = atomic_ruby_queue_node_new(Qnil, Qnil);
|
|
222
|
+
RB_OBJ_WRITE(self, &atomic_ruby_queue->head, sentinel);
|
|
223
|
+
RB_OBJ_WRITE(self, &atomic_ruby_queue->tail, sentinel);
|
|
224
|
+
|
|
225
|
+
return self;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static VALUE rb_cAtomicQueue_push(VALUE self, VALUE value) {
|
|
229
|
+
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
230
|
+
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
231
|
+
|
|
232
|
+
VALUE new_node = atomic_ruby_queue_node_new(value, Qnil);
|
|
233
|
+
|
|
234
|
+
VALUE tail;
|
|
235
|
+
while (1) {
|
|
236
|
+
tail = (VALUE)RUBY_ATOMIC_PTR_LOAD(atomic_ruby_queue->tail);
|
|
237
|
+
atomic_ruby_queue_node_t *tail_node;
|
|
238
|
+
TypedData_Get_Struct(tail, atomic_ruby_queue_node_t, &atomic_ruby_queue_node_type, tail_node);
|
|
239
|
+
VALUE tail_next = (VALUE)RUBY_ATOMIC_PTR_LOAD(tail_node->next);
|
|
240
|
+
|
|
241
|
+
if (tail != (VALUE)RUBY_ATOMIC_PTR_LOAD(atomic_ruby_queue->tail)) continue;
|
|
242
|
+
|
|
243
|
+
if (tail_next != Qnil) {
|
|
244
|
+
RUBY_ATOMIC_VALUE_CAS(atomic_ruby_queue->tail, tail, tail_next);
|
|
245
|
+
RB_OBJ_WRITTEN(self, tail, tail_next);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (RUBY_ATOMIC_VALUE_CAS(tail_node->next, Qnil, new_node) == Qnil) {
|
|
250
|
+
RB_OBJ_WRITTEN(tail, Qnil, new_node);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
RUBY_ATOMIC_VALUE_CAS(atomic_ruby_queue->tail, tail, new_node);
|
|
256
|
+
RB_OBJ_WRITTEN(self, tail, new_node);
|
|
257
|
+
RUBY_ATOMIC_INC(atomic_ruby_queue->count);
|
|
258
|
+
|
|
259
|
+
return self;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
static VALUE rb_cAtomicQueue_pop(VALUE self) {
|
|
263
|
+
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
264
|
+
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
265
|
+
|
|
266
|
+
VALUE head, head_next, result;
|
|
267
|
+
while (1) {
|
|
268
|
+
head = (VALUE)RUBY_ATOMIC_PTR_LOAD(atomic_ruby_queue->head);
|
|
269
|
+
VALUE tail = (VALUE)RUBY_ATOMIC_PTR_LOAD(atomic_ruby_queue->tail);
|
|
270
|
+
atomic_ruby_queue_node_t *head_node;
|
|
271
|
+
TypedData_Get_Struct(head, atomic_ruby_queue_node_t, &atomic_ruby_queue_node_type, head_node);
|
|
272
|
+
head_next = (VALUE)RUBY_ATOMIC_PTR_LOAD(head_node->next);
|
|
273
|
+
|
|
274
|
+
if (head != (VALUE)RUBY_ATOMIC_PTR_LOAD(atomic_ruby_queue->head)) continue;
|
|
275
|
+
|
|
276
|
+
if (head == tail) {
|
|
277
|
+
if (head_next == Qnil) return Qnil;
|
|
278
|
+
|
|
279
|
+
RUBY_ATOMIC_VALUE_CAS(atomic_ruby_queue->tail, tail, head_next);
|
|
280
|
+
RB_OBJ_WRITTEN(self, tail, head_next);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
atomic_ruby_queue_node_t *next_node;
|
|
285
|
+
TypedData_Get_Struct(head_next, atomic_ruby_queue_node_t, &atomic_ruby_queue_node_type, next_node);
|
|
286
|
+
result = next_node->value;
|
|
287
|
+
|
|
288
|
+
if (RUBY_ATOMIC_VALUE_CAS(atomic_ruby_queue->head, head, head_next) == head) {
|
|
289
|
+
RB_OBJ_WRITTEN(self, head, head_next);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
RUBY_ATOMIC_DEC(atomic_ruby_queue->count);
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
static VALUE rb_cAtomicQueue_size(VALUE self) {
|
|
299
|
+
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
300
|
+
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
301
|
+
return UINT2NUM((unsigned int)RUBY_ATOMIC_LOAD(atomic_ruby_queue->count));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
static VALUE rb_cAtomicQueue_empty_p(VALUE self) {
|
|
305
|
+
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
306
|
+
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
307
|
+
return RUBY_ATOMIC_LOAD(atomic_ruby_queue->count) == 0 ? Qtrue : Qfalse;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
typedef struct {
|
|
311
|
+
volatile VALUE prev;
|
|
312
|
+
volatile VALUE next;
|
|
313
|
+
volatile VALUE thread;
|
|
314
|
+
} atomic_ruby_condition_variable_waiter_t;
|
|
315
|
+
|
|
316
|
+
static void atomic_ruby_condition_variable_waiter_mark(void *ptr) {
|
|
317
|
+
atomic_ruby_condition_variable_waiter_t *atomic_ruby_condition_variable_waiter = (atomic_ruby_condition_variable_waiter_t *)ptr;
|
|
318
|
+
rb_gc_mark_movable(atomic_ruby_condition_variable_waiter->prev);
|
|
319
|
+
rb_gc_mark_movable(atomic_ruby_condition_variable_waiter->next);
|
|
320
|
+
rb_gc_mark_movable(atomic_ruby_condition_variable_waiter->thread);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
static void atomic_ruby_condition_variable_waiter_free(void *ptr) {
|
|
324
|
+
xfree(ptr);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
static size_t atomic_ruby_condition_variable_waiter_memsize(const void *ptr) {
|
|
328
|
+
return sizeof(atomic_ruby_condition_variable_waiter_t);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
static void atomic_ruby_condition_variable_waiter_compact(void *ptr) {
|
|
332
|
+
atomic_ruby_condition_variable_waiter_t *atomic_ruby_condition_variable_waiter = (atomic_ruby_condition_variable_waiter_t *)ptr;
|
|
333
|
+
atomic_ruby_condition_variable_waiter->prev = rb_gc_location(atomic_ruby_condition_variable_waiter->prev);
|
|
334
|
+
atomic_ruby_condition_variable_waiter->next = rb_gc_location(atomic_ruby_condition_variable_waiter->next);
|
|
335
|
+
atomic_ruby_condition_variable_waiter->thread = rb_gc_location(atomic_ruby_condition_variable_waiter->thread);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
static const rb_data_type_t atomic_ruby_condition_variable_waiter_type = {
|
|
339
|
+
.wrap_struct_name = "AtomicRuby::AtomicConditionVariable::Waiter",
|
|
340
|
+
.function = {
|
|
341
|
+
.dmark = atomic_ruby_condition_variable_waiter_mark,
|
|
342
|
+
.dfree = atomic_ruby_condition_variable_waiter_free,
|
|
343
|
+
.dsize = atomic_ruby_condition_variable_waiter_memsize,
|
|
344
|
+
.dcompact = atomic_ruby_condition_variable_waiter_compact
|
|
345
|
+
},
|
|
346
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
static VALUE rb_cAtomicConditionVariableWaiter;
|
|
350
|
+
|
|
351
|
+
static VALUE atomic_ruby_condition_variable_waiter_new(VALUE thread) {
|
|
352
|
+
atomic_ruby_condition_variable_waiter_t *atomic_ruby_condition_variable_waiter;
|
|
353
|
+
VALUE obj = TypedData_Make_Struct(rb_cAtomicConditionVariableWaiter, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, atomic_ruby_condition_variable_waiter);
|
|
354
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_condition_variable_waiter->prev, Qnil);
|
|
355
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_condition_variable_waiter->next, Qnil);
|
|
356
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_condition_variable_waiter->thread, thread);
|
|
357
|
+
return obj;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
typedef struct {
|
|
361
|
+
volatile VALUE head;
|
|
362
|
+
volatile VALUE tail;
|
|
363
|
+
volatile rb_atomic_t count;
|
|
364
|
+
} atomic_ruby_condition_variable_t;
|
|
365
|
+
|
|
366
|
+
static void atomic_ruby_condition_variable_mark(void *ptr) {
|
|
367
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable = (atomic_ruby_condition_variable_t *)ptr;
|
|
368
|
+
rb_gc_mark_movable(atomic_ruby_condition_variable->head);
|
|
369
|
+
rb_gc_mark_movable(atomic_ruby_condition_variable->tail);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
static void atomic_ruby_condition_variable_free(void *ptr) {
|
|
373
|
+
xfree(ptr);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
static size_t atomic_ruby_condition_variable_memsize(const void *ptr) {
|
|
377
|
+
return sizeof(atomic_ruby_condition_variable_t);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static void atomic_ruby_condition_variable_compact(void *ptr) {
|
|
381
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable = (atomic_ruby_condition_variable_t *)ptr;
|
|
382
|
+
atomic_ruby_condition_variable->head = rb_gc_location(atomic_ruby_condition_variable->head);
|
|
383
|
+
atomic_ruby_condition_variable->tail = rb_gc_location(atomic_ruby_condition_variable->tail);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
static const rb_data_type_t atomic_ruby_condition_variable_type = {
|
|
387
|
+
.wrap_struct_name = "AtomicRuby::AtomicConditionVariable",
|
|
388
|
+
.function = {
|
|
389
|
+
.dmark = atomic_ruby_condition_variable_mark,
|
|
390
|
+
.dfree = atomic_ruby_condition_variable_free,
|
|
391
|
+
.dsize = atomic_ruby_condition_variable_memsize,
|
|
392
|
+
.dcompact = atomic_ruby_condition_variable_compact
|
|
393
|
+
},
|
|
394
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
static VALUE rb_cAtomicConditionVariable_allocate(VALUE klass) {
|
|
398
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable;
|
|
399
|
+
VALUE obj = TypedData_Make_Struct(klass, atomic_ruby_condition_variable_t, &atomic_ruby_condition_variable_type, atomic_ruby_condition_variable);
|
|
400
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_condition_variable->head, Qnil);
|
|
401
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_condition_variable->tail, Qnil);
|
|
402
|
+
atomic_ruby_condition_variable->count = 0;
|
|
403
|
+
return obj;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
static VALUE rb_cAtomicConditionVariable_initialize(VALUE self) {
|
|
407
|
+
return self;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static VALUE rb_cAtomicConditionVariable_add_waiter(VALUE self, VALUE thread) {
|
|
411
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable;
|
|
412
|
+
TypedData_Get_Struct(self, atomic_ruby_condition_variable_t, &atomic_ruby_condition_variable_type, atomic_ruby_condition_variable);
|
|
413
|
+
|
|
414
|
+
VALUE new_waiter = atomic_ruby_condition_variable_waiter_new(thread);
|
|
415
|
+
atomic_ruby_condition_variable_waiter_t *new_waiter_data;
|
|
416
|
+
TypedData_Get_Struct(new_waiter, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, new_waiter_data);
|
|
417
|
+
|
|
418
|
+
VALUE tail = atomic_ruby_condition_variable->tail;
|
|
419
|
+
if (tail == Qnil) {
|
|
420
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->head, new_waiter);
|
|
421
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->tail, new_waiter);
|
|
422
|
+
} else {
|
|
423
|
+
atomic_ruby_condition_variable_waiter_t *tail_data;
|
|
424
|
+
TypedData_Get_Struct(tail, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, tail_data);
|
|
425
|
+
RB_OBJ_WRITE(tail, &tail_data->next, new_waiter);
|
|
426
|
+
RB_OBJ_WRITE(new_waiter, &new_waiter_data->prev, tail);
|
|
427
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->tail, new_waiter);
|
|
428
|
+
}
|
|
429
|
+
RUBY_ATOMIC_INC(atomic_ruby_condition_variable->count);
|
|
430
|
+
|
|
431
|
+
return new_waiter;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static VALUE rb_cAtomicConditionVariable_remove_waiter(VALUE self, VALUE waiter) {
|
|
435
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable;
|
|
436
|
+
TypedData_Get_Struct(self, atomic_ruby_condition_variable_t, &atomic_ruby_condition_variable_type, atomic_ruby_condition_variable);
|
|
437
|
+
atomic_ruby_condition_variable_waiter_t *waiter_data;
|
|
438
|
+
TypedData_Get_Struct(waiter, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, waiter_data);
|
|
439
|
+
|
|
440
|
+
VALUE prev = waiter_data->prev;
|
|
441
|
+
VALUE next = waiter_data->next;
|
|
442
|
+
|
|
443
|
+
if (prev == Qnil && next == Qnil && atomic_ruby_condition_variable->head != waiter) return Qnil;
|
|
444
|
+
|
|
445
|
+
if (prev == Qnil) {
|
|
446
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->head, next);
|
|
447
|
+
} else {
|
|
448
|
+
atomic_ruby_condition_variable_waiter_t *prev_data;
|
|
449
|
+
TypedData_Get_Struct(prev, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, prev_data);
|
|
450
|
+
RB_OBJ_WRITE(prev, &prev_data->next, next);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (next == Qnil) {
|
|
454
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->tail, prev);
|
|
455
|
+
} else {
|
|
456
|
+
atomic_ruby_condition_variable_waiter_t *next_data;
|
|
457
|
+
TypedData_Get_Struct(next, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, next_data);
|
|
458
|
+
RB_OBJ_WRITE(next, &next_data->prev, prev);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
RB_OBJ_WRITE(waiter, &waiter_data->prev, Qnil);
|
|
462
|
+
RB_OBJ_WRITE(waiter, &waiter_data->next, Qnil);
|
|
463
|
+
RUBY_ATOMIC_DEC(atomic_ruby_condition_variable->count);
|
|
464
|
+
|
|
465
|
+
return Qnil;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
static VALUE rb_cAtomicConditionVariable_shift_thread(VALUE self) {
|
|
469
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable;
|
|
470
|
+
TypedData_Get_Struct(self, atomic_ruby_condition_variable_t, &atomic_ruby_condition_variable_type, atomic_ruby_condition_variable);
|
|
471
|
+
|
|
472
|
+
VALUE head = atomic_ruby_condition_variable->head;
|
|
473
|
+
if (head == Qnil) return Qnil;
|
|
474
|
+
|
|
475
|
+
atomic_ruby_condition_variable_waiter_t *head_data;
|
|
476
|
+
TypedData_Get_Struct(head, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, head_data);
|
|
477
|
+
VALUE thread = head_data->thread;
|
|
478
|
+
VALUE next = head_data->next;
|
|
479
|
+
|
|
480
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->head, next);
|
|
481
|
+
if (next == Qnil) {
|
|
482
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->tail, Qnil);
|
|
483
|
+
} else {
|
|
484
|
+
atomic_ruby_condition_variable_waiter_t *next_data;
|
|
485
|
+
TypedData_Get_Struct(next, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, next_data);
|
|
486
|
+
RB_OBJ_WRITE(next, &next_data->prev, Qnil);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
RB_OBJ_WRITE(head, &head_data->prev, Qnil);
|
|
490
|
+
RB_OBJ_WRITE(head, &head_data->next, Qnil);
|
|
491
|
+
RUBY_ATOMIC_DEC(atomic_ruby_condition_variable->count);
|
|
492
|
+
|
|
493
|
+
return thread;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
static VALUE rb_cAtomicConditionVariable_drain_threads(VALUE self) {
|
|
497
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable;
|
|
498
|
+
TypedData_Get_Struct(self, atomic_ruby_condition_variable_t, &atomic_ruby_condition_variable_type, atomic_ruby_condition_variable);
|
|
499
|
+
|
|
500
|
+
VALUE threads = rb_ary_new();
|
|
501
|
+
VALUE current = atomic_ruby_condition_variable->head;
|
|
502
|
+
while (current != Qnil) {
|
|
503
|
+
atomic_ruby_condition_variable_waiter_t *waiter_data;
|
|
504
|
+
TypedData_Get_Struct(current, atomic_ruby_condition_variable_waiter_t, &atomic_ruby_condition_variable_waiter_type, waiter_data);
|
|
505
|
+
rb_ary_push(threads, waiter_data->thread);
|
|
506
|
+
VALUE next = waiter_data->next;
|
|
507
|
+
RB_OBJ_WRITE(current, &waiter_data->prev, Qnil);
|
|
508
|
+
RB_OBJ_WRITE(current, &waiter_data->next, Qnil);
|
|
509
|
+
current = next;
|
|
510
|
+
}
|
|
511
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->head, Qnil);
|
|
512
|
+
RB_OBJ_WRITE(self, &atomic_ruby_condition_variable->tail, Qnil);
|
|
513
|
+
atomic_ruby_condition_variable->count = 0;
|
|
514
|
+
|
|
515
|
+
return threads;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
static VALUE rb_cAtomicConditionVariable_waiter_count(VALUE self) {
|
|
519
|
+
atomic_ruby_condition_variable_t *atomic_ruby_condition_variable;
|
|
520
|
+
TypedData_Get_Struct(self, atomic_ruby_condition_variable_t, &atomic_ruby_condition_variable_type, atomic_ruby_condition_variable);
|
|
521
|
+
return UINT2NUM((unsigned int)RUBY_ATOMIC_LOAD(atomic_ruby_condition_variable->count));
|
|
522
|
+
}
|
|
523
|
+
|
|
125
524
|
RUBY_FUNC_EXPORTED void Init_atomic_ruby(void) {
|
|
126
525
|
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
127
526
|
rb_ext_ractor_safe(true);
|
|
@@ -141,4 +540,27 @@ RUBY_FUNC_EXPORTED void Init_atomic_ruby(void) {
|
|
|
141
540
|
#else
|
|
142
541
|
rb_define_const(rb_mAtomicRuby, "RACTOR_SAFE", Qfalse);
|
|
143
542
|
#endif
|
|
543
|
+
|
|
544
|
+
VALUE rb_cAtomicQueue = rb_define_class_under(rb_mAtomicRuby, "AtomicQueue", rb_cObject);
|
|
545
|
+
rb_cAtomicQueueNode = rb_define_class_under(rb_cAtomicQueue, "Node", rb_cObject);
|
|
546
|
+
rb_undef_alloc_func(rb_cAtomicQueueNode);
|
|
547
|
+
|
|
548
|
+
rb_define_alloc_func(rb_cAtomicQueue, rb_cAtomicQueue_allocate);
|
|
549
|
+
rb_define_private_method(rb_cAtomicQueue, "_initialize", rb_cAtomicQueue_initialize, 0);
|
|
550
|
+
rb_define_private_method(rb_cAtomicQueue, "_push", rb_cAtomicQueue_push, 1);
|
|
551
|
+
rb_define_private_method(rb_cAtomicQueue, "_pop", rb_cAtomicQueue_pop, 0);
|
|
552
|
+
rb_define_private_method(rb_cAtomicQueue, "_size", rb_cAtomicQueue_size, 0);
|
|
553
|
+
rb_define_private_method(rb_cAtomicQueue, "_empty_p", rb_cAtomicQueue_empty_p, 0);
|
|
554
|
+
|
|
555
|
+
VALUE rb_cAtomicConditionVariable = rb_define_class_under(rb_mAtomicRuby, "AtomicConditionVariable", rb_cObject);
|
|
556
|
+
rb_cAtomicConditionVariableWaiter = rb_define_class_under(rb_cAtomicConditionVariable, "Waiter", rb_cObject);
|
|
557
|
+
rb_undef_alloc_func(rb_cAtomicConditionVariableWaiter);
|
|
558
|
+
|
|
559
|
+
rb_define_alloc_func(rb_cAtomicConditionVariable, rb_cAtomicConditionVariable_allocate);
|
|
560
|
+
rb_define_private_method(rb_cAtomicConditionVariable, "_initialize", rb_cAtomicConditionVariable_initialize, 0);
|
|
561
|
+
rb_define_private_method(rb_cAtomicConditionVariable, "_add_waiter", rb_cAtomicConditionVariable_add_waiter, 1);
|
|
562
|
+
rb_define_private_method(rb_cAtomicConditionVariable, "_remove_waiter", rb_cAtomicConditionVariable_remove_waiter, 1);
|
|
563
|
+
rb_define_private_method(rb_cAtomicConditionVariable, "_shift_thread", rb_cAtomicConditionVariable_shift_thread, 0);
|
|
564
|
+
rb_define_private_method(rb_cAtomicConditionVariable, "_drain_threads", rb_cAtomicConditionVariable_drain_threads, 0);
|
|
565
|
+
rb_define_private_method(rb_cAtomicConditionVariable, "_waiter_count", rb_cAtomicConditionVariable_waiter_count, 0);
|
|
144
566
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "atomic_ruby/atomic_ruby"
|
|
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. The set of parked threads is tracked
|
|
12
|
+
# in a native doubly-linked list, so registering a waiter, removing a
|
|
13
|
+
# waiter, and shifting the head off for a signal are all O(1) and
|
|
14
|
+
# allocation-light. Parking still uses `Thread.stop` and
|
|
15
|
+
# `Thread#wakeup`, which are the standard kernel-level primitives Ruby
|
|
16
|
+
# exposes for sleeping a thread.
|
|
17
|
+
#
|
|
18
|
+
# The lost-wakeup race in a naive `check-then-park` consumer is avoided
|
|
19
|
+
# by the {#wait} contract: a waiter registers itself before re-evaluating
|
|
20
|
+
# the predicate, so any signal that fires after the producer makes the
|
|
21
|
+
# predicate true is guaranteed to see the waiter and wake it. Ruby also
|
|
22
|
+
# remembers pending wakeups across `Thread.stop`, so a wakeup that
|
|
23
|
+
# arrives between the predicate check and the actual park is not lost.
|
|
24
|
+
#
|
|
25
|
+
# @example Basic usage
|
|
26
|
+
# condvar = AtomicConditionVariable.new
|
|
27
|
+
# ready = AtomicBoolean.new(false)
|
|
28
|
+
#
|
|
29
|
+
# waiter = Thread.new do
|
|
30
|
+
# condvar.wait { ready.true? }
|
|
31
|
+
# puts "ready"
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# ready.make_true
|
|
35
|
+
# condvar.signal
|
|
36
|
+
#
|
|
37
|
+
# @example Worker loop draining an atomic queue
|
|
38
|
+
# condvar.wait do
|
|
39
|
+
# work = queue.pop
|
|
40
|
+
# work || shutdown.true?
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# @note This class is NOT Ractor-safe as it parks `Thread` references,
|
|
44
|
+
# which cannot be shared across ractors.
|
|
45
|
+
class AtomicConditionVariable
|
|
46
|
+
# Creates a new condition variable with no parked threads.
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# condvar = AtomicConditionVariable.new
|
|
50
|
+
#
|
|
51
|
+
# @rbs () -> void
|
|
52
|
+
def initialize
|
|
53
|
+
_initialize
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the number of currently parked waiters.
|
|
57
|
+
#
|
|
58
|
+
# This operation is atomic and thread-safe. The returned value reflects
|
|
59
|
+
# the state at the time of the call, but may change immediately after
|
|
60
|
+
# in concurrent environments.
|
|
61
|
+
#
|
|
62
|
+
# @return [Integer] The number of currently parked waiters
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# condvar = AtomicConditionVariable.new
|
|
66
|
+
# puts condvar.waiter_count #=> 0
|
|
67
|
+
#
|
|
68
|
+
# @rbs () -> Integer
|
|
69
|
+
def waiter_count
|
|
70
|
+
_waiter_count
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Wakes one parked waiter, or no-ops if none are parked.
|
|
74
|
+
#
|
|
75
|
+
# If a waiter has registered itself but is not yet inside `Thread.stop`,
|
|
76
|
+
# Ruby remembers the wakeup and the next `Thread.stop` returns
|
|
77
|
+
# immediately.
|
|
78
|
+
#
|
|
79
|
+
# @return [true, false] true if a waiter was signalled, false otherwise
|
|
80
|
+
#
|
|
81
|
+
# @example
|
|
82
|
+
# condvar = AtomicConditionVariable.new
|
|
83
|
+
# condvar.signal #=> false
|
|
84
|
+
#
|
|
85
|
+
# @rbs () -> bool
|
|
86
|
+
def signal
|
|
87
|
+
thread = _shift_thread
|
|
88
|
+
return false unless thread
|
|
89
|
+
|
|
90
|
+
thread.wakeup rescue nil
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Wakes every parked waiter.
|
|
95
|
+
#
|
|
96
|
+
# Each woken thread observes the wake the same way as with {#signal}.
|
|
97
|
+
#
|
|
98
|
+
# @return [Integer] The number of waiters signalled
|
|
99
|
+
#
|
|
100
|
+
# @example
|
|
101
|
+
# condvar = AtomicConditionVariable.new
|
|
102
|
+
# condvar.broadcast #=> 0
|
|
103
|
+
#
|
|
104
|
+
# @rbs () -> Integer
|
|
105
|
+
def broadcast
|
|
106
|
+
threads = _drain_threads
|
|
107
|
+
threads.each { |thread| thread.wakeup rescue nil }
|
|
108
|
+
threads.size
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Blocks until the given block returns a truthy value, then returns that
|
|
112
|
+
# value.
|
|
113
|
+
#
|
|
114
|
+
# The block is evaluated optimistically first. If it returns truthy on
|
|
115
|
+
# that pass, no waiter is registered and the call returns immediately.
|
|
116
|
+
# Otherwise the calling thread registers itself, re-evaluates the
|
|
117
|
+
# block, and parks via `Thread.stop` until a {#signal} or {#broadcast}
|
|
118
|
+
# wakes it. The block may run more than once and may run concurrently
|
|
119
|
+
# with a signalling thread.
|
|
120
|
+
#
|
|
121
|
+
# @yieldreturn [untyped] Truthy to wake, falsy to keep waiting
|
|
122
|
+
# @return [untyped] The first truthy value returned by the block
|
|
123
|
+
#
|
|
124
|
+
# @example Simple wait
|
|
125
|
+
# condvar = AtomicConditionVariable.new
|
|
126
|
+
# ready = AtomicBoolean.new(false)
|
|
127
|
+
#
|
|
128
|
+
# Thread.new do
|
|
129
|
+
# sleep(1)
|
|
130
|
+
# ready.make_true
|
|
131
|
+
# condvar.signal
|
|
132
|
+
# end
|
|
133
|
+
#
|
|
134
|
+
# condvar.wait { ready.true? } #=> true
|
|
135
|
+
#
|
|
136
|
+
# @rbs () { () -> untyped } -> untyped
|
|
137
|
+
def wait
|
|
138
|
+
result = yield
|
|
139
|
+
return result if result
|
|
140
|
+
|
|
141
|
+
self_thread = Thread.current
|
|
142
|
+
loop do
|
|
143
|
+
waiter = _add_waiter(self_thread)
|
|
144
|
+
result = yield
|
|
145
|
+
if result
|
|
146
|
+
_remove_waiter(waiter)
|
|
147
|
+
return result
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
Thread.stop
|
|
151
|
+
_remove_waiter(waiter)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
AtomicConditionVariable = AtomicRuby::AtomicConditionVariable
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "atomic_ruby/atomic_ruby"
|
|
5
|
+
|
|
6
|
+
module AtomicRuby
|
|
7
|
+
# Provides a lock-free FIFO queue using atomic operations.
|
|
8
|
+
#
|
|
9
|
+
# AtomicQueue is a multi-producer, multi-consumer queue backed by a
|
|
10
|
+
# singly-linked list of nodes with atomic head and tail pointers.
|
|
11
|
+
# Enqueueing and dequeueing use compare-and-swap operations so
|
|
12
|
+
# concurrent producers and consumers never block one another.
|
|
13
|
+
#
|
|
14
|
+
# The queue is implemented as a Michael-Scott lock-free FIFO with a
|
|
15
|
+
# dummy sentinel node. Both {#push} and {#pop} are O(1). Nodes are
|
|
16
|
+
# Ruby-managed values, so the garbage collector reclaims dequeued
|
|
17
|
+
# nodes once no thread holds a reference to them.
|
|
18
|
+
#
|
|
19
|
+
# @example Basic usage
|
|
20
|
+
# queue = AtomicQueue.new
|
|
21
|
+
# queue.push(1)
|
|
22
|
+
# queue.push(2)
|
|
23
|
+
# puts queue.pop #=> 1
|
|
24
|
+
# puts queue.pop #=> 2
|
|
25
|
+
# puts queue.pop #=> nil
|
|
26
|
+
#
|
|
27
|
+
# @example Producer/consumer coordination
|
|
28
|
+
# queue = AtomicQueue.new
|
|
29
|
+
# producers = 4.times.map do
|
|
30
|
+
# Thread.new { 100.times { |index| queue.push(index) } }
|
|
31
|
+
# end
|
|
32
|
+
# producers.each(&:join)
|
|
33
|
+
# puts queue.size #=> 400
|
|
34
|
+
#
|
|
35
|
+
# @note This class is NOT Ractor-safe as it stores mutable references
|
|
36
|
+
# to queued values that cannot be safely shared across ractors.
|
|
37
|
+
class AtomicQueue
|
|
38
|
+
# Creates a new empty queue.
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# queue = AtomicQueue.new
|
|
42
|
+
#
|
|
43
|
+
# @rbs () -> void
|
|
44
|
+
def initialize
|
|
45
|
+
_initialize
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Enqueues a value at the tail of the queue.
|
|
49
|
+
#
|
|
50
|
+
# This operation is atomic and thread-safe. Multiple threads may
|
|
51
|
+
# push concurrently without blocking one another.
|
|
52
|
+
#
|
|
53
|
+
# @param value [untyped] The value to enqueue
|
|
54
|
+
# @return [self]
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# queue = AtomicQueue.new
|
|
58
|
+
# queue.push(1)
|
|
59
|
+
# queue.push("two")
|
|
60
|
+
# queue.push(nil)
|
|
61
|
+
# puts queue.size #=> 3
|
|
62
|
+
#
|
|
63
|
+
# @rbs (untyped value) -> self
|
|
64
|
+
def push(value)
|
|
65
|
+
_push(value)
|
|
66
|
+
end
|
|
67
|
+
# Alias for {#push}.
|
|
68
|
+
#
|
|
69
|
+
# @rbs (untyped value) -> self
|
|
70
|
+
alias << push
|
|
71
|
+
|
|
72
|
+
# Dequeues the value at the head of the queue, or returns nil when
|
|
73
|
+
# the queue is empty.
|
|
74
|
+
#
|
|
75
|
+
# This operation is atomic and thread-safe. Multiple threads may
|
|
76
|
+
# pop concurrently without blocking one another.
|
|
77
|
+
#
|
|
78
|
+
# @return [untyped, nil] The dequeued value, or nil when the queue is empty
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# queue = AtomicQueue.new
|
|
82
|
+
# queue.push(1)
|
|
83
|
+
# queue.push(2)
|
|
84
|
+
# puts queue.pop #=> 1
|
|
85
|
+
# puts queue.pop #=> 2
|
|
86
|
+
# puts queue.pop #=> nil
|
|
87
|
+
#
|
|
88
|
+
# @rbs () -> untyped
|
|
89
|
+
def pop
|
|
90
|
+
_pop
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns the number of values currently queued.
|
|
94
|
+
#
|
|
95
|
+
# This operation is atomic and thread-safe. The returned value
|
|
96
|
+
# reflects the state at the time of the call, but may change
|
|
97
|
+
# immediately after in concurrent environments.
|
|
98
|
+
#
|
|
99
|
+
# @return [Integer] The number of queued values
|
|
100
|
+
#
|
|
101
|
+
# @example
|
|
102
|
+
# queue = AtomicQueue.new
|
|
103
|
+
# queue.push(1)
|
|
104
|
+
# queue.push(2)
|
|
105
|
+
# puts queue.size #=> 2
|
|
106
|
+
#
|
|
107
|
+
# @rbs () -> Integer
|
|
108
|
+
def size
|
|
109
|
+
_size
|
|
110
|
+
end
|
|
111
|
+
# Alias for {#size}.
|
|
112
|
+
#
|
|
113
|
+
# @rbs () -> Integer
|
|
114
|
+
alias length size
|
|
115
|
+
|
|
116
|
+
# Returns true when the queue is empty.
|
|
117
|
+
#
|
|
118
|
+
# This operation is atomic and thread-safe. The returned value
|
|
119
|
+
# reflects the state at the time of the call, but may change
|
|
120
|
+
# immediately after in concurrent environments.
|
|
121
|
+
#
|
|
122
|
+
# @return [true, false] true when the queue has no values
|
|
123
|
+
#
|
|
124
|
+
# @example
|
|
125
|
+
# queue = AtomicQueue.new
|
|
126
|
+
# puts queue.empty? #=> true
|
|
127
|
+
# queue.push(1)
|
|
128
|
+
# puts queue.empty? #=> false
|
|
129
|
+
#
|
|
130
|
+
# @rbs () -> bool
|
|
131
|
+
def empty?
|
|
132
|
+
_empty_p
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
AtomicQueue = AtomicRuby::AtomicQueue
|
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require_relative "atom"
|
|
5
|
+
require_relative "atomic_boolean"
|
|
6
|
+
require_relative "atomic_condition_variable"
|
|
7
|
+
require_relative "atomic_queue"
|
|
5
8
|
|
|
6
9
|
module AtomicRuby
|
|
7
10
|
# Provides a fixed-size thread pool using atomic operations for work queuing.
|
|
8
11
|
#
|
|
9
12
|
# AtomicThreadPool maintains a fixed number of worker threads that process
|
|
10
|
-
# work items from an
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# The queue is implemented as a two-stack structure (in/out) backed by
|
|
14
|
-
# immutable frozen linked-list nodes. Enqueueing prepends to the `in` stack
|
|
15
|
-
# in O(1); dequeueing pops from the `out` stack in O(1), reversing `in` into
|
|
16
|
-
# `out` only when `out` is exhausted (amortized O(1) per item).
|
|
13
|
+
# work items from an {AtomicQueue}. Both enqueueing and dequeueing are O(1)
|
|
14
|
+
# and lock-free, so concurrent producers and consumers never block one
|
|
15
|
+
# another.
|
|
17
16
|
#
|
|
18
17
|
# @example Basic usage
|
|
19
18
|
# pool = AtomicThreadPool.new(size: 4)
|
|
@@ -85,7 +84,9 @@ module AtomicRuby
|
|
|
85
84
|
@name = name
|
|
86
85
|
@on_error = on_error
|
|
87
86
|
|
|
88
|
-
@
|
|
87
|
+
@queue = AtomicQueue.new
|
|
88
|
+
@shutdown = AtomicBoolean.new(false)
|
|
89
|
+
@work_available = AtomicConditionVariable.new
|
|
89
90
|
@started_thread_count = Atom.new(0)
|
|
90
91
|
@active_thread_count = Atom.new(0)
|
|
91
92
|
@threads = []
|
|
@@ -117,17 +118,10 @@ module AtomicRuby
|
|
|
117
118
|
#
|
|
118
119
|
# @rbs (Proc work) -> void
|
|
119
120
|
def <<(work)
|
|
120
|
-
|
|
121
|
-
if current_state[:shutdown]
|
|
122
|
-
current_state
|
|
123
|
-
else
|
|
124
|
-
new_node = { value: work, next: current_state[:in] }.freeze
|
|
125
|
-
current_state.merge(in: new_node, count: current_state[:count] + 1)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
raise EnqueuedWorkAfterShutdownError if state[:shutdown]
|
|
121
|
+
raise EnqueuedWorkAfterShutdownError if @shutdown.true?
|
|
129
122
|
|
|
130
|
-
@
|
|
123
|
+
@queue.push(work)
|
|
124
|
+
@work_available.signal
|
|
131
125
|
end
|
|
132
126
|
|
|
133
127
|
# Returns the number of currently alive worker threads.
|
|
@@ -168,7 +162,7 @@ module AtomicRuby
|
|
|
168
162
|
#
|
|
169
163
|
# @rbs () -> Integer
|
|
170
164
|
def queue_length
|
|
171
|
-
@
|
|
165
|
+
@queue.size
|
|
172
166
|
end
|
|
173
167
|
# Alias for {#queue_length}.
|
|
174
168
|
#
|
|
@@ -225,17 +219,10 @@ module AtomicRuby
|
|
|
225
219
|
#
|
|
226
220
|
# @rbs () -> void
|
|
227
221
|
def shutdown
|
|
228
|
-
|
|
229
|
-
@state.swap do |current_state|
|
|
230
|
-
if current_state[:shutdown]
|
|
231
|
-
already_shutdown = true
|
|
232
|
-
current_state
|
|
233
|
-
else
|
|
234
|
-
current_state.merge(shutdown: true)
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
return if already_shutdown
|
|
222
|
+
return if @shutdown.true?
|
|
238
223
|
|
|
224
|
+
@shutdown.make_true
|
|
225
|
+
@work_available.broadcast
|
|
239
226
|
@threads.each(&:join)
|
|
240
227
|
end
|
|
241
228
|
|
|
@@ -263,40 +250,26 @@ module AtomicRuby
|
|
|
263
250
|
work = nil
|
|
264
251
|
should_shutdown = false
|
|
265
252
|
|
|
266
|
-
@
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
elsif current_state[:out]
|
|
271
|
-
work = current_state[:out][:value]
|
|
272
|
-
current_state.merge(out: current_state[:out][:next], count: current_state[:count] - 1)
|
|
273
|
-
elsif current_state[:in]
|
|
274
|
-
new_out = reverse_list(current_state[:in])
|
|
275
|
-
work = new_out[:value]
|
|
276
|
-
current_state.merge(in: nil, out: new_out[:next], count: current_state[:count] - 1)
|
|
277
|
-
else
|
|
278
|
-
current_state
|
|
279
|
-
end
|
|
253
|
+
@work_available.wait do
|
|
254
|
+
work = @queue.pop
|
|
255
|
+
should_shutdown = @shutdown.true? && @queue.empty? unless work
|
|
256
|
+
work || should_shutdown
|
|
280
257
|
end
|
|
281
258
|
|
|
282
|
-
if should_shutdown
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
warn err.full_message
|
|
294
|
-
end
|
|
295
|
-
ensure
|
|
296
|
-
@active_thread_count.swap { |current_count| current_count - 1 }
|
|
259
|
+
break if should_shutdown
|
|
260
|
+
|
|
261
|
+
@active_thread_count.swap { |current_count| current_count + 1 }
|
|
262
|
+
begin
|
|
263
|
+
work.call
|
|
264
|
+
rescue => err
|
|
265
|
+
if @on_error
|
|
266
|
+
@on_error.call(err)
|
|
267
|
+
else
|
|
268
|
+
warn "#{thread_name} rescued:"
|
|
269
|
+
warn err.full_message
|
|
297
270
|
end
|
|
298
|
-
|
|
299
|
-
|
|
271
|
+
ensure
|
|
272
|
+
@active_thread_count.swap { |current_count| current_count - 1 }
|
|
300
273
|
end
|
|
301
274
|
end
|
|
302
275
|
end
|
|
@@ -305,22 +278,6 @@ module AtomicRuby
|
|
|
305
278
|
|
|
306
279
|
Thread.pass until @started_thread_count.value == @size
|
|
307
280
|
end
|
|
308
|
-
|
|
309
|
-
# Reverses a linked-list of frozen nodes, returning a new reversed list.
|
|
310
|
-
# Each node in the returned list is a new frozen hash.
|
|
311
|
-
#
|
|
312
|
-
# @param node [Hash, nil] Head of the list to reverse
|
|
313
|
-
# @return [Hash, nil] Head of the reversed list
|
|
314
|
-
#
|
|
315
|
-
# @rbs (Hash? node) -> Hash?
|
|
316
|
-
def reverse_list(node)
|
|
317
|
-
reversed = nil
|
|
318
|
-
while node
|
|
319
|
-
reversed = { value: node[:value], next: reversed }.freeze
|
|
320
|
-
node = node[:next]
|
|
321
|
-
end
|
|
322
|
-
reversed
|
|
323
|
-
end
|
|
324
281
|
end
|
|
325
282
|
end
|
|
326
283
|
|
data/lib/atomic-ruby/version.rb
CHANGED
data/lib/atomic-ruby.rb
CHANGED
|
@@ -3,5 +3,7 @@
|
|
|
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"
|
|
7
|
+
require_relative "atomic-ruby/atomic_queue"
|
|
6
8
|
require_relative "atomic-ruby/atomic_thread_pool"
|
|
7
9
|
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.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Young
|
|
@@ -27,7 +27,9 @@ 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
|
|
32
|
+
- lib/atomic-ruby/atomic_queue.rb
|
|
31
33
|
- lib/atomic-ruby/atomic_thread_pool.rb
|
|
32
34
|
- lib/atomic-ruby/version.rb
|
|
33
35
|
homepage: https://github.com/joshuay03/atomic-ruby
|
|
@@ -50,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
50
52
|
- !ruby/object:Gem::Version
|
|
51
53
|
version: '0'
|
|
52
54
|
requirements: []
|
|
53
|
-
rubygems_version: 4.0.
|
|
55
|
+
rubygems_version: 4.0.16
|
|
54
56
|
specification_version: 4
|
|
55
57
|
summary: Atomic primitives for Ruby
|
|
56
58
|
test_files: []
|