atomic-ruby 0.15.2 → 0.15.4
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 +132 -37
- data/ext/atomic_ruby/atomic_ruby.c +11 -0
- data/lib/atomic-ruby/atomic_condition_variable.rb +19 -32
- data/lib/atomic-ruby/atomic_count_down_latch.rb +1 -5
- data/lib/atomic-ruby/atomic_thread_pool.rb +21 -23
- data/lib/atomic-ruby/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7c0136cf37fe5133affc1f090fa517636f5bfda0f5f43564a4877eee15bae3f
|
|
4
|
+
data.tar.gz: 0ae3a3aadabcc2bb69621df62188800451289a46032fa57a49c1af9326bcc31e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1786faa43a25379cbe68c9de2372c6168a62ab225436c45bbf5049791c39191e3ed75955ab2f5f6385069ddeb86640aa11ba431ce9f2ab54335005a61bd42092
|
|
7
|
+
data.tar.gz: 704c7e7224c9eec351aa0174f597c10c992f4e1594921b8ca91e9ad2df43a6b3eb2c260b9b25ab2d47f8fb0934afa9739ed5312fb00d5d7519d9283c6bf68170
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.15.4] - 2026-09-01
|
|
4
|
+
|
|
5
|
+
- Keep temporary `AtomicThreadPool` workers alive for five seconds
|
|
6
|
+
- Speed up `AtomicThreadPool` scaling for blocking work
|
|
7
|
+
|
|
8
|
+
## [0.15.3] - 2026-08-31
|
|
9
|
+
|
|
10
|
+
- Prevent enqueues from racing `AtomicThreadPool#shutdown`
|
|
11
|
+
- Remove interrupted waiters from `AtomicConditionVariable`
|
|
12
|
+
- Replace `Thread.pass` busy-wait with `sleep` in `AtomicCountDownLatch#wait`
|
|
13
|
+
|
|
3
14
|
## [0.15.2] - 2026-08-30
|
|
4
15
|
|
|
5
16
|
- Avoid thread event hook overhead for fixed thread pools
|
data/README.md
CHANGED
|
@@ -267,8 +267,8 @@ puts "Atomic Ruby Atomic Bank Account: #{results[2].real.round(6)} seconds"
|
|
|
267
267
|
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atom_benchmark.rb
|
|
268
268
|
|
|
269
269
|
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
270
|
-
concurrent-ruby version: 1.3.
|
|
271
|
-
atomic-ruby version: 0.15.
|
|
270
|
+
concurrent-ruby version: 1.3.8
|
|
271
|
+
atomic-ruby version: 0.15.4
|
|
272
272
|
|
|
273
273
|
Balances:
|
|
274
274
|
Synchronized Bank Account Balance: 975
|
|
@@ -276,9 +276,9 @@ Concurrent Ruby Atomic Bank Account Balance: 975
|
|
|
276
276
|
Atomic Ruby Atomic Bank Account Balance: 975
|
|
277
277
|
|
|
278
278
|
Benchmark Results:
|
|
279
|
-
Synchronized Bank Account: 5.
|
|
280
|
-
Concurrent Ruby Atomic Bank Account: 5.
|
|
281
|
-
Atomic Ruby Atomic Bank Account: 5.
|
|
279
|
+
Synchronized Bank Account: 5.104959 seconds
|
|
280
|
+
Concurrent Ruby Atomic Bank Account: 5.120305 seconds
|
|
281
|
+
Atomic Ruby Atomic Bank Account: 5.102732 seconds
|
|
282
282
|
```
|
|
283
283
|
|
|
284
284
|
</details>
|
|
@@ -358,22 +358,22 @@ end
|
|
|
358
358
|
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_boolean_benchmark.rb
|
|
359
359
|
|
|
360
360
|
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
361
|
-
concurrent-ruby version: 1.3.
|
|
362
|
-
atomic-ruby version: 0.15.
|
|
361
|
+
concurrent-ruby version: 1.3.8
|
|
362
|
+
atomic-ruby version: 0.15.4
|
|
363
363
|
|
|
364
364
|
Warming up --------------------------------------
|
|
365
|
-
Synchronized Boolean Toggle
|
|
366
|
-
Concurrent Ruby Atomic Boolean Toggle
|
|
367
|
-
Atomic Ruby Atomic Boolean Toggle
|
|
365
|
+
Synchronized Boolean Toggle 165.000 i/100ms
|
|
366
|
+
Concurrent Ruby Atomic Boolean Toggle 132.000 i/100ms
|
|
367
|
+
Atomic Ruby Atomic Boolean Toggle 119.000 i/100ms
|
|
368
368
|
Calculating -------------------------------------
|
|
369
|
-
Synchronized Boolean Toggle 1.
|
|
370
|
-
Concurrent Ruby Atomic Boolean Toggle 1.
|
|
371
|
-
Atomic Ruby Atomic Boolean Toggle 1.
|
|
369
|
+
Synchronized Boolean Toggle 1.647k (± 2.1%) i/s (606.98 μs/i) - 8.250k in 5.007598s
|
|
370
|
+
Concurrent Ruby Atomic Boolean Toggle 1.343k (± 1.9%) i/s (744.55 μs/i) - 6.732k in 5.012307s
|
|
371
|
+
Atomic Ruby Atomic Boolean Toggle 1.207k (± 1.9%) i/s (828.31 μs/i) - 6.069k in 5.027033s
|
|
372
372
|
|
|
373
373
|
Comparison:
|
|
374
|
-
Synchronized Boolean Toggle:
|
|
375
|
-
Concurrent Ruby Atomic Boolean Toggle:
|
|
376
|
-
Atomic Ruby Atomic Boolean Toggle:
|
|
374
|
+
Synchronized Boolean Toggle: 1647.5 i/s
|
|
375
|
+
Concurrent Ruby Atomic Boolean Toggle: 1343.1 i/s - 1.23x slower
|
|
376
|
+
Atomic Ruby Atomic Boolean Toggle: 1207.3 i/s - 1.36x slower
|
|
377
377
|
```
|
|
378
378
|
|
|
379
379
|
</details>
|
|
@@ -442,18 +442,18 @@ end
|
|
|
442
442
|
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_condition_variable_benchmark.rb
|
|
443
443
|
|
|
444
444
|
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
445
|
-
atomic-ruby version: 0.15.
|
|
445
|
+
atomic-ruby version: 0.15.4
|
|
446
446
|
|
|
447
447
|
Warming up --------------------------------------
|
|
448
|
-
Synchronized Condition Variable Wait/Signal
|
|
449
|
-
Atomic Ruby Atomic Condition Variable Wait/Signal
|
|
448
|
+
Synchronized Condition Variable Wait/Signal 4.062k i/100ms
|
|
449
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal 4.133k i/100ms
|
|
450
450
|
Calculating -------------------------------------
|
|
451
|
-
Synchronized Condition Variable Wait/Signal
|
|
452
|
-
Atomic Ruby Atomic Condition Variable Wait/Signal
|
|
451
|
+
Synchronized Condition Variable Wait/Signal 41.923k (± 3.5%) i/s (23.85 μs/i) - 211.224k in 5.038330s
|
|
452
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal 40.666k (± 3.8%) i/s (24.59 μs/i) - 206.650k in 5.081595s
|
|
453
453
|
|
|
454
454
|
Comparison:
|
|
455
|
-
Synchronized Condition Variable Wait/Signal:
|
|
456
|
-
Atomic Ruby Atomic Condition Variable Wait/Signal:
|
|
455
|
+
Synchronized Condition Variable Wait/Signal: 41923.4 i/s
|
|
456
|
+
Atomic Ruby Atomic Condition Variable Wait/Signal: 40666.4 i/s - same-ish: difference falls within error
|
|
457
457
|
```
|
|
458
458
|
|
|
459
459
|
</details>
|
|
@@ -517,18 +517,18 @@ end
|
|
|
517
517
|
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_queue_benchmark.rb
|
|
518
518
|
|
|
519
519
|
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
520
|
-
atomic-ruby version: 0.15.
|
|
520
|
+
atomic-ruby version: 0.15.4
|
|
521
521
|
|
|
522
522
|
Warming up --------------------------------------
|
|
523
|
-
Synchronized Queue Push/Pop
|
|
524
|
-
Atomic Ruby Atomic Queue Push/Pop
|
|
523
|
+
Synchronized Queue Push/Pop 184.000 i/100ms
|
|
524
|
+
Atomic Ruby Atomic Queue Push/Pop 149.000 i/100ms
|
|
525
525
|
Calculating -------------------------------------
|
|
526
|
-
Synchronized Queue Push/Pop 1.
|
|
527
|
-
Atomic Ruby Atomic Queue Push/Pop 1.
|
|
526
|
+
Synchronized Queue Push/Pop 1.819k (± 2.0%) i/s (549.73 μs/i) - 9.200k in 5.057550s
|
|
527
|
+
Atomic Ruby Atomic Queue Push/Pop 1.493k (± 1.9%) i/s (669.87 μs/i) - 7.599k in 5.090319s
|
|
528
528
|
|
|
529
529
|
Comparison:
|
|
530
|
-
Synchronized Queue Push/Pop:
|
|
531
|
-
Atomic Ruby Atomic Queue Push/Pop:
|
|
530
|
+
Synchronized Queue Push/Pop: 1819.1 i/s
|
|
531
|
+
Atomic Ruby Atomic Queue Push/Pop: 1492.8 i/s - 1.22x slower
|
|
532
532
|
|
|
533
533
|
```
|
|
534
534
|
|
|
@@ -547,7 +547,7 @@ require "benchmark"
|
|
|
547
547
|
require "concurrent-ruby"
|
|
548
548
|
require_relative "../lib/atomic-ruby"
|
|
549
549
|
|
|
550
|
-
|
|
550
|
+
fixed_results = []
|
|
551
551
|
|
|
552
552
|
2.times do |idx|
|
|
553
553
|
result = Benchmark.measure do
|
|
@@ -569,6 +569,101 @@ results = []
|
|
|
569
569
|
pool.wait_for_termination if idx == 0
|
|
570
570
|
end
|
|
571
571
|
|
|
572
|
+
fixed_results << result
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
adaptive_results = []
|
|
576
|
+
|
|
577
|
+
2.times do |idx|
|
|
578
|
+
result = Benchmark.measure do
|
|
579
|
+
pool = case idx
|
|
580
|
+
when 0 then Concurrent.new_io_executor
|
|
581
|
+
when 1 then AtomicThreadPool.new(size: 1, max_size: Float::INFINITY)
|
|
582
|
+
end
|
|
583
|
+
latch = case idx
|
|
584
|
+
when 0 then Concurrent::CountDownLatch.new(100)
|
|
585
|
+
when 1 then AtomicCountDownLatch.new(100)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
100.times do
|
|
589
|
+
pool << proc do
|
|
590
|
+
sleep(0.2)
|
|
591
|
+
latch.count_down
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
latch.wait
|
|
596
|
+
pool.shutdown
|
|
597
|
+
# concurrent-ruby's #shutdown does not wait for threads to terminate
|
|
598
|
+
pool.wait_for_termination if idx == 0
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
adaptive_results << result
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
puts "\n"
|
|
605
|
+
puts "ruby version: #{RUBY_DESCRIPTION}"
|
|
606
|
+
puts "concurrent-ruby version: #{Concurrent::VERSION}"
|
|
607
|
+
puts "atomic-ruby version: #{AtomicRuby::VERSION}"
|
|
608
|
+
puts "\n"
|
|
609
|
+
puts "Fixed Pool Results:"
|
|
610
|
+
puts "Concurrent Ruby Fixed Thread Pool: #{fixed_results[0].real.round(6)} seconds"
|
|
611
|
+
puts "Atomic Ruby Fixed Thread Pool: #{fixed_results[1].real.round(6)} seconds"
|
|
612
|
+
puts "\n"
|
|
613
|
+
puts "Adaptive Pool Results:"
|
|
614
|
+
puts "Concurrent Ruby IO Thread Pool: #{adaptive_results[0].real.round(6)} seconds"
|
|
615
|
+
puts "Atomic Ruby Adaptive Thread Pool: #{adaptive_results[1].real.round(6)} seconds"
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
```
|
|
619
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_thread_pool_benchmark.rb
|
|
620
|
+
|
|
621
|
+
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
622
|
+
concurrent-ruby version: 1.3.8
|
|
623
|
+
atomic-ruby version: 0.15.4
|
|
624
|
+
|
|
625
|
+
Fixed Pool Results:
|
|
626
|
+
Concurrent Ruby Fixed Thread Pool: 5.069464 seconds
|
|
627
|
+
Atomic Ruby Fixed Thread Pool: 4.791382 seconds
|
|
628
|
+
|
|
629
|
+
Adaptive Pool Results:
|
|
630
|
+
Concurrent Ruby IO Thread Pool: 0.22309 seconds
|
|
631
|
+
Atomic Ruby Adaptive Thread Pool: 0.61561 seconds
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
</details>
|
|
635
|
+
|
|
636
|
+
<details>
|
|
637
|
+
|
|
638
|
+
<summary>AtomicCountDownLatch</summary>
|
|
639
|
+
|
|
640
|
+
<br>
|
|
641
|
+
|
|
642
|
+
```ruby
|
|
643
|
+
# frozen_string_literal: true
|
|
644
|
+
|
|
645
|
+
require "benchmark"
|
|
646
|
+
require "concurrent-ruby"
|
|
647
|
+
require_relative "../lib/atomic-ruby"
|
|
648
|
+
|
|
649
|
+
results = []
|
|
650
|
+
|
|
651
|
+
2.times do |idx|
|
|
652
|
+
result = Benchmark.measure do
|
|
653
|
+
latch = case idx
|
|
654
|
+
when 0 then Concurrent::CountDownLatch.new(1)
|
|
655
|
+
when 1 then AtomicCountDownLatch.new(1)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
thread = Thread.new do
|
|
659
|
+
sleep(2)
|
|
660
|
+
latch.count_down
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
latch.wait
|
|
664
|
+
thread.join
|
|
665
|
+
end
|
|
666
|
+
|
|
572
667
|
results << result
|
|
573
668
|
end
|
|
574
669
|
|
|
@@ -578,20 +673,20 @@ puts "concurrent-ruby version: #{Concurrent::VERSION}"
|
|
|
578
673
|
puts "atomic-ruby version: #{AtomicRuby::VERSION}"
|
|
579
674
|
puts "\n"
|
|
580
675
|
puts "Benchmark Results:"
|
|
581
|
-
puts "Concurrent Ruby
|
|
582
|
-
puts "Atomic Ruby Atomic
|
|
676
|
+
puts "Concurrent Ruby Count Down Latch: #{results[0].total.round(6)} CPU seconds, #{results[0].real.round(6)} elapsed seconds"
|
|
677
|
+
puts "Atomic Ruby Atomic Count Down Latch: #{results[1].total.round(6)} CPU seconds, #{results[1].real.round(6)} elapsed seconds"
|
|
583
678
|
```
|
|
584
679
|
|
|
585
680
|
```
|
|
586
|
-
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/
|
|
681
|
+
> bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_count_down_latch_benchmark.rb
|
|
587
682
|
|
|
588
683
|
ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
|
|
589
684
|
concurrent-ruby version: 1.3.8
|
|
590
|
-
atomic-ruby version: 0.15.
|
|
685
|
+
atomic-ruby version: 0.15.4
|
|
591
686
|
|
|
592
687
|
Benchmark Results:
|
|
593
|
-
Concurrent Ruby
|
|
594
|
-
Atomic Ruby Atomic
|
|
688
|
+
Concurrent Ruby Count Down Latch: 0.000511 CPU seconds, 2.001465 elapsed seconds
|
|
689
|
+
Atomic Ruby Atomic Count Down Latch: 0.023529 CPU seconds, 2.00364 elapsed seconds
|
|
595
690
|
```
|
|
596
691
|
|
|
597
692
|
</details>
|
|
@@ -172,6 +172,7 @@ typedef struct {
|
|
|
172
172
|
volatile VALUE head;
|
|
173
173
|
volatile VALUE tail;
|
|
174
174
|
volatile rb_atomic_t count;
|
|
175
|
+
bool closed;
|
|
175
176
|
} atomic_ruby_queue_t;
|
|
176
177
|
|
|
177
178
|
static void atomic_ruby_queue_mark(void *ptr) {
|
|
@@ -211,6 +212,7 @@ static VALUE rb_cAtomicQueue_allocate(VALUE klass) {
|
|
|
211
212
|
RB_OBJ_WRITE(obj, &atomic_ruby_queue->head, Qnil);
|
|
212
213
|
RB_OBJ_WRITE(obj, &atomic_ruby_queue->tail, Qnil);
|
|
213
214
|
atomic_ruby_queue->count = 0;
|
|
215
|
+
atomic_ruby_queue->closed = false;
|
|
214
216
|
return obj;
|
|
215
217
|
}
|
|
216
218
|
|
|
@@ -228,6 +230,7 @@ static VALUE rb_cAtomicQueue_initialize(VALUE self) {
|
|
|
228
230
|
static VALUE rb_cAtomicQueue_push(VALUE self, VALUE value) {
|
|
229
231
|
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
230
232
|
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
233
|
+
if (atomic_ruby_queue->closed) return Qfalse;
|
|
231
234
|
|
|
232
235
|
VALUE new_node = atomic_ruby_queue_node_new(value, Qnil);
|
|
233
236
|
|
|
@@ -259,6 +262,13 @@ static VALUE rb_cAtomicQueue_push(VALUE self, VALUE value) {
|
|
|
259
262
|
return self;
|
|
260
263
|
}
|
|
261
264
|
|
|
265
|
+
static VALUE rb_cAtomicQueue_close(VALUE self) {
|
|
266
|
+
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
267
|
+
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
268
|
+
atomic_ruby_queue->closed = true;
|
|
269
|
+
return Qnil;
|
|
270
|
+
}
|
|
271
|
+
|
|
262
272
|
static VALUE rb_cAtomicQueue_pop(VALUE self) {
|
|
263
273
|
atomic_ruby_queue_t *atomic_ruby_queue;
|
|
264
274
|
TypedData_Get_Struct(self, atomic_ruby_queue_t, &atomic_ruby_queue_type, atomic_ruby_queue);
|
|
@@ -787,6 +797,7 @@ RUBY_FUNC_EXPORTED void Init_atomic_ruby(void) {
|
|
|
787
797
|
rb_define_alloc_func(rb_cAtomicQueue, rb_cAtomicQueue_allocate);
|
|
788
798
|
rb_define_private_method(rb_cAtomicQueue, "_initialize", rb_cAtomicQueue_initialize, 0);
|
|
789
799
|
rb_define_private_method(rb_cAtomicQueue, "_push", rb_cAtomicQueue_push, 1);
|
|
800
|
+
rb_define_private_method(rb_cAtomicQueue, "_close", rb_cAtomicQueue_close, 0);
|
|
790
801
|
rb_define_private_method(rb_cAtomicQueue, "_pop", rb_cAtomicQueue_pop, 0);
|
|
791
802
|
rb_define_private_method(rb_cAtomicQueue, "_size", rb_cAtomicQueue_size, 0);
|
|
792
803
|
rb_define_private_method(rb_cAtomicQueue, "_empty_p", rb_cAtomicQueue_empty_p, 0);
|
|
@@ -8,19 +8,10 @@ module AtomicRuby
|
|
|
8
8
|
#
|
|
9
9
|
# AtomicConditionVariable lets one or more threads park until another
|
|
10
10
|
# thread signals them, without the paired `Mutex` that Ruby's
|
|
11
|
-
# `ConditionVariable` requires.
|
|
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.
|
|
11
|
+
# `ConditionVariable` requires.
|
|
17
12
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
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.
|
|
13
|
+
# Waiters supply a predicate that is checked again after registration,
|
|
14
|
+
# preventing signals sent after a relevant state change from being lost.
|
|
24
15
|
#
|
|
25
16
|
# @example Basic usage
|
|
26
17
|
# condvar = AtomicConditionVariable.new
|
|
@@ -72,10 +63,6 @@ module AtomicRuby
|
|
|
72
63
|
|
|
73
64
|
# Wakes one parked waiter, or no-ops if none are parked.
|
|
74
65
|
#
|
|
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
66
|
# @return [true, false] true if a waiter was signalled, false otherwise
|
|
80
67
|
#
|
|
81
68
|
# @example
|
|
@@ -93,8 +80,6 @@ module AtomicRuby
|
|
|
93
80
|
|
|
94
81
|
# Wakes every parked waiter.
|
|
95
82
|
#
|
|
96
|
-
# Each woken thread observes the wake the same way as with {#signal}.
|
|
97
|
-
#
|
|
98
83
|
# @return [Integer] The number of waiters signalled
|
|
99
84
|
#
|
|
100
85
|
# @example
|
|
@@ -111,12 +96,10 @@ module AtomicRuby
|
|
|
111
96
|
# Blocks until the given block returns a truthy value, then returns that
|
|
112
97
|
# value.
|
|
113
98
|
#
|
|
114
|
-
# The block
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# wakes it. The block may run more than once and may run concurrently
|
|
119
|
-
# with a signalling thread.
|
|
99
|
+
# The block may run more than once and may run concurrently with a
|
|
100
|
+
# signalling thread. Rechecking it after registration prevents lost
|
|
101
|
+
# wakeups. Interrupted waits are unregistered before the exception is
|
|
102
|
+
# propagated.
|
|
120
103
|
#
|
|
121
104
|
# @yieldreturn [untyped] Truthy to wake, falsy to keep waiting
|
|
122
105
|
# @return [untyped] The first truthy value returned by the block
|
|
@@ -140,15 +123,19 @@ module AtomicRuby
|
|
|
140
123
|
|
|
141
124
|
self_thread = Thread.current
|
|
142
125
|
loop do
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
126
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
127
|
+
waiter = _add_waiter(self_thread)
|
|
128
|
+
begin
|
|
129
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
|
130
|
+
result = yield
|
|
131
|
+
return result if result
|
|
149
132
|
|
|
150
|
-
|
|
151
|
-
|
|
133
|
+
Thread.stop
|
|
134
|
+
end
|
|
135
|
+
ensure
|
|
136
|
+
_remove_waiter(waiter)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
152
139
|
end
|
|
153
140
|
end
|
|
154
141
|
end
|
|
@@ -133,10 +133,6 @@ module AtomicRuby
|
|
|
133
133
|
|
|
134
134
|
# Blocks the current thread until the count reaches zero.
|
|
135
135
|
#
|
|
136
|
-
# This method will block the calling thread until other threads have
|
|
137
|
-
# called {#count_down} enough times to reduce the count to zero.
|
|
138
|
-
# The method uses busy-waiting (Thread.pass) to check the count.
|
|
139
|
-
#
|
|
140
136
|
# @return [void]
|
|
141
137
|
#
|
|
142
138
|
# @example Simple wait
|
|
@@ -166,7 +162,7 @@ module AtomicRuby
|
|
|
166
162
|
#
|
|
167
163
|
# @rbs () -> void
|
|
168
164
|
def wait
|
|
169
|
-
|
|
165
|
+
sleep 0.001 while @count.value > 0
|
|
170
166
|
end
|
|
171
167
|
end
|
|
172
168
|
end
|
|
@@ -51,11 +51,10 @@ module AtomicRuby
|
|
|
51
51
|
# @note This class is NOT Ractor-safe as it contains mutable thread state
|
|
52
52
|
# that cannot be safely shared across ractors.
|
|
53
53
|
class AtomicThreadPool
|
|
54
|
-
|
|
55
|
-
AUTOSCALE_IDLE_TIME = 1
|
|
54
|
+
AUTOSCALE_IDLE_TIME = 5
|
|
56
55
|
AUTOSCALE_INTERVAL = 0.01
|
|
57
56
|
AUTOSCALE_WINDOW = 0.05
|
|
58
|
-
private_constant :
|
|
57
|
+
private_constant :AUTOSCALE_IDLE_TIME, :AUTOSCALE_INTERVAL, :AUTOSCALE_WINDOW
|
|
59
58
|
|
|
60
59
|
class Error < StandardError; end
|
|
61
60
|
|
|
@@ -71,7 +70,7 @@ module AtomicRuby
|
|
|
71
70
|
# if work remains queued while its active workers spend most of their time
|
|
72
71
|
# blocked outside the GVL, but not when Ruby execution is using the
|
|
73
72
|
# available CPU.
|
|
74
|
-
# Temporary workers
|
|
73
|
+
# Temporary workers remain available between bursts before the pool returns
|
|
75
74
|
# to its baseline size. Omitting `max_size` creates a fixed-size pool.
|
|
76
75
|
#
|
|
77
76
|
# @param size [Integer] The baseline number of worker threads (must be positive)
|
|
@@ -155,12 +154,13 @@ module AtomicRuby
|
|
|
155
154
|
#
|
|
156
155
|
# @rbs (Proc work) -> void
|
|
157
156
|
def <<(work)
|
|
158
|
-
|
|
157
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
158
|
+
@trim_requested&.make_false
|
|
159
|
+
raise EnqueuedWorkAfterShutdownError unless @queue.push(work)
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
@autoscale_available&.signal
|
|
161
|
+
@work_available.signal
|
|
162
|
+
@autoscale_available&.signal
|
|
163
|
+
end
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
# Returns the number of currently alive worker threads.
|
|
@@ -242,6 +242,9 @@ module AtomicRuby
|
|
|
242
242
|
# 2. Waits for all currently queued work to complete
|
|
243
243
|
# 3. Waits for all worker threads to terminate
|
|
244
244
|
#
|
|
245
|
+
# Enqueues accepted before shutdown are processed before the workers
|
|
246
|
+
# terminate.
|
|
247
|
+
#
|
|
245
248
|
# After shutdown, all worker threads will be terminated and the pool
|
|
246
249
|
# cannot be restarted. Attempting to enqueue work after shutdown
|
|
247
250
|
# will raise an exception.
|
|
@@ -258,11 +261,12 @@ module AtomicRuby
|
|
|
258
261
|
#
|
|
259
262
|
# @rbs () -> void
|
|
260
263
|
def shutdown
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
265
|
+
@queue.send(:_close)
|
|
266
|
+
@shutdown.make_true
|
|
267
|
+
@autoscale_available&.broadcast
|
|
268
|
+
@work_available.broadcast
|
|
269
|
+
end
|
|
266
270
|
@autoscaler&.join
|
|
267
271
|
@threads.each(&:join)
|
|
268
272
|
@thread_pool_monitor&.stop
|
|
@@ -374,7 +378,6 @@ module AtomicRuby
|
|
|
374
378
|
previous_snapshot = @thread_pool_monitor.snapshot
|
|
375
379
|
pressure_started_at = nil
|
|
376
380
|
idle_started_at = nil
|
|
377
|
-
growth_samples = 0
|
|
378
381
|
phase_samples = [0, 0, 0]
|
|
379
382
|
|
|
380
383
|
loop do
|
|
@@ -390,7 +393,6 @@ module AtomicRuby
|
|
|
390
393
|
pressure_started_at = nil
|
|
391
394
|
previous_snapshot = snapshot
|
|
392
395
|
idle_started_at ||= now
|
|
393
|
-
growth_samples = 0
|
|
394
396
|
phase_samples.fill(0)
|
|
395
397
|
|
|
396
398
|
if now - idle_started_at >= AUTOSCALE_IDLE_TIME
|
|
@@ -407,13 +409,9 @@ module AtomicRuby
|
|
|
407
409
|
phase_samples.fill(0)
|
|
408
410
|
elsif now - pressure_started_at >= AUTOSCALE_WINDOW
|
|
409
411
|
if should_grow?(previous_snapshot, snapshot, phase_samples, now - pressure_started_at)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
growth_samples = 0
|
|
414
|
-
end
|
|
415
|
-
else
|
|
416
|
-
growth_samples = 0
|
|
412
|
+
worker_count = @threads.length
|
|
413
|
+
workers_to_add = [worker_count, @queue.size, @max_size - worker_count].min
|
|
414
|
+
workers_to_add.times { spawn_worker(temporary: true) }
|
|
417
415
|
end
|
|
418
416
|
previous_snapshot = snapshot
|
|
419
417
|
pressure_started_at = now
|
data/lib/atomic-ruby/version.rb
CHANGED