concurrent-ruby 1.3.4 → 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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -2
  3. data/README.md +5 -3
  4. data/Rakefile +2 -0
  5. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +1 -1
  6. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +1 -1
  7. data/lib/concurrent-ruby/concurrent/agent.rb +2 -2
  8. data/lib/concurrent-ruby/concurrent/async.rb +1 -1
  9. data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
  10. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +1 -0
  11. data/lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb +55 -0
  12. data/lib/concurrent-ruby/concurrent/collection/timeout_queue.rb +18 -0
  13. data/lib/concurrent-ruby/concurrent/concern/logging.rb +17 -12
  14. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  15. data/lib/concurrent-ruby/concurrent/delay.rb +1 -1
  16. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +2 -4
  17. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +1 -0
  18. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +2 -0
  19. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +2 -0
  20. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +54 -31
  21. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +1 -1
  22. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +4 -1
  23. data/lib/concurrent-ruby/concurrent/executors.rb +0 -1
  24. data/lib/concurrent-ruby/concurrent/map.rb +1 -1
  25. data/lib/concurrent-ruby/concurrent/mvar.rb +4 -4
  26. data/lib/concurrent-ruby/concurrent/promise.rb +2 -2
  27. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +1 -1
  28. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +1 -1
  29. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +1 -1
  30. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +1 -1
  31. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +1 -1
  32. data/lib/concurrent-ruby/concurrent/timer_task.rb +7 -2
  33. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  34. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01c9677c137013ec86d98778a4aa6a9e69bca9b0ae29e923a37254c9cdc515fe
4
- data.tar.gz: 3be169b6d0a11914f85e4a0e1221cd5c122bd59035abe9718892e1a3b4bc690b
3
+ metadata.gz: e27f11f8dd5047c9ca365ca8babf3241defab1d707be5055720d1b905e5aa188
4
+ data.tar.gz: 3c5c5e998d8bd76b3b1faf813b870e779d9546149bb6a88a229659dfd741dd13
5
5
  SHA512:
6
- metadata.gz: 89544dfc5575906cac0f5c57ce5348b944a70a6f77154fb70d0f185b9cc79344a5bb5147a7cf22808e071b4213c80c94e3036b98a2f4102018daf8ba16492db8
7
- data.tar.gz: f084a5a9aa8c6e2ad305fe360f1e8e3b264ad0b45f7add6c04561ff4945cbf0b7c30b2c0d6801be46bc61b3f22f60975e8397973a321268bbf302f4bcb65ab24
6
+ metadata.gz: c5cf562bf20ba3f20c5ed974a90b55d5f9f08e70fb4af2b722b296ebc1f2726daf431b7410199f19b310b22da260d85d6508c472ef59356903b51ab1123d4d1d
7
+ data.tar.gz: e187fd07e9e10852e408a6c371be9b734b1821fbc42b7c820440caafa5febef3786cf4fd473bd81f1e28c59e1cb4f177ebeec47ef913ed9445425003022c0a65
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
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
+
9
+ ## Release v1.3.5, edge v0.7.2 (15 January 2025)
10
+
11
+ concurrent-ruby:
12
+
13
+ * (#1062) Remove dependency on logger.
14
+
15
+ concurrent-ruby-edge:
16
+
17
+ * (#1062) Remove dependency on logger.
18
+
3
19
  ## Release v1.3.4 (10 August 2024)
4
20
 
5
21
  * (#1060) Fix bug with return value of `Concurrent.available_processor_count` when `cpu.cfs_quota_us` is -1.
@@ -296,7 +312,7 @@ concurrent-ruby-edge:
296
312
  * Simplification of `RubySingleThreadExecutor`
297
313
  * `Async` improvements
298
314
  - Each object uses its own `SingleThreadExecutor` instead of the global thread pool.
299
- - No longers supports executor injection
315
+ - No longer supports executor injection
300
316
  - Much better documentation
301
317
  * `Atom` updates
302
318
  - No longer `Dereferenceable`
@@ -471,7 +487,7 @@ Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/iss
471
487
  * Fixed bug with return value of `Concurrent::Actor::Utils::Pool#ask`
472
488
  * Fixed timing bug in `TimerTask`
473
489
  * Fixed bug when creating a `JavaThreadPoolExecutor` with minimum pool size of zero
474
- * Removed confusing warning when not using native extenstions
490
+ * Removed confusing warning when not using native extensions
475
491
  * Improved documentation
476
492
 
477
493
  ## Release v0.7.0 (13 August 2014)
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
 
@@ -284,7 +284,7 @@ To use the tools in the Edge gem it must be required separately:
284
284
  require 'concurrent-edge'
285
285
  ```
286
286
 
287
- If the library does not behave as expected, `Concurrent.use_stdlib_logger(Logger::DEBUG)` could
287
+ If the library does not behave as expected, `Concurrent.use_simple_logger(:DEBUG)` could
288
288
  help to reveal the problem.
289
289
 
290
290
  ## Installation
@@ -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
 
data/Rakefile CHANGED
@@ -12,6 +12,8 @@ ENV['JRUBY_HOME'] = ENV['CONCURRENT_JRUBY_HOME'] if ENV['CONCURRENT_JRUBY_HOME']
12
12
  Rake::JavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext|
13
13
  ext.ext_dir = 'ext/concurrent-ruby'
14
14
  ext.lib_dir = 'lib/concurrent-ruby/concurrent'
15
+ ext.source_version = '8'
16
+ ext.target_version = '8'
15
17
  end
16
18
 
17
19
  if RUBY_ENGINE == 'ruby'
@@ -481,7 +481,7 @@ public class ConcurrentHashMapV8<K, V>
481
481
  *
482
482
  * Maintaining API and serialization compatibility with previous
483
483
  * versions of this class introduces several oddities. Mainly: We
484
- * leave untouched but unused constructor arguments refering to
484
+ * leave untouched but unused constructor arguments referring to
485
485
  * concurrencyLevel. We accept a loadFactor constructor argument,
486
486
  * but apply it only to initial table capacity (which is the only
487
487
  * time that we can guarantee to honor it.) We also declare an
@@ -484,7 +484,7 @@ public class ConcurrentHashMapV8<K, V>
484
484
  *
485
485
  * Maintaining API and serialization compatibility with previous
486
486
  * versions of this class introduces several oddities. Mainly: We
487
- * leave untouched but unused constructor arguments refering to
487
+ * leave untouched but unused constructor arguments referring to
488
488
  * concurrencyLevel. We accept a loadFactor constructor argument,
489
489
  * but apply it only to initial table capacity (which is the only
490
490
  * time that we can guarantee to honor it.) We also declare an
@@ -371,7 +371,7 @@ module Concurrent
371
371
  # @param [Float] timeout the maximum number of seconds to wait
372
372
  # @return [Boolean] true if all actions complete before timeout
373
373
  #
374
- # @raise [Concurrent::TimeoutError] when timout is reached
374
+ # @raise [Concurrent::TimeoutError] when timeout is reached
375
375
  #
376
376
  # @!macro agent_await_warning
377
377
  def await_for!(timeout)
@@ -477,7 +477,7 @@ module Concurrent
477
477
  # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
478
478
  # @return [Boolean] true if all actions complete before timeout
479
479
  #
480
- # @raise [Concurrent::TimeoutError] when timout is reached
480
+ # @raise [Concurrent::TimeoutError] when timeout is reached
481
481
  # @!macro agent_await_warning
482
482
  def await_for!(timeout, *agents)
483
483
  raise Concurrent::TimeoutError unless await_for(timeout, *agents)
@@ -218,7 +218,7 @@ module Concurrent
218
218
 
219
219
  # @!method self.new(*args, &block)
220
220
  #
221
- # Instanciate a new object and ensure proper initialization of the
221
+ # Instantiate a new object and ensure proper initialization of the
222
222
  # synchronization mechanisms.
223
223
  #
224
224
  # @param [Array<Object>] args Zero or more arguments to be passed to the
@@ -113,7 +113,7 @@ module Concurrent
113
113
  # @option opts [Proc] :validator (nil) Optional proc used to validate new
114
114
  # values. It must accept one and only one argument which will be the
115
115
  # intended new value. The validator will return true if the new value
116
- # is acceptable else return false (preferrably) or raise an exception.
116
+ # is acceptable else return false (preferably) or raise an exception.
117
117
  #
118
118
  # @!macro deref_options
119
119
  #
@@ -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
@@ -1,4 +1,3 @@
1
- require 'logger'
2
1
  require 'concurrent/atomic/atomic_reference'
3
2
 
4
3
  module Concurrent
@@ -8,10 +7,12 @@ module Concurrent
8
7
  #
9
8
  # @!visibility private
10
9
  module Logging
11
- include Logger::Severity
10
+ # The same as Logger::Severity but we copy it here to avoid a dependency on the logger gem just for these 7 constants
11
+ DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN = 0, 1, 2, 3, 4, 5
12
+ SEV_LABEL = %w[DEBUG INFO WARN ERROR FATAL ANY].freeze
12
13
 
13
14
  # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger
14
- # @param [Integer] level one of Logger::Severity constants
15
+ # @param [Integer] level one of Concurrent::Concern::Logging constants
15
16
  # @param [String] progname e.g. a path of an Actor
16
17
  # @param [String, nil] message when nil block is used to generate the message
17
18
  # @yieldreturn [String] a message
@@ -23,7 +24,7 @@ module Concurrent
23
24
  end
24
25
  logger.call level, progname, message, &block
25
26
  rescue => error
26
- $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
27
+ $stderr.puts "`Concurrent.global_logger` failed to log #{[level, progname, message, block]}\n" +
27
28
  "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
28
29
  end
29
30
  end
@@ -33,8 +34,10 @@ end
33
34
  module Concurrent
34
35
  extend Concern::Logging
35
36
 
36
- # @return [Logger] Logger with provided level and output.
37
- def self.create_simple_logger(level = Logger::FATAL, output = $stderr)
37
+ # Create a simple logger with provided level and output.
38
+ def self.create_simple_logger(level = :FATAL, output = $stderr)
39
+ level = Concern::Logging.const_get(level) unless level.is_a?(Integer)
40
+
38
41
  # TODO (pitr-ch 24-Dec-2016): figure out why it had to be replaced, stdlogger was deadlocking
39
42
  lambda do |severity, progname, message = nil, &block|
40
43
  return false if severity < level
@@ -52,7 +55,7 @@ module Concurrent
52
55
 
53
56
  output.print format "[%s] %5s -- %s: %s\n",
54
57
  Time.now.strftime('%Y-%m-%d %H:%M:%S.%L'),
55
- Logger::SEV_LABEL[severity],
58
+ Concern::Logging::SEV_LABEL[severity],
56
59
  progname,
57
60
  formatted_message
58
61
  true
@@ -60,13 +63,15 @@ module Concurrent
60
63
  end
61
64
 
62
65
  # Use logger created by #create_simple_logger to log concurrent-ruby messages.
63
- def self.use_simple_logger(level = Logger::FATAL, output = $stderr)
66
+ def self.use_simple_logger(level = :FATAL, output = $stderr)
64
67
  Concurrent.global_logger = create_simple_logger level, output
65
68
  end
66
69
 
67
- # @return [Logger] Logger with provided level and output.
70
+ # Create a stdlib logger with provided level and output.
71
+ # If you use this deprecated method you might need to add logger to your Gemfile to avoid warnings from Ruby 3.3.5+.
68
72
  # @deprecated
69
- def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
73
+ def self.create_stdlib_logger(level = :FATAL, output = $stderr)
74
+ require 'logger'
70
75
  logger = Logger.new(output)
71
76
  logger.level = level
72
77
  logger.formatter = lambda do |severity, datetime, progname, msg|
@@ -93,7 +98,7 @@ module Concurrent
93
98
 
94
99
  # Use logger created by #create_stdlib_logger to log concurrent-ruby messages.
95
100
  # @deprecated
96
- def self.use_stdlib_logger(level = Logger::FATAL, output = $stderr)
101
+ def self.use_stdlib_logger(level = :FATAL, output = $stderr)
97
102
  Concurrent.global_logger = create_stdlib_logger level, output
98
103
  end
99
104
 
@@ -103,7 +108,7 @@ module Concurrent
103
108
  NULL_LOGGER = lambda { |level, progname, message = nil, &block| }
104
109
 
105
110
  # @!visibility private
106
- GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(Logger::WARN))
111
+ GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(:WARN))
107
112
  private_constant :GLOBAL_LOGGER
108
113
 
109
114
  def self.global_logger
@@ -19,7 +19,7 @@ module Concurrent
19
19
  #
20
20
  # When a `Delay` is created its state is set to `pending`. The value and
21
21
  # reason are both `nil`. The first time the `#value` method is called the
22
- # enclosed opration will be run and the calling thread will block. Other
22
+ # enclosed operation will be run and the calling thread will block. Other
23
23
  # threads attempting to call `#value` will block as well. Once the operation
24
24
  # is complete the *value* will be set to the result of the operation or the
25
25
  # *reason* will be set to the raised exception, as appropriate. All threads
@@ -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 explicitely 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
@@ -27,7 +27,7 @@ module Concurrent
27
27
  # is received. This pattern has several issues. The thread itself is highly
28
28
  # susceptible to errors during processing. Also, the thread itself must be
29
29
  # constantly monitored and restarted should it die. `SingleThreadExecutor`
30
- # encapsulates all these bahaviors. The task processor is highly resilient
30
+ # encapsulates all these behaviors. The task processor is highly resilient
31
31
  # to errors from within tasks. Also, should the thread die it will
32
32
  # automatically be restarted.
33
33
  #
@@ -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'
@@ -148,7 +148,7 @@ module Concurrent
148
148
  if value = super # non-falsy value is an existing mapping, return it right away
149
149
  value
150
150
  # re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call
151
- # a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrent +nil+ value
151
+ # a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrect +nil+ value
152
152
  # would be returned)
153
153
  # note: nil == value check is not technically necessary
154
154
  elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL))
@@ -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
 
@@ -103,7 +103,7 @@ module Concurrent
103
103
  # - if parent is *rejected* the child will be *pending* (but will ultimately be *rejected*)
104
104
  #
105
105
  # Promises are executed asynchronously from the main thread. By the time a
106
- # child Promise finishes intialization it may be in a different state than its
106
+ # child Promise finishes initialization it may be in a different state than its
107
107
  # parent (by the time a child is created its parent may have completed
108
108
  # execution and changed state). Despite being asynchronous, however, the order
109
109
  # of execution of Promise objects in a chain (or tree) is strictly defined.
@@ -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
  # ```
@@ -193,7 +193,7 @@ module Concurrent
193
193
  end
194
194
  end
195
195
 
196
- # The `delay` value given at instanciation.
196
+ # The `delay` value given at instantiation.
197
197
  #
198
198
  # @return [Float] the initial delay.
199
199
  def initial_delay
@@ -157,7 +157,7 @@ module Concurrent
157
157
  end
158
158
  end
159
159
  members.each_with_index do |member, index|
160
- clazz.send :remove_method, member if clazz.instance_methods.include? member
160
+ clazz.send :remove_method, member if clazz.instance_methods(false).include? member
161
161
  clazz.send(:define_method, member) do
162
162
  @values[index]
163
163
  end
@@ -58,7 +58,7 @@ module Concurrent
58
58
 
59
59
  # Creates methods for reading and writing to a instance variable with
60
60
  # volatile (Java) semantic as {.attr_volatile} does.
61
- # The instance variable should be accessed oly through generated methods.
61
+ # The instance variable should be accessed only through generated methods.
62
62
  # This method generates following methods: `value`, `value=(new_value) #=> new_value`,
63
63
  # `swap_value(new_value) #=> old_value`,
64
64
  # `compare_and_set_value(expected, value) #=> true || false`, `update_value(&block)`.
@@ -9,7 +9,7 @@ module Concurrent
9
9
  # @!visibility private
10
10
  module Util
11
11
 
12
- # A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8
12
+ # A Ruby port of the Doug Lea's jsr166e.LongAdder class version 1.8
13
13
  # available in public domain.
14
14
  #
15
15
  # Original source code available here:
@@ -15,7 +15,7 @@ module Concurrent
15
15
  # Usage:
16
16
  # x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed
17
17
  # while true
18
- # if (x = XorShiftRandom.xorshift).odd? # thread-localy generate a next random number
18
+ # if (x = XorShiftRandom.xorshift).odd? # thread-locally generate a next random number
19
19
  # do_something_at_random
20
20
  # end
21
21
  # end
@@ -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.4'
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.4
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: 2024-08-10 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,