concurrent-ruby 1.2.3 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
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.