atomic-ruby 0.15.5 → 0.16.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6022f081e7f3e4ce545c40989e5a644beea0697da5d840f645ae3764fbb80e4e
4
- data.tar.gz: 94b4312e55cfbda05127bde7ac74efd98da8708cb9835e718e7c02641f1d241a
3
+ metadata.gz: b17a39b18e427bf9b4467457831fd5acf724af9d86b4ca35a3ef10582726b74e
4
+ data.tar.gz: 3285f13f8e5ef021f7fdbcbdcf52b17ab6ff171fc70106543799c0f09f577979
5
5
  SHA512:
6
- metadata.gz: bbcef6942791f3611e55c7c9806bd5daa4b71dd4bae4bedd255c0d1598e16c0ba50a19ae92423768fca04a4baafd80f464877f60960a7e43be0fc08ed2ffb3a3
7
- data.tar.gz: '0883a4a401963ad0db23713c6ae6917bec1c3bd1cd3bd8ecff069cc63c99b3000a914ee0c9345cd9f2495f88f0d6da6c35a51057b80a56877409ba19ce3c5e4f'
6
+ metadata.gz: 0b99a0e2100707aada701ed45c044cc06b6319bffe5a68ff996b8e536a42e1c1cd0ca36c092e9dfe6e24cfea03dd739cc1338b08926479ba2424d59a6a216221
7
+ data.tar.gz: 51d042574a1c3b9c8eb6b7606eaa3e6512ad97f9dbb72a38f349d40bc14eaa5b271e6953bac796ece002192ebd3831ac28ce6538e70297721eaa724cf12ab3bf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.16.1] - 2026-10-18
4
+
5
+ - Grow `AtomicThreadPool` until Ruby execution uses three quarters of one CPU
6
+
7
+ ## [0.16.0] - 2026-10-18
8
+
9
+ - Add `AtomicThreadPool#wait_for_capacity`
10
+
3
11
  ## [0.15.5] - 2026-09-03
4
12
 
5
13
  - Adapt `AtomicThreadPool` scaling to workload pressure
data/README.md CHANGED
@@ -270,7 +270,7 @@ puts "Atomic Ruby Atomic Bank Account: #{results[2].real.round(6)} seconds"
270
270
 
271
271
  ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
272
272
  concurrent-ruby version: 1.3.8
273
- atomic-ruby version: 0.15.5
273
+ atomic-ruby version: 0.16.1
274
274
 
275
275
  Balances:
276
276
  Synchronized Bank Account Balance: 975
@@ -361,7 +361,7 @@ end
361
361
 
362
362
  ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
363
363
  concurrent-ruby version: 1.3.8
364
- atomic-ruby version: 0.15.5
364
+ atomic-ruby version: 0.16.1
365
365
 
366
366
  Warming up --------------------------------------
367
367
  Synchronized Boolean Toggle 165.000 i/100ms
@@ -444,7 +444,7 @@ end
444
444
  > bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_condition_variable_benchmark.rb
445
445
 
446
446
  ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
447
- atomic-ruby version: 0.15.5
447
+ atomic-ruby version: 0.16.1
448
448
 
449
449
  Warming up --------------------------------------
450
450
  Synchronized Condition Variable Wait/Signal 4.062k i/100ms
@@ -519,7 +519,7 @@ end
519
519
  > bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_queue_benchmark.rb
520
520
 
521
521
  ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
522
- atomic-ruby version: 0.15.5
522
+ atomic-ruby version: 0.16.1
523
523
 
524
524
  Warming up --------------------------------------
525
525
  Synchronized Queue Push/Pop 184.000 i/100ms
@@ -620,17 +620,17 @@ puts "Atomic Ruby Adaptive Thread Pool: #{adaptive_results[1].real.round(6)} sec
620
620
  ```
621
621
  > bundle exec rake clobber && bundle exec rake compile && bundle exec ruby examples/atomic_thread_pool_benchmark.rb
622
622
 
623
- ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
623
+ ruby version: ruby 4.0.7 (2026-09-15 revision 229531a6cf) +YJIT +PRISM [arm64-darwin23]
624
624
  concurrent-ruby version: 1.3.8
625
- atomic-ruby version: 0.15.5
625
+ atomic-ruby version: 0.16.1
626
626
 
627
627
  Fixed Pool Results:
628
- Concurrent Ruby Fixed Thread Pool: 5.05313 seconds
629
- Atomic Ruby Fixed Thread Pool: 4.753026 seconds
628
+ Concurrent Ruby Fixed Thread Pool: 5.147226 seconds
629
+ Atomic Ruby Fixed Thread Pool: 4.743301 seconds
630
630
 
631
631
  Adaptive Pool Results:
632
- Concurrent Ruby IO Thread Pool: 0.221072 seconds
633
- Atomic Ruby Adaptive Thread Pool: 0.233328 seconds
632
+ Concurrent Ruby IO Thread Pool: 0.217971 seconds
633
+ Atomic Ruby Adaptive Thread Pool: 0.227701 seconds
634
634
  ```
635
635
 
636
636
  </details>
@@ -684,7 +684,7 @@ puts "Atomic Ruby Atomic Count Down Latch: #{results[1].total.round(6)} CPU seco
684
684
 
685
685
  ruby version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
686
686
  concurrent-ruby version: 1.3.8
687
- atomic-ruby version: 0.15.5
687
+ atomic-ruby version: 0.16.1
688
688
 
689
689
  Benchmark Results:
690
690
  Concurrent Ruby Count Down Latch: 0.000511 CPU seconds, 2.001465 elapsed seconds
@@ -55,7 +55,8 @@ module AtomicRuby
55
55
  AUTOSCALE_INITIAL_WINDOW = 0.002
56
56
  AUTOSCALE_MAX_WINDOW = 0.05
57
57
  AUTOSCALE_SAMPLES_PER_WINDOW = 5
58
- private_constant :AUTOSCALE_IDLE_TIME, :AUTOSCALE_INITIAL_WINDOW, :AUTOSCALE_MAX_WINDOW, :AUTOSCALE_SAMPLES_PER_WINDOW
58
+ AUTOSCALE_RUBY_CPU_SHARE = 0.75
59
+ private_constant :AUTOSCALE_IDLE_TIME, :AUTOSCALE_INITIAL_WINDOW, :AUTOSCALE_MAX_WINDOW, :AUTOSCALE_SAMPLES_PER_WINDOW, :AUTOSCALE_RUBY_CPU_SHARE
59
60
 
60
61
  class Error < StandardError; end
61
62
 
@@ -118,6 +119,7 @@ module AtomicRuby
118
119
  @queue = AtomicQueue.new
119
120
  @shutdown = AtomicBoolean.new(false)
120
121
  @work_available = AtomicConditionVariable.new
122
+ @capacity_changed = AtomicConditionVariable.new
121
123
  @alive_thread_count = Atom.new(0)
122
124
  @active_thread_count = Atom.new(0)
123
125
  @threads = []
@@ -238,6 +240,25 @@ module AtomicRuby
238
240
  @active_thread_count.value
239
241
  end
240
242
 
243
+ # Blocks until the given block returns a truthy value, checking it again
244
+ # whenever a worker takes or finishes work.
245
+ #
246
+ # Useful for applying backpressure, such as holding off on accepting more
247
+ # work until the pool has capacity for it.
248
+ #
249
+ # @yieldreturn [untyped] Truthy to stop waiting, falsy to keep waiting
250
+ # @return [untyped] The first truthy value returned by the block
251
+ #
252
+ # @example Wait until no work is queued
253
+ # pool = AtomicThreadPool.new(size: 2)
254
+ # 5.times { pool << proc { sleep(0.1) } }
255
+ # pool.wait_for_capacity { pool.queue_length.zero? } #=> true
256
+ #
257
+ # @rbs () { () -> untyped } -> untyped
258
+ def wait_for_capacity(&block)
259
+ @capacity_changed.wait(&block)
260
+ end
261
+
241
262
  # Gracefully shuts down the thread pool.
242
263
  #
243
264
  # This method:
@@ -351,6 +372,7 @@ module AtomicRuby
351
372
  break if should_exit
352
373
 
353
374
  @active_thread_count.swap { |current_count| current_count + 1 }
375
+ @capacity_changed.broadcast unless @capacity_changed.waiter_count.zero?
354
376
  if temporary
355
377
  work_started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
356
378
  running_cpu_time_at_start = @thread_pool_monitor.snapshot[6]
@@ -368,10 +390,11 @@ module AtomicRuby
368
390
  ensure
369
391
  @thread_pool_monitor&.stop_work
370
392
  @active_thread_count.swap { |current_count| current_count - 1 }
393
+ @capacity_changed.broadcast unless @capacity_changed.waiter_count.zero?
371
394
  if temporary && !@queue.empty?
372
395
  elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - work_started_at
373
396
  running_cpu_time = @thread_pool_monitor.snapshot[6] - running_cpu_time_at_start
374
- if running_cpu_time * 2 >= elapsed_time * 1_000_000_000
397
+ if running_cpu_time >= elapsed_time * 1_000_000_000 * AUTOSCALE_RUBY_CPU_SHARE
375
398
  @ruby_cpu_trim_requested.make_true
376
399
  @work_available.broadcast
377
400
  end
@@ -465,8 +488,9 @@ module AtomicRuby
465
488
  #
466
489
  # Requiring workers to spend a majority of their time blocked outside the
467
490
  # GVL recognizes blocking operations. The pool grows while Ruby execution
468
- # uses less than half of one CPU and shrinks when Ruby execution becomes
469
- # the bottleneck, without mistaking OS scheduling delays for GVL contention.
491
+ # uses less than three quarters of one CPU and shrinks when Ruby execution
492
+ # becomes the bottleneck, without mistaking OS scheduling delays for GVL
493
+ # contention.
470
494
  #
471
495
  # @param previous_snapshot [Array<Integer>] previous GVL state snapshot
472
496
  # @param snapshot [Array<Integer>] current GVL state snapshot
@@ -492,7 +516,7 @@ module AtomicRuby
492
516
  total_time = running_time + waiting_time + blocked_time
493
517
  end
494
518
 
495
- ruby_cpu_bound = running_cpu_time * 2 >= elapsed_time * 1_000_000_000
519
+ ruby_cpu_bound = running_cpu_time >= elapsed_time * 1_000_000_000 * AUTOSCALE_RUBY_CPU_SHARE
496
520
  blocking = total_time.positive? && blocked_time * 2 > total_time && !ruby_cpu_bound
497
521
 
498
522
  return :up if blocking && workers.length < @max_size
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtomicRuby
4
- VERSION = "0.15.5"
4
+ VERSION = "0.16.1"
5
5
  end
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.15.5
4
+ version: 0.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
@@ -52,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  requirements: []
55
- rubygems_version: 4.0.16
55
+ rubygems_version: 4.0.20
56
56
  specification_version: 4
57
57
  summary: Atomic primitives for Ruby
58
58
  test_files: []