concurrent-ruby 1.2.3 → 1.3.5

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: 1c6483d36c3c75859c9b38b6ba2ba13577a0bb89b3b725a032d43f3543797099
4
- data.tar.gz: e94ac3e1ba23db29a7bf025736e34a4027ba7a137ae08b493c78ce3756803792
3
+ metadata.gz: 14d5a264c6fdc7b6087131a0f0a2fc1ce661d18029440e426e094443eb359ca6
4
+ data.tar.gz: 42e7b1b8fb885aec9b7b08488c6682af2743e65ad6dcd731f8c86d34f7768981
5
5
  SHA512:
6
- metadata.gz: dc0353e60638a409bf891d663626bd2c1c5f05206c08036326d63e239bfe0ea7e1e3480982c75fe44f9d416a7f76a66a2a2aeef78fc60084ed7ff94c7d3181d9
7
- data.tar.gz: 24bdd3a040528c05061ec15b4057184be96fa933f69f3321755e8583ba8e6175beec48c36f1671cc3dad7e413588c9601c7f0e5b503b3de947dff63c2ba76b1e
6
+ metadata.gz: 31b6cf4fcc533349681610e74f6261c95e5c664583014d86101578f0380a6889288b29a89314a5da59e92cd422bcd97783c34d99c3e5c21a90db0f0d3fcc57e1
7
+ data.tar.gz: 4438de87d8ec3ff1cc6d7d246821002bbb76d5a9ddd8b77ebf4a15c0449f1509d694c4f57936b1e1a69e19f32736f21518fd8e82b8cdfbb99d5e2948976c230e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  ## Current
2
2
 
3
+ ## Release v1.3.5, edge v0.7.2 (15 January 2025)
4
+
5
+ concurrent-ruby:
6
+
7
+ * (#1062) Remove dependency on logger.
8
+
9
+ concurrent-ruby-edge:
10
+
11
+ * (#1062) Remove dependency on logger.
12
+
13
+ ## Release v1.3.4 (10 August 2024)
14
+
15
+ * (#1060) Fix bug with return value of `Concurrent.available_processor_count` when `cpu.cfs_quota_us` is -1.
16
+ * (#1058) Add `Concurrent.cpu_shares` that is cgroups aware.
17
+
18
+ ## Release v1.3.3 (9 June 2024)
19
+
20
+ * (#1053) Improve the speed of `Concurrent.physical_processor_count` on Windows.
21
+
22
+ ## Release v1.3.2, edge v0.7.1 (7 June 2024)
23
+
24
+ concurrent-ruby:
25
+
26
+ * (#1051) Remove dependency on `win32ole`.
27
+
28
+ concurrent-ruby-edge:
29
+
30
+ * (#1052) Fix dependency on `concurrent-ruby` to allow the latest release.
31
+
32
+ ## Release v1.3.1 (29 May 2024)
33
+
34
+ * Release 1.3.0 was broken when pushed to RubyGems. 1.3.1 is a packaging fix.
35
+
36
+ ## Release v1.3.0 (28 May 2024)
37
+
38
+ * (#1042) Align Java Executor Service behavior for `shuttingdown?`, `shutdown?`
39
+ * (#1038) Add `Concurrent.available_processor_count` that is cgroups aware.
40
+
3
41
  ## Release v1.2.3 (16 Jan 2024)
4
42
 
5
43
  * See [the GitHub release](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.2.3) for details.
@@ -268,7 +306,7 @@ concurrent-ruby-edge:
268
306
  * Simplification of `RubySingleThreadExecutor`
269
307
  * `Async` improvements
270
308
  - Each object uses its own `SingleThreadExecutor` instead of the global thread pool.
271
- - No longers supports executor injection
309
+ - No longer supports executor injection
272
310
  - Much better documentation
273
311
  * `Atom` updates
274
312
  - No longer `Dereferenceable`
@@ -443,7 +481,7 @@ Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/iss
443
481
  * Fixed bug with return value of `Concurrent::Actor::Utils::Pool#ask`
444
482
  * Fixed timing bug in `TimerTask`
445
483
  * Fixed bug when creating a `JavaThreadPoolExecutor` with minimum pool size of zero
446
- * Removed confusing warning when not using native extenstions
484
+ * Removed confusing warning when not using native extensions
447
485
  * Improved documentation
448
486
 
449
487
  ## Release v0.7.0 (13 August 2014)
data/Gemfile CHANGED
@@ -1,14 +1,14 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- require File.join(File.dirname(__FILE__), 'lib/concurrent-ruby/concurrent/version')
4
- require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby-edge/concurrent/edge/version')
3
+ version = File.read("#{__dir__}/lib/concurrent-ruby/concurrent/version.rb")[/'(.+)'/, 1] or raise
4
+ edge_version = File.read("#{__dir__}/lib/concurrent-ruby-edge/concurrent/edge/version.rb")[/'(.+)'/, 1] or raise
5
5
 
6
6
  no_path = ENV['NO_PATH']
7
7
  options = no_path ? {} : { path: '.' }
8
8
 
9
- gem 'concurrent-ruby', Concurrent::VERSION, options
10
- gem 'concurrent-ruby-edge', Concurrent::EDGE_VERSION, options
11
- gem 'concurrent-ruby-ext', Concurrent::VERSION, options.merge(platform: :mri)
9
+ gem 'concurrent-ruby', version, options
10
+ gem 'concurrent-ruby-edge', edge_version, options
11
+ gem 'concurrent-ruby-ext', version, options.merge(platform: :mri)
12
12
 
13
13
  group :development do
14
14
  gem 'rake', '~> 13.0'
data/README.md CHANGED
@@ -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
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
- require_relative 'lib/concurrent-ruby/concurrent/version'
2
- require_relative 'lib/concurrent-ruby-edge/concurrent/edge/version'
3
- require_relative 'lib/concurrent-ruby/concurrent/utility/engine'
1
+ version = File.read("#{__dir__}/lib/concurrent-ruby/concurrent/version.rb")[/'(.+)'/, 1] or raise
2
+ edge_version = File.read("#{__dir__}/lib/concurrent-ruby-edge/concurrent/edge/version.rb")[/'(.+)'/, 1] or raise
4
3
 
5
4
  core_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby.gemspec')
6
5
  ext_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby-ext.gemspec')
@@ -8,14 +7,16 @@ edge_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby-edge.
8
7
 
9
8
  require 'rake/javaextensiontask'
10
9
 
11
- ENV['JRUBY_HOME'] = ENV['CONCURRENT_JRUBY_HOME'] if ENV['CONCURRENT_JRUBY_HOME'] && !Concurrent.on_jruby?
10
+ ENV['JRUBY_HOME'] = ENV['CONCURRENT_JRUBY_HOME'] if ENV['CONCURRENT_JRUBY_HOME'] && RUBY_ENGINE != 'jruby'
12
11
 
13
12
  Rake::JavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext|
14
13
  ext.ext_dir = 'ext/concurrent-ruby'
15
14
  ext.lib_dir = 'lib/concurrent-ruby/concurrent'
15
+ ext.source_version = '8'
16
+ ext.target_version = '8'
16
17
  end
17
18
 
18
- unless Concurrent.on_jruby? || Concurrent.on_truffleruby?
19
+ if RUBY_ENGINE == 'ruby'
19
20
  require 'rake/extensiontask'
20
21
 
21
22
  Rake::ExtensionTask.new('concurrent_ruby_ext', ext_gemspec) do |ext|
@@ -28,6 +29,10 @@ unless Concurrent.on_jruby? || Concurrent.on_truffleruby?
28
29
  end
29
30
  end
30
31
 
32
+ def which?(executable)
33
+ !`which #{executable} 2>/dev/null`.empty?
34
+ end
35
+
31
36
  require 'rake_compiler_dock'
32
37
  namespace :repackage do
33
38
  desc '* with Windows fat distributions'
@@ -42,12 +47,19 @@ namespace :repackage do
42
47
  Rake::Task['lib/concurrent-ruby/concurrent/concurrent_ruby.jar'].invoke
43
48
 
44
49
  # build all gem files
50
+ rack_compiler_dock_kwargs = {}
51
+ if which?('podman') and (!which?('docker') || `docker --version`.include?('podman'))
52
+ # podman and only podman available, so RakeCompilerDock will use podman, otherwise it uses docker
53
+ rack_compiler_dock_kwargs = {
54
+ options: ['--privileged'], # otherwise the directory in the image is empty
55
+ runas: false
56
+ }
57
+ end
45
58
  %w[x86-mingw32 x64-mingw32].each do |plat|
46
59
  RakeCompilerDock.sh(
47
60
  "bundle install --local && bundle exec rake native:#{plat} gem --trace",
48
61
  platform: plat,
49
- options: ['--privileged'], # otherwise the directory in the image is empty
50
- runas: false)
62
+ **rack_compiler_dock_kwargs)
51
63
  end
52
64
  end
53
65
  end
@@ -57,7 +69,7 @@ require 'rubygems'
57
69
  require 'rubygems/package_task'
58
70
 
59
71
  Gem::PackageTask.new(core_gemspec) {} if core_gemspec
60
- Gem::PackageTask.new(ext_gemspec) {} if ext_gemspec && !Concurrent.on_jruby?
72
+ Gem::PackageTask.new(ext_gemspec) {} if ext_gemspec && RUBY_ENGINE != 'jruby'
61
73
  Gem::PackageTask.new(edge_gemspec) {} if edge_gemspec
62
74
 
63
75
  CLEAN.include(
@@ -85,9 +97,9 @@ begin
85
97
  task :installed do
86
98
  Bundler.with_original_env do
87
99
  Dir.chdir(__dir__) do
88
- sh "gem install pkg/concurrent-ruby-#{Concurrent::VERSION}.gem"
89
- sh "gem install pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" if Concurrent.on_cruby?
90
- sh "gem install pkg/concurrent-ruby-edge-#{Concurrent::EDGE_VERSION}.gem"
100
+ sh "gem install pkg/concurrent-ruby-#{version}.gem"
101
+ sh "gem install pkg/concurrent-ruby-ext-#{version}.gem" if RUBY_ENGINE == 'ruby'
102
+ sh "gem install pkg/concurrent-ruby-edge-#{edge_version}.gem"
91
103
  ENV['NO_PATH'] = 'true'
92
104
  sh 'bundle update'
93
105
  sh 'bundle exec rake spec:ci'
@@ -117,7 +129,7 @@ rescue LoadError => e
117
129
  puts 'RSpec is not installed, skipping test task definitions: ' + e.message
118
130
  end
119
131
 
120
- current_yard_version_name = Concurrent::VERSION
132
+ current_yard_version_name = version
121
133
 
122
134
  begin
123
135
  require 'yard'
@@ -221,6 +233,8 @@ namespace :release do
221
233
  # Depends on environment of @pitr-ch
222
234
 
223
235
  task :checks do
236
+ raise '$CONCURRENT_JRUBY_HOME must be set' unless ENV['CONCURRENT_JRUBY_HOME']
237
+
224
238
  Dir.chdir(__dir__) do
225
239
  sh 'test -z "$(git status --porcelain)"' do |ok, res|
226
240
  unless ok
@@ -251,15 +265,19 @@ namespace :release do
251
265
 
252
266
  desc '* test actual installed gems instead of cloned repository on MRI and JRuby'
253
267
  task :test do
268
+ raise '$CONCURRENT_JRUBY_HOME must be set' unless ENV['CONCURRENT_JRUBY_HOME']
269
+
254
270
  Dir.chdir(__dir__) do
255
271
  puts "Testing with the installed gem"
256
272
 
257
273
  Bundler.with_original_env do
258
274
  sh 'ruby -v'
275
+ sh 'bundle install'
259
276
  sh 'bundle exec rake spec:installed'
260
277
 
261
- env = { "PATH" => "#{ENV['CONCURRENT_JRUBY_HOME']}/bin:#{ENV['PATH']}" }
278
+ env = { "PATH" => "#{ENV.fetch('CONCURRENT_JRUBY_HOME')}/bin:#{ENV['PATH']}" }
262
279
  sh env, 'ruby -v'
280
+ sh env, 'bundle install'
263
281
  sh env, 'bundle exec rake spec:installed'
264
282
  end
265
283
 
@@ -271,8 +289,8 @@ namespace :release do
271
289
  task :publish => ['publish:ask', 'publish:tag', 'publish:rubygems', 'publish:post_steps']
272
290
 
273
291
  namespace :publish do
274
- publish_base = true
275
- publish_edge = false
292
+ publish_base = nil
293
+ publish_edge = nil
276
294
 
277
295
  task :ask do
278
296
  begin
@@ -280,8 +298,15 @@ namespace :release do
280
298
  input = STDIN.gets.strip.downcase
281
299
  end until %w(y n).include?(input)
282
300
  exit 1 if input == 'n'
301
+
302
+ begin
303
+ STDOUT.puts 'Do you want to publish `concurrent-ruby`? (y/n)'
304
+ input = STDIN.gets.strip.downcase
305
+ end until %w(y n).include?(input)
306
+ publish_base = input == 'y'
307
+
283
308
  begin
284
- STDOUT.puts 'It will publish `concurrent-ruby`. Do you want to publish `concurrent-ruby-edge`? (y/n)'
309
+ STDOUT.puts 'Do you want to publish `concurrent-ruby-edge`? (y/n)'
285
310
  input = STDIN.gets.strip.downcase
286
311
  end until %w(y n).include?(input)
287
312
  publish_edge = input == 'y'
@@ -290,21 +315,21 @@ namespace :release do
290
315
  desc '** tag HEAD with current version and push to github'
291
316
  task :tag => :ask do
292
317
  Dir.chdir(__dir__) do
293
- sh "git tag v#{Concurrent::VERSION}" if publish_base
294
- sh "git push origin v#{Concurrent::VERSION}" if publish_base
295
- sh "git tag edge-v#{Concurrent::EDGE_VERSION}" if publish_edge
296
- sh "git push origin edge-v#{Concurrent::EDGE_VERSION}" if publish_edge
318
+ sh "git tag v#{version}" if publish_base
319
+ sh "git push origin v#{version}" if publish_base
320
+ sh "git tag edge-v#{edge_version}" if publish_edge
321
+ sh "git push origin edge-v#{edge_version}" if publish_edge
297
322
  end
298
323
  end
299
324
 
300
325
  desc '** push all *.gem files to rubygems'
301
326
  task :rubygems => :ask do
302
327
  Dir.chdir(__dir__) do
303
- sh "gem push pkg/concurrent-ruby-#{Concurrent::VERSION}.gem" if publish_base
304
- sh "gem push pkg/concurrent-ruby-edge-#{Concurrent::EDGE_VERSION}.gem" if publish_edge
305
- sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" if publish_base
306
- sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}-x64-mingw32.gem" if publish_base
307
- sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}-x86-mingw32.gem" if publish_base
328
+ sh "gem push pkg/concurrent-ruby-#{version}.gem" if publish_base
329
+ sh "gem push pkg/concurrent-ruby-edge-#{edge_version}.gem" if publish_edge
330
+ sh "gem push pkg/concurrent-ruby-ext-#{version}.gem" if publish_base
331
+ sh "gem push pkg/concurrent-ruby-ext-#{version}-x64-mingw32.gem" if publish_base
332
+ sh "gem push pkg/concurrent-ruby-ext-#{version}-x86-mingw32.gem" if publish_base
308
333
  end
309
334
  end
310
335
 
@@ -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
  #
@@ -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
@@ -83,7 +83,7 @@ module Concurrent
83
83
  #
84
84
  # This is a no-op on some pool implementation (e.g. the Java one). The Ruby
85
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
86
+ # this method explicitly in case your application post jobs in bursts (a
87
87
  # lot of jobs and then nothing for long periods)
88
88
 
89
89
  # @!macro thread_pool_executor_public_api
@@ -57,15 +57,11 @@ if Concurrent.on_jruby?
57
57
  end
58
58
 
59
59
  def ns_shuttingdown?
60
- if @executor.respond_to? :isTerminating
61
- @executor.isTerminating
62
- else
63
- false
64
- end
60
+ @executor.isShutdown && !@executor.isTerminated
65
61
  end
66
62
 
67
63
  def ns_shutdown?
68
- @executor.isShutdown || @executor.isTerminated
64
+ @executor.isTerminated
69
65
  end
70
66
 
71
67
  class Job
@@ -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
  #
@@ -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))
@@ -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.
@@ -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
@@ -11,6 +11,8 @@ module Concurrent
11
11
  def initialize
12
12
  @processor_count = Delay.new { compute_processor_count }
13
13
  @physical_processor_count = Delay.new { compute_physical_processor_count }
14
+ @cpu_quota = Delay.new { compute_cpu_quota }
15
+ @cpu_shares = Delay.new { compute_cpu_shares }
14
16
  end
15
17
 
16
18
  def processor_count
@@ -21,6 +23,29 @@ module Concurrent
21
23
  @physical_processor_count.value
22
24
  end
23
25
 
26
+ def available_processor_count
27
+ cpu_count = processor_count.to_f
28
+ quota = cpu_quota
29
+
30
+ return cpu_count if quota.nil?
31
+
32
+ # cgroup cpus quotas have no limits, so they can be set to higher than the
33
+ # real count of cores.
34
+ if quota > cpu_count
35
+ cpu_count
36
+ else
37
+ quota
38
+ end
39
+ end
40
+
41
+ def cpu_quota
42
+ @cpu_quota.value
43
+ end
44
+
45
+ def cpu_shares
46
+ @cpu_shares.value
47
+ end
48
+
24
49
  private
25
50
 
26
51
  def compute_processor_count
@@ -48,10 +73,20 @@ module Concurrent
48
73
  end
49
74
  cores.count
50
75
  when /mswin|mingw/
51
- require 'win32ole'
52
- result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
53
- "select NumberOfCores from Win32_Processor")
54
- result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
76
+ # Get-CimInstance introduced in PowerShell 3 or earlier: https://learn.microsoft.com/en-us/previous-versions/powershell/module/cimcmdlets/get-ciminstance?view=powershell-3.0
77
+ result = run('powershell -command "Get-CimInstance -ClassName Win32_Processor -Property NumberOfCores | Select-Object -Property NumberOfCores"')
78
+ if !result || $?.exitstatus != 0
79
+ # fallback to deprecated wmic for older systems
80
+ result = run("wmic cpu get NumberOfCores")
81
+ end
82
+ if !result || $?.exitstatus != 0
83
+ # Bail out if both commands returned something unexpected
84
+ processor_count
85
+ else
86
+ # powershell: "\nNumberOfCores\n-------------\n 4\n\n\n"
87
+ # wmic: "NumberOfCores \n\n4 \n\n\n\n"
88
+ result.scan(/\d+/).map(&:to_i).reduce(:+)
89
+ end
55
90
  else
56
91
  processor_count
57
92
  end
@@ -60,6 +95,45 @@ module Concurrent
60
95
  rescue
61
96
  return 1
62
97
  end
98
+
99
+ def run(command)
100
+ IO.popen(command, &:read)
101
+ rescue Errno::ENOENT
102
+ end
103
+
104
+ def compute_cpu_quota
105
+ if RbConfig::CONFIG["target_os"].include?("linux")
106
+ if File.exist?("/sys/fs/cgroup/cpu.max")
107
+ # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
108
+ cpu_max = File.read("/sys/fs/cgroup/cpu.max")
109
+ return nil if cpu_max.start_with?("max ") # no limit
110
+ max, period = cpu_max.split.map(&:to_f)
111
+ max / period
112
+ elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
113
+ # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
114
+ max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
115
+ # If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions
116
+ # https://docs.kernel.org/scheduler/sched-bwc.html#management
117
+ return nil if max <= 0
118
+ period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f
119
+ max / period
120
+ end
121
+ end
122
+ end
123
+
124
+ def compute_cpu_shares
125
+ if RbConfig::CONFIG["target_os"].include?("linux")
126
+ if File.exist?("/sys/fs/cgroup/cpu.weight")
127
+ # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
128
+ # Ref: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2254-cgroup-v2#phase-1-convert-from-cgroups-v1-settings-to-v2
129
+ weight = File.read("/sys/fs/cgroup/cpu.weight").to_f
130
+ ((((weight - 1) * 262142) / 9999) + 2) / 1024
131
+ elsif File.exist?("/sys/fs/cgroup/cpu/cpu.shares")
132
+ # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
133
+ File.read("/sys/fs/cgroup/cpu/cpu.shares").to_f / 1024
134
+ end
135
+ end
136
+ end
63
137
  end
64
138
  end
65
139
 
@@ -75,8 +149,8 @@ module Concurrent
75
149
  # `java.lang.Runtime.getRuntime.availableProcessors` will be used. According
76
150
  # to the Java documentation this "value may change during a particular
77
151
  # invocation of the virtual machine... [applications] should therefore
78
- # occasionally poll this property." Subsequently the result will NOT be
79
- # memoized under JRuby.
152
+ # occasionally poll this property." We still memoize this value once under
153
+ # JRuby.
80
154
  #
81
155
  # Otherwise Ruby's Etc.nprocessors will be used.
82
156
  #
@@ -107,4 +181,40 @@ module Concurrent
107
181
  def self.physical_processor_count
108
182
  processor_counter.physical_processor_count
109
183
  end
184
+
185
+ # Number of processors cores available for process scheduling.
186
+ # This method takes in account the CPU quota if the process is inside a cgroup with a
187
+ # dedicated CPU quota (typically Docker).
188
+ # Otherwise it returns the same value as #processor_count but as a Float.
189
+ #
190
+ # For performance reasons the calculated value will be memoized on the first
191
+ # call.
192
+ #
193
+ # @return [Float] number of available processors
194
+ def self.available_processor_count
195
+ processor_counter.available_processor_count
196
+ end
197
+
198
+ # The maximum number of processors cores available for process scheduling.
199
+ # Returns `nil` if there is no enforced limit, or a `Float` if the
200
+ # process is inside a cgroup with a dedicated CPU quota (typically Docker).
201
+ #
202
+ # Note that nothing prevents setting a CPU quota higher than the actual number of
203
+ # cores on the system.
204
+ #
205
+ # For performance reasons the calculated value will be memoized on the first
206
+ # call.
207
+ #
208
+ # @return [nil, Float] Maximum number of available processors as set by a cgroup CPU quota, or nil if none set
209
+ def self.cpu_quota
210
+ processor_counter.cpu_quota
211
+ end
212
+
213
+ # The CPU shares requested by the process. For performance reasons the calculated
214
+ # value will be memoized on the first call.
215
+ #
216
+ # @return [Float, nil] CPU shares requested by the process, or nil if not set
217
+ def self.cpu_shares
218
+ processor_counter.cpu_shares
219
+ end
110
220
  end
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '1.2.3'
2
+ VERSION = '1.3.5'
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.2.3
4
+ version: 1.3.5
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-01-16 00:00:00.000000000 Z
13
+ date: 2025-01-15 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.