concurrent-ruby 1.2.3 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -2
- data/Gemfile +5 -5
- data/README.md +5 -3
- data/Rakefile +50 -25
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +1 -1
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +1 -1
- data/lib/concurrent-ruby/concurrent/agent.rb +2 -2
- data/lib/concurrent-ruby/concurrent/async.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +1 -0
- data/lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb +55 -0
- data/lib/concurrent-ruby/concurrent/collection/timeout_queue.rb +18 -0
- data/lib/concurrent-ruby/concurrent/concern/logging.rb +17 -12
- data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent-ruby/concurrent/delay.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +2 -4
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +3 -6
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +2 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +2 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +54 -31
- data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +4 -1
- data/lib/concurrent-ruby/concurrent/executors.rb +0 -1
- data/lib/concurrent-ruby/concurrent/map.rb +1 -1
- data/lib/concurrent-ruby/concurrent/mvar.rb +4 -4
- data/lib/concurrent-ruby/concurrent/promise.rb +2 -2
- data/lib/concurrent-ruby/concurrent/scheduled_task.rb +1 -1
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +1 -1
- data/lib/concurrent-ruby/concurrent/synchronization/object.rb +1 -1
- data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +1 -1
- data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +1 -1
- data/lib/concurrent-ruby/concurrent/timer_task.rb +7 -2
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +116 -6
- data/lib/concurrent-ruby/concurrent/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e27f11f8dd5047c9ca365ca8babf3241defab1d707be5055720d1b905e5aa188
|
|
4
|
+
data.tar.gz: 3c5c5e998d8bd76b3b1faf813b870e779d9546149bb6a88a229659dfd741dd13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5cf562bf20ba3f20c5ed974a90b55d5f9f08e70fb4af2b722b296ebc1f2726daf431b7410199f19b310b22da260d85d6508c472ef59356903b51ab1123d4d1d
|
|
7
|
+
data.tar.gz: e187fd07e9e10852e408a6c371be9b734b1821fbc42b7c820440caafa5febef3786cf4fd473bd81f1e28c59e1cb4f177ebeec47ef913ed9445425003022c0a65
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
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
|
+
|
|
19
|
+
## Release v1.3.4 (10 August 2024)
|
|
20
|
+
|
|
21
|
+
* (#1060) Fix bug with return value of `Concurrent.available_processor_count` when `cpu.cfs_quota_us` is -1.
|
|
22
|
+
* (#1058) Add `Concurrent.cpu_shares` that is cgroups aware.
|
|
23
|
+
|
|
24
|
+
## Release v1.3.3 (9 June 2024)
|
|
25
|
+
|
|
26
|
+
* (#1053) Improve the speed of `Concurrent.physical_processor_count` on Windows.
|
|
27
|
+
|
|
28
|
+
## Release v1.3.2, edge v0.7.1 (7 June 2024)
|
|
29
|
+
|
|
30
|
+
concurrent-ruby:
|
|
31
|
+
|
|
32
|
+
* (#1051) Remove dependency on `win32ole`.
|
|
33
|
+
|
|
34
|
+
concurrent-ruby-edge:
|
|
35
|
+
|
|
36
|
+
* (#1052) Fix dependency on `concurrent-ruby` to allow the latest release.
|
|
37
|
+
|
|
38
|
+
## Release v1.3.1 (29 May 2024)
|
|
39
|
+
|
|
40
|
+
* Release 1.3.0 was broken when pushed to RubyGems. 1.3.1 is a packaging fix.
|
|
41
|
+
|
|
42
|
+
## Release v1.3.0 (28 May 2024)
|
|
43
|
+
|
|
44
|
+
* (#1042) Align Java Executor Service behavior for `shuttingdown?`, `shutdown?`
|
|
45
|
+
* (#1038) Add `Concurrent.available_processor_count` that is cgroups aware.
|
|
46
|
+
|
|
3
47
|
## Release v1.2.3 (16 Jan 2024)
|
|
4
48
|
|
|
5
49
|
* See [the GitHub release](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.2.3) for details.
|
|
@@ -268,7 +312,7 @@ concurrent-ruby-edge:
|
|
|
268
312
|
* Simplification of `RubySingleThreadExecutor`
|
|
269
313
|
* `Async` improvements
|
|
270
314
|
- Each object uses its own `SingleThreadExecutor` instead of the global thread pool.
|
|
271
|
-
- No
|
|
315
|
+
- No longer supports executor injection
|
|
272
316
|
- Much better documentation
|
|
273
317
|
* `Atom` updates
|
|
274
318
|
- No longer `Dereferenceable`
|
|
@@ -443,7 +487,7 @@ Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/iss
|
|
|
443
487
|
* Fixed bug with return value of `Concurrent::Actor::Utils::Pool#ask`
|
|
444
488
|
* Fixed timing bug in `TimerTask`
|
|
445
489
|
* Fixed bug when creating a `JavaThreadPoolExecutor` with minimum pool size of zero
|
|
446
|
-
* Removed confusing warning when not using native
|
|
490
|
+
* Removed confusing warning when not using native extensions
|
|
447
491
|
* Improved documentation
|
|
448
492
|
|
|
449
493
|
## Release v0.7.0 (13 August 2014)
|
data/Gemfile
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
source 'https://rubygems.org'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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',
|
|
10
|
-
gem 'concurrent-ruby-edge',
|
|
11
|
-
gem 'concurrent-ruby-ext',
|
|
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
|
@@ -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 (
|
|
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.
|
|
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
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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'] &&
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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-#{
|
|
89
|
-
sh "gem install pkg/concurrent-ruby-ext-#{
|
|
90
|
-
sh "gem install pkg/concurrent-ruby-edge-#{
|
|
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 =
|
|
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
|
|
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 =
|
|
275
|
-
publish_edge =
|
|
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 '
|
|
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#{
|
|
294
|
-
sh "git push origin v#{
|
|
295
|
-
sh "git tag edge-v#{
|
|
296
|
-
sh "git push origin edge-v#{
|
|
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-#{
|
|
304
|
-
sh "gem push pkg/concurrent-ruby-edge-#{
|
|
305
|
-
sh "gem push pkg/concurrent-ruby-ext-#{
|
|
306
|
-
sh "gem push pkg/concurrent-ruby-ext-#{
|
|
307
|
-
sh "gem push pkg/concurrent-ruby-ext-#{
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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 (
|
|
116
|
+
# is acceptable else return false (preferably) or raise an exception.
|
|
117
117
|
#
|
|
118
118
|
# @!macro deref_options
|
|
119
119
|
#
|
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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
|
-
#
|
|
37
|
-
def self.create_simple_logger(level =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
#
|
|
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 =
|
|
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 =
|
|
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(
|
|
111
|
+
GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(:WARN))
|
|
107
112
|
private_constant :GLOBAL_LOGGER
|
|
108
113
|
|
|
109
114
|
def self.global_logger
|
|
Binary file
|
|
@@ -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
|
|
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
|
|
85
|
-
#
|
|
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
|
|
@@ -57,15 +58,11 @@ if Concurrent.on_jruby?
|
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def ns_shuttingdown?
|
|
60
|
-
|
|
61
|
-
@executor.isTerminating
|
|
62
|
-
else
|
|
63
|
-
false
|
|
64
|
-
end
|
|
61
|
+
@executor.isShutdown && !@executor.isTerminated
|
|
65
62
|
end
|
|
66
63
|
|
|
67
64
|
def ns_shutdown?
|
|
68
|
-
@executor.
|
|
65
|
+
@executor.isTerminated
|
|
69
66
|
end
|
|
70
67
|
|
|
71
68
|
class Job
|
|
@@ -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
|
|
99
|
-
synchronize
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
359
|
+
prunable = true
|
|
342
360
|
|
|
343
|
-
|
|
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.
|
|
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
|
|
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
|
-
@
|
|
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
|
|
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
|
-
# `#
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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 #=>
|
|
170
|
+
# c1.value #=> 42
|
|
171
171
|
# c2.wait.state #=> :rejected
|
|
172
172
|
# c2.reason #=> #<RuntimeError: Boom!>
|
|
173
173
|
# ```
|
|
@@ -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
|
|
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.
|
|
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-
|
|
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?
|
|
@@ -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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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."
|
|
79
|
-
#
|
|
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
|
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.
|
|
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:
|
|
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.
|
|
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,
|