concurrent-ruby 1.3.5 → 1.3.6

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: 14d5a264c6fdc7b6087131a0f0a2fc1ce661d18029440e426e094443eb359ca6
4
- data.tar.gz: 42e7b1b8fb885aec9b7b08488c6682af2743e65ad6dcd731f8c86d34f7768981
3
+ metadata.gz: e27f11f8dd5047c9ca365ca8babf3241defab1d707be5055720d1b905e5aa188
4
+ data.tar.gz: 3c5c5e998d8bd76b3b1faf813b870e779d9546149bb6a88a229659dfd741dd13
5
5
  SHA512:
6
- metadata.gz: 31b6cf4fcc533349681610e74f6261c95e5c664583014d86101578f0380a6889288b29a89314a5da59e92cd422bcd97783c34d99c3e5c21a90db0f0d3fcc57e1
7
- data.tar.gz: 4438de87d8ec3ff1cc6d7d246821002bbb76d5a9ddd8b77ebf4a15c0449f1509d694c4f57936b1e1a69e19f32736f21518fd8e82b8cdfbb99d5e2948976c230e
6
+ metadata.gz: c5cf562bf20ba3f20c5ed974a90b55d5f9f08e70fb4af2b722b296ebc1f2726daf431b7410199f19b310b22da260d85d6508c472ef59356903b51ab1123d4d1d
7
+ data.tar.gz: e187fd07e9e10852e408a6c371be9b734b1821fbc42b7c820440caafa5febef3786cf4fd473bd81f1e28c59e1cb4f177ebeec47ef913ed9445425003022c0a65
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## Current
2
2
 
3
+ ## Release v1.3.6 (13 December 2025)
4
+
5
+ concurrent-ruby:
6
+
7
+ * See the [release notes on GitHub](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.3.6).
8
+
3
9
  ## Release v1.3.5, edge v0.7.2 (15 January 2025)
4
10
 
5
11
  concurrent-ruby:
data/README.md CHANGED
@@ -207,7 +207,7 @@ Deprecated features are still available and bugs are being fixed, but new featur
207
207
  These are available in the `concurrent-ruby-edge` companion gem.
208
208
 
209
209
  These features are under active development and may change frequently. They are expected not to
210
- keep backward compatibility (there may also lack tests and documentation). Semantic versions will
210
+ keep backward compatibility (they may also lack tests and documentation). Semantic versions will
211
211
  be obeyed though. Features developed in `concurrent-ruby-edge` are expected to move to
212
212
  `concurrent-ruby` when final.
213
213
 
@@ -358,7 +358,8 @@ best practice is to depend on `concurrent-ruby` and let users to decide if they
358
358
  * Recent CRuby
359
359
  * JRuby, `rbenv install jruby-9.2.17.0`
360
360
  * Set env variable `CONCURRENT_JRUBY_HOME` to point to it, e.g. `/usr/local/opt/rbenv/versions/jruby-9.2.17.0`
361
- * Install Docker, required for Windows builds
361
+ * Install Docker or Podman, required for Windows builds
362
+ * If `bundle config get path` is set, use `bundle config set --local path.system true` otherwise the `gem name, path: '.'` gems won't be found (Bundler limitation).
362
363
 
363
364
  ### Publishing the Gem
364
365
 
@@ -378,6 +379,7 @@ best practice is to depend on `concurrent-ruby` and let users to decide if they
378
379
  * [Charles Oliver Nutter](https://github.com/headius)
379
380
  * [Ben Sheldon](https://github.com/bensheldon)
380
381
  * [Samuel Williams](https://github.com/ioquatix)
382
+ * [Joshua Young](https://github.com/joshuay03)
381
383
 
382
384
  ### Special Thanks to
383
385
 
@@ -6,6 +6,7 @@ module Concurrent
6
6
  # @!visibility private
7
7
  def self.mutex_owned_per_thread?
8
8
  return false if Concurrent.on_jruby? || Concurrent.on_truffleruby?
9
+ return RUBY_VERSION < "3.0" if Concurrent.on_cruby?
9
10
 
10
11
  mutex = Mutex.new
11
12
  # Lock the mutex:
@@ -0,0 +1,55 @@
1
+ module Concurrent
2
+ module Collection
3
+ # @!visibility private
4
+ # @!macro ruby_timeout_queue
5
+ class RubyTimeoutQueue < ::Queue
6
+ def initialize(*args)
7
+ if RUBY_VERSION >= '3.2'
8
+ raise "#{self.class.name} is not needed on Ruby 3.2 or later, use ::Queue instead"
9
+ end
10
+
11
+ super(*args)
12
+
13
+ @mutex = Mutex.new
14
+ @cond_var = ConditionVariable.new
15
+ end
16
+
17
+ def push(obj)
18
+ @mutex.synchronize do
19
+ super(obj)
20
+ @cond_var.signal
21
+ end
22
+ end
23
+ alias_method :enq, :push
24
+ alias_method :<<, :push
25
+
26
+ def pop(non_block = false, timeout: nil)
27
+ if non_block && timeout
28
+ raise ArgumentError, "can't set a timeout if non_block is enabled"
29
+ end
30
+
31
+ if non_block
32
+ super(true)
33
+ elsif timeout
34
+ @mutex.synchronize do
35
+ deadline = Concurrent.monotonic_time + timeout
36
+ while (now = Concurrent.monotonic_time) < deadline && empty?
37
+ @cond_var.wait(@mutex, deadline - now)
38
+ end
39
+ begin
40
+ return super(true)
41
+ rescue ThreadError
42
+ # still empty
43
+ nil
44
+ end
45
+ end
46
+ else
47
+ super(false)
48
+ end
49
+ end
50
+ alias_method :deq, :pop
51
+ alias_method :shift, :pop
52
+ end
53
+ private_constant :RubyTimeoutQueue
54
+ end
55
+ end
@@ -0,0 +1,18 @@
1
+ module Concurrent
2
+ module Collection
3
+ # @!visibility private
4
+ # @!macro internal_implementation_note
5
+ TimeoutQueueImplementation = if RUBY_VERSION >= '3.2'
6
+ ::Queue
7
+ else
8
+ require 'concurrent/collection/ruby_timeout_queue'
9
+ RubyTimeoutQueue
10
+ end
11
+ private_constant :TimeoutQueueImplementation
12
+
13
+ # @!visibility private
14
+ # @!macro timeout_queue
15
+ class TimeoutQueue < TimeoutQueueImplementation
16
+ end
17
+ end
18
+ end
@@ -81,10 +81,8 @@ module Concurrent
81
81
  # What is being pruned is controlled by the min_threads and idletime
82
82
  # parameters passed at pool creation time
83
83
  #
84
- # This is a no-op on some pool implementation (e.g. the Java one). The Ruby
85
- # pool will auto-prune each time a new job is posted. You will need to call
86
- # this method explicitly in case your application post jobs in bursts (a
87
- # lot of jobs and then nothing for long periods)
84
+ # This is a no-op on all pool implementations as they prune themselves
85
+ # automatically, and has been deprecated.
88
86
 
89
87
  # @!macro thread_pool_executor_public_api
90
88
  #
@@ -46,6 +46,7 @@ if Concurrent.on_jruby?
46
46
  def kill
47
47
  synchronize do
48
48
  @executor.shutdownNow
49
+ wait_for_termination
49
50
  nil
50
51
  end
51
52
  end
@@ -8,6 +8,7 @@ if Concurrent.on_jruby?
8
8
  # @!macro thread_pool_options
9
9
  # @!visibility private
10
10
  class JavaThreadPoolExecutor < JavaExecutorService
11
+ include Concern::Deprecation
11
12
 
12
13
  # @!macro thread_pool_executor_constant_default_max_pool_size
13
14
  DEFAULT_MAX_POOL_SIZE = java.lang.Integer::MAX_VALUE # 2147483647
@@ -100,6 +101,7 @@ if Concurrent.on_jruby?
100
101
 
101
102
  # @!macro thread_pool_executor_method_prune_pool
102
103
  def prune_pool
104
+ deprecated "#prune_pool has no effect and will be removed in the next release."
103
105
  end
104
106
 
105
107
  private
@@ -1,4 +1,5 @@
1
1
  require 'concurrent/executor/ruby_thread_pool_executor'
2
+ require 'concurrent/executor/serial_executor_service'
2
3
 
3
4
  module Concurrent
4
5
 
@@ -6,6 +7,7 @@ module Concurrent
6
7
  # @!macro abstract_executor_service_public_api
7
8
  # @!visibility private
8
9
  class RubySingleThreadExecutor < RubyThreadPoolExecutor
10
+ include SerialExecutorService
9
11
 
10
12
  # @!macro single_thread_executor_method_initialize
11
13
  def initialize(opts = {})
@@ -3,6 +3,7 @@ require 'concurrent/atomic/event'
3
3
  require 'concurrent/concern/logging'
4
4
  require 'concurrent/executor/ruby_executor_service'
5
5
  require 'concurrent/utility/monotonic_time'
6
+ require 'concurrent/collection/timeout_queue'
6
7
 
7
8
  module Concurrent
8
9
 
@@ -10,6 +11,7 @@ module Concurrent
10
11
  # @!macro thread_pool_options
11
12
  # @!visibility private
12
13
  class RubyThreadPoolExecutor < RubyExecutorService
14
+ include Concern::Deprecation
13
15
 
14
16
  # @!macro thread_pool_executor_constant_default_max_pool_size
15
17
  DEFAULT_MAX_POOL_SIZE = 2_147_483_647 # java.lang.Integer::MAX_VALUE
@@ -94,9 +96,28 @@ module Concurrent
94
96
  end
95
97
  end
96
98
 
99
+ # removes the worker if it can be pruned
100
+ #
101
+ # @return [true, false] if the worker was pruned
102
+ #
97
103
  # @!visibility private
98
- def remove_busy_worker(worker)
99
- synchronize { ns_remove_busy_worker worker }
104
+ def prune_worker(worker)
105
+ synchronize do
106
+ if ns_prunable_capacity > 0
107
+ remove_worker worker
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+ end
114
+
115
+ # @!visibility private
116
+ def remove_worker(worker)
117
+ synchronize do
118
+ ns_remove_ready_worker worker
119
+ ns_remove_busy_worker worker
120
+ end
100
121
  end
101
122
 
102
123
  # @!visibility private
@@ -116,7 +137,7 @@ module Concurrent
116
137
 
117
138
  # @!macro thread_pool_executor_method_prune_pool
118
139
  def prune_pool
119
- synchronize { ns_prune_pool }
140
+ deprecated "#prune_pool has no effect and will be removed in next the release, see https://github.com/ruby-concurrency/concurrent-ruby/pull/1082."
120
141
  end
121
142
 
122
143
  private
@@ -146,9 +167,6 @@ module Concurrent
146
167
  @largest_length = 0
147
168
  @workers_counter = 0
148
169
  @ruby_pid = $$ # detects if Ruby has forked
149
-
150
- @gc_interval = opts.fetch(:gc_interval, @idletime / 2.0).to_i # undocumented
151
- @next_gc_time = Concurrent.monotonic_time + @gc_interval
152
170
  end
153
171
 
154
172
  # @!visibility private
@@ -162,12 +180,10 @@ module Concurrent
162
180
 
163
181
  if ns_assign_worker(*args, &task) || ns_enqueue(*args, &task)
164
182
  @scheduled_task_count += 1
183
+ nil
165
184
  else
166
- return fallback_action(*args, &task)
185
+ fallback_action(*args, &task)
167
186
  end
168
-
169
- ns_prune_pool if @next_gc_time < Concurrent.monotonic_time
170
- nil
171
187
  end
172
188
 
173
189
  # @!visibility private
@@ -218,7 +234,7 @@ module Concurrent
218
234
  # @!visibility private
219
235
  def ns_enqueue(*args, &task)
220
236
  return false if @synchronous
221
-
237
+
222
238
  if !ns_limited_queue? || @queue.size < @max_queue
223
239
  @queue << [task, args]
224
240
  true
@@ -265,7 +281,7 @@ module Concurrent
265
281
  end
266
282
  end
267
283
 
268
- # removes a worker which is not in not tracked in @ready
284
+ # removes a worker which is not tracked in @ready
269
285
  #
270
286
  # @!visibility private
271
287
  def ns_remove_busy_worker(worker)
@@ -274,25 +290,27 @@ module Concurrent
274
290
  true
275
291
  end
276
292
 
277
- # try oldest worker if it is idle for enough time, it's returned back at the start
278
- #
279
293
  # @!visibility private
280
- def ns_prune_pool
281
- now = Concurrent.monotonic_time
282
- stopped_workers = 0
283
- while !@ready.empty? && (@pool.size - stopped_workers > @min_length)
284
- worker, last_message = @ready.first
285
- if now - last_message > self.idletime
286
- stopped_workers += 1
287
- @ready.shift
288
- worker << :stop
289
- else break
290
- end
294
+ def ns_remove_ready_worker(worker)
295
+ if index = @ready.index { |rw, _| rw == worker }
296
+ @ready.delete_at(index)
291
297
  end
298
+ true
299
+ end
292
300
 
293
- @next_gc_time = Concurrent.monotonic_time + @gc_interval
301
+ # @return [Integer] number of excess idle workers which can be removed without
302
+ # going below min_length, or all workers if not running
303
+ #
304
+ # @!visibility private
305
+ def ns_prunable_capacity
306
+ if running?
307
+ [@pool.size - @min_length, @ready.size].min
308
+ else
309
+ @pool.size
310
+ end
294
311
  end
295
312
 
313
+ # @!visibility private
296
314
  def ns_reset_if_forked
297
315
  if $$ != @ruby_pid
298
316
  @queue.clear
@@ -312,7 +330,7 @@ module Concurrent
312
330
 
313
331
  def initialize(pool, id)
314
332
  # instance variables accessed only under pool's lock so no need to sync here again
315
- @queue = Queue.new
333
+ @queue = Collection::TimeoutQueue.new
316
334
  @pool = pool
317
335
  @thread = create_worker @queue, pool, pool.idletime
318
336
 
@@ -338,17 +356,22 @@ module Concurrent
338
356
  def create_worker(queue, pool, idletime)
339
357
  Thread.new(queue, pool, idletime) do |my_queue, my_pool, my_idletime|
340
358
  catch(:stop) do
341
- loop do
359
+ prunable = true
342
360
 
343
- case message = my_queue.pop
361
+ loop do
362
+ timeout = prunable && my_pool.running? ? my_idletime : nil
363
+ case message = my_queue.pop(timeout: timeout)
364
+ when nil
365
+ throw :stop if my_pool.prune_worker(self)
366
+ prunable = false
344
367
  when :stop
345
- my_pool.remove_busy_worker(self)
368
+ my_pool.remove_worker(self)
346
369
  throw :stop
347
-
348
370
  else
349
371
  task, args = message
350
372
  run_task my_pool, task, args
351
373
  my_pool.ready_worker(self, Concurrent.monotonic_time)
374
+ prunable = true
352
375
  end
353
376
  end
354
377
  end
@@ -61,6 +61,7 @@ module Concurrent
61
61
  # not running.
62
62
  def kill
63
63
  shutdown
64
+ @timer_executor.kill
64
65
  end
65
66
 
66
67
  private :<<
@@ -122,7 +123,9 @@ module Concurrent
122
123
  def ns_shutdown_execution
123
124
  ns_reset_if_forked
124
125
  @queue.clear
125
- @timer_executor.kill
126
+ @condition.set
127
+ @condition.reset
128
+ @timer_executor.shutdown
126
129
  stopped_event.set
127
130
  end
128
131
 
@@ -10,7 +10,6 @@ require 'concurrent/executor/java_thread_pool_executor'
10
10
  require 'concurrent/executor/ruby_executor_service'
11
11
  require 'concurrent/executor/ruby_single_thread_executor'
12
12
  require 'concurrent/executor/ruby_thread_pool_executor'
13
- require 'concurrent/executor/cached_thread_pool'
14
13
  require 'concurrent/executor/safe_task_executor'
15
14
  require 'concurrent/executor/serial_executor_service'
16
15
  require 'concurrent/executor/serialized_execution'
@@ -9,7 +9,7 @@ module Concurrent
9
9
  # queue of length one, or a special kind of mutable variable.
10
10
  #
11
11
  # On top of the fundamental `#put` and `#take` operations, we also provide a
12
- # `#mutate` that is atomic with respect to operations on the same instance.
12
+ # `#modify` that is atomic with respect to operations on the same instance.
13
13
  # These operations all support timeouts.
14
14
  #
15
15
  # We also support non-blocking operations `#try_put!` and `#try_take!`, a
@@ -87,7 +87,7 @@ module Concurrent
87
87
  @mutex.synchronize do
88
88
  wait_for_full(timeout)
89
89
 
90
- # if we timeoud out we'll still be empty
90
+ # If we timed out we'll still be empty
91
91
  if unlocked_full?
92
92
  yield @value
93
93
  else
@@ -116,10 +116,10 @@ module Concurrent
116
116
  end
117
117
 
118
118
  # Atomically `take`, yield the value to a block for transformation, and then
119
- # `put` the transformed value. Returns the transformed value. A timeout can
119
+ # `put` the transformed value. Returns the pre-transform value. A timeout can
120
120
  # be set to limit the time spent blocked, in which case it returns `TIMEOUT`
121
121
  # if the time is exceeded.
122
- # @return [Object] the transformed value, or `TIMEOUT`
122
+ # @return [Object] the pre-transform value, or `TIMEOUT`
123
123
  def modify(timeout = nil)
124
124
  raise ArgumentError.new('no block given') unless block_given?
125
125
 
@@ -167,7 +167,7 @@ module Concurrent
167
167
  # c2 = p.then(-> reason { raise 'Boom!' })
168
168
  #
169
169
  # c1.wait.state #=> :fulfilled
170
- # c1.value #=> 45
170
+ # c1.value #=> 42
171
171
  # c2.wait.state #=> :rejected
172
172
  # c2.reason #=> #<RuntimeError: Boom!>
173
173
  # ```
@@ -2,6 +2,7 @@ require 'concurrent/collection/copy_on_notify_observer_set'
2
2
  require 'concurrent/concern/dereferenceable'
3
3
  require 'concurrent/concern/observable'
4
4
  require 'concurrent/atomic/atomic_boolean'
5
+ require 'concurrent/atomic/atomic_fixnum'
5
6
  require 'concurrent/executor/executor_service'
6
7
  require 'concurrent/executor/ruby_executor_service'
7
8
  require 'concurrent/executor/safe_task_executor'
@@ -236,6 +237,7 @@ module Concurrent
236
237
  synchronize do
237
238
  if @running.false?
238
239
  @running.make_true
240
+ @age.increment
239
241
  schedule_next_task(@run_now ? 0 : @execution_interval)
240
242
  end
241
243
  end
@@ -309,6 +311,7 @@ module Concurrent
309
311
  @task = Concurrent::SafeTaskExecutor.new(task)
310
312
  @executor = opts[:executor] || Concurrent.global_io_executor
311
313
  @running = Concurrent::AtomicBoolean.new(false)
314
+ @age = Concurrent::AtomicFixnum.new(0)
312
315
  @value = nil
313
316
 
314
317
  self.observers = Collection::CopyOnNotifyObserverSet.new
@@ -328,13 +331,15 @@ module Concurrent
328
331
 
329
332
  # @!visibility private
330
333
  def schedule_next_task(interval = execution_interval)
331
- ScheduledTask.execute(interval, executor: @executor, args: [Concurrent::Event.new], &method(:execute_task))
334
+ ScheduledTask.execute(interval, executor: @executor, args: [Concurrent::Event.new, @age.value], &method(:execute_task))
332
335
  nil
333
336
  end
334
337
 
335
338
  # @!visibility private
336
- def execute_task(completion)
339
+ def execute_task(completion, age_when_scheduled)
337
340
  return nil unless @running.true?
341
+ return nil unless @age.value == age_when_scheduled
342
+
338
343
  start_time = Concurrent.monotonic_time
339
344
  _success, value, reason = @task.execute(self)
340
345
  if completion.try?
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '1.3.5'
2
+ VERSION = '1.3.6'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: concurrent-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerry D'Antonio
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-01-15 00:00:00.000000000 Z
13
+ date: 2025-12-13 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: |
16
16
  Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more.
@@ -82,6 +82,8 @@ files:
82
82
  - lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb
83
83
  - lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb
84
84
  - lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb
85
+ - lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb
86
+ - lib/concurrent-ruby/concurrent/collection/timeout_queue.rb
85
87
  - lib/concurrent-ruby/concurrent/concern/deprecation.rb
86
88
  - lib/concurrent-ruby/concurrent/concern/dereferenceable.rb
87
89
  - lib/concurrent-ruby/concurrent/concern/logging.rb
@@ -181,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
183
  - !ruby/object:Gem::Version
182
184
  version: '0'
183
185
  requirements: []
184
- rubygems_version: 3.3.26
186
+ rubygems_version: 3.3.27
185
187
  signing_key:
186
188
  specification_version: 4
187
189
  summary: Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,