concurrent-ruby 1.2.3 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c6483d36c3c75859c9b38b6ba2ba13577a0bb89b3b725a032d43f3543797099
4
- data.tar.gz: e94ac3e1ba23db29a7bf025736e34a4027ba7a137ae08b493c78ce3756803792
3
+ metadata.gz: 01c9677c137013ec86d98778a4aa6a9e69bca9b0ae29e923a37254c9cdc515fe
4
+ data.tar.gz: 3be169b6d0a11914f85e4a0e1221cd5c122bd59035abe9718892e1a3b4bc690b
5
5
  SHA512:
6
- metadata.gz: dc0353e60638a409bf891d663626bd2c1c5f05206c08036326d63e239bfe0ea7e1e3480982c75fe44f9d416a7f76a66a2a2aeef78fc60084ed7ff94c7d3181d9
7
- data.tar.gz: 24bdd3a040528c05061ec15b4057184be96fa933f69f3321755e8583ba8e6175beec48c36f1671cc3dad7e413588c9601c7f0e5b503b3de947dff63c2ba76b1e
6
+ metadata.gz: 89544dfc5575906cac0f5c57ce5348b944a70a6f77154fb70d0f185b9cc79344a5bb5147a7cf22808e071b4213c80c94e3036b98a2f4102018daf8ba16492db8
7
+ data.tar.gz: f084a5a9aa8c6e2ad305fe360f1e8e3b264ad0b45f7add6c04561ff4945cbf0b7c30b2c0d6801be46bc61b3f22f60975e8397973a321268bbf302f4bcb65ab24
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  ## Current
2
2
 
3
+ ## Release v1.3.4 (10 August 2024)
4
+
5
+ * (#1060) Fix bug with return value of `Concurrent.available_processor_count` when `cpu.cfs_quota_us` is -1.
6
+ * (#1058) Add `Concurrent.cpu_shares` that is cgroups aware.
7
+
8
+ ## Release v1.3.3 (9 June 2024)
9
+
10
+ * (#1053) Improve the speed of `Concurrent.physical_processor_count` on Windows.
11
+
12
+ ## Release v1.3.2, edge v0.7.1 (7 June 2024)
13
+
14
+ concurrent-ruby:
15
+
16
+ * (#1051) Remove dependency on `win32ole`.
17
+
18
+ concurrent-ruby-edge:
19
+
20
+ * (#1052) Fix dependency on `concurrent-ruby` to allow the latest release.
21
+
22
+ ## Release v1.3.1 (29 May 2024)
23
+
24
+ * Release 1.3.0 was broken when pushed to RubyGems. 1.3.1 is a packaging fix.
25
+
26
+ ## Release v1.3.0 (28 May 2024)
27
+
28
+ * (#1042) Align Java Executor Service behavior for `shuttingdown?`, `shutdown?`
29
+ * (#1038) Add `Concurrent.available_processor_count` that is cgroups aware.
30
+
3
31
  ## Release v1.2.3 (16 Jan 2024)
4
32
 
5
33
  * See [the GitHub release](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.2.3) for details.
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/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,14 @@ 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'
16
15
  end
17
16
 
18
- unless Concurrent.on_jruby? || Concurrent.on_truffleruby?
17
+ if RUBY_ENGINE == 'ruby'
19
18
  require 'rake/extensiontask'
20
19
 
21
20
  Rake::ExtensionTask.new('concurrent_ruby_ext', ext_gemspec) do |ext|
@@ -28,6 +27,10 @@ unless Concurrent.on_jruby? || Concurrent.on_truffleruby?
28
27
  end
29
28
  end
30
29
 
30
+ def which?(executable)
31
+ !`which #{executable} 2>/dev/null`.empty?
32
+ end
33
+
31
34
  require 'rake_compiler_dock'
32
35
  namespace :repackage do
33
36
  desc '* with Windows fat distributions'
@@ -42,12 +45,19 @@ namespace :repackage do
42
45
  Rake::Task['lib/concurrent-ruby/concurrent/concurrent_ruby.jar'].invoke
43
46
 
44
47
  # build all gem files
48
+ rack_compiler_dock_kwargs = {}
49
+ if which?('podman') and (!which?('docker') || `docker --version`.include?('podman'))
50
+ # podman and only podman available, so RakeCompilerDock will use podman, otherwise it uses docker
51
+ rack_compiler_dock_kwargs = {
52
+ options: ['--privileged'], # otherwise the directory in the image is empty
53
+ runas: false
54
+ }
55
+ end
45
56
  %w[x86-mingw32 x64-mingw32].each do |plat|
46
57
  RakeCompilerDock.sh(
47
58
  "bundle install --local && bundle exec rake native:#{plat} gem --trace",
48
59
  platform: plat,
49
- options: ['--privileged'], # otherwise the directory in the image is empty
50
- runas: false)
60
+ **rack_compiler_dock_kwargs)
51
61
  end
52
62
  end
53
63
  end
@@ -57,7 +67,7 @@ require 'rubygems'
57
67
  require 'rubygems/package_task'
58
68
 
59
69
  Gem::PackageTask.new(core_gemspec) {} if core_gemspec
60
- Gem::PackageTask.new(ext_gemspec) {} if ext_gemspec && !Concurrent.on_jruby?
70
+ Gem::PackageTask.new(ext_gemspec) {} if ext_gemspec && RUBY_ENGINE != 'jruby'
61
71
  Gem::PackageTask.new(edge_gemspec) {} if edge_gemspec
62
72
 
63
73
  CLEAN.include(
@@ -85,9 +95,9 @@ begin
85
95
  task :installed do
86
96
  Bundler.with_original_env do
87
97
  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"
98
+ sh "gem install pkg/concurrent-ruby-#{version}.gem"
99
+ sh "gem install pkg/concurrent-ruby-ext-#{version}.gem" if RUBY_ENGINE == 'ruby'
100
+ sh "gem install pkg/concurrent-ruby-edge-#{edge_version}.gem"
91
101
  ENV['NO_PATH'] = 'true'
92
102
  sh 'bundle update'
93
103
  sh 'bundle exec rake spec:ci'
@@ -117,7 +127,7 @@ rescue LoadError => e
117
127
  puts 'RSpec is not installed, skipping test task definitions: ' + e.message
118
128
  end
119
129
 
120
- current_yard_version_name = Concurrent::VERSION
130
+ current_yard_version_name = version
121
131
 
122
132
  begin
123
133
  require 'yard'
@@ -221,6 +231,8 @@ namespace :release do
221
231
  # Depends on environment of @pitr-ch
222
232
 
223
233
  task :checks do
234
+ raise '$CONCURRENT_JRUBY_HOME must be set' unless ENV['CONCURRENT_JRUBY_HOME']
235
+
224
236
  Dir.chdir(__dir__) do
225
237
  sh 'test -z "$(git status --porcelain)"' do |ok, res|
226
238
  unless ok
@@ -251,15 +263,19 @@ namespace :release do
251
263
 
252
264
  desc '* test actual installed gems instead of cloned repository on MRI and JRuby'
253
265
  task :test do
266
+ raise '$CONCURRENT_JRUBY_HOME must be set' unless ENV['CONCURRENT_JRUBY_HOME']
267
+
254
268
  Dir.chdir(__dir__) do
255
269
  puts "Testing with the installed gem"
256
270
 
257
271
  Bundler.with_original_env do
258
272
  sh 'ruby -v'
273
+ sh 'bundle install'
259
274
  sh 'bundle exec rake spec:installed'
260
275
 
261
- env = { "PATH" => "#{ENV['CONCURRENT_JRUBY_HOME']}/bin:#{ENV['PATH']}" }
276
+ env = { "PATH" => "#{ENV.fetch('CONCURRENT_JRUBY_HOME')}/bin:#{ENV['PATH']}" }
262
277
  sh env, 'ruby -v'
278
+ sh env, 'bundle install'
263
279
  sh env, 'bundle exec rake spec:installed'
264
280
  end
265
281
 
@@ -271,8 +287,8 @@ namespace :release do
271
287
  task :publish => ['publish:ask', 'publish:tag', 'publish:rubygems', 'publish:post_steps']
272
288
 
273
289
  namespace :publish do
274
- publish_base = true
275
- publish_edge = false
290
+ publish_base = nil
291
+ publish_edge = nil
276
292
 
277
293
  task :ask do
278
294
  begin
@@ -280,8 +296,15 @@ namespace :release do
280
296
  input = STDIN.gets.strip.downcase
281
297
  end until %w(y n).include?(input)
282
298
  exit 1 if input == 'n'
299
+
300
+ begin
301
+ STDOUT.puts 'Do you want to publish `concurrent-ruby`? (y/n)'
302
+ input = STDIN.gets.strip.downcase
303
+ end until %w(y n).include?(input)
304
+ publish_base = input == 'y'
305
+
283
306
  begin
284
- STDOUT.puts 'It will publish `concurrent-ruby`. Do you want to publish `concurrent-ruby-edge`? (y/n)'
307
+ STDOUT.puts 'Do you want to publish `concurrent-ruby-edge`? (y/n)'
285
308
  input = STDIN.gets.strip.downcase
286
309
  end until %w(y n).include?(input)
287
310
  publish_edge = input == 'y'
@@ -290,21 +313,21 @@ namespace :release do
290
313
  desc '** tag HEAD with current version and push to github'
291
314
  task :tag => :ask do
292
315
  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
316
+ sh "git tag v#{version}" if publish_base
317
+ sh "git push origin v#{version}" if publish_base
318
+ sh "git tag edge-v#{edge_version}" if publish_edge
319
+ sh "git push origin edge-v#{edge_version}" if publish_edge
297
320
  end
298
321
  end
299
322
 
300
323
  desc '** push all *.gem files to rubygems'
301
324
  task :rubygems => :ask do
302
325
  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
326
+ sh "gem push pkg/concurrent-ruby-#{version}.gem" if publish_base
327
+ sh "gem push pkg/concurrent-ruby-edge-#{edge_version}.gem" if publish_edge
328
+ sh "gem push pkg/concurrent-ruby-ext-#{version}.gem" if publish_base
329
+ sh "gem push pkg/concurrent-ruby-ext-#{version}-x64-mingw32.gem" if publish_base
330
+ sh "gem push pkg/concurrent-ruby-ext-#{version}-x86-mingw32.gem" if publish_base
308
331
  end
309
332
  end
310
333
 
@@ -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
@@ -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.4'
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.4
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: 2024-08-10 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.