concurrent-ruby 1.2.2 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -2
  3. data/Gemfile +6 -6
  4. data/README.md +7 -3
  5. data/Rakefile +52 -28
  6. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +3 -38
  7. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +1 -1
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +1 -1
  9. data/lib/concurrent-ruby/concurrent/agent.rb +2 -2
  10. data/lib/concurrent-ruby/concurrent/array.rb +3 -3
  11. data/lib/concurrent-ruby/concurrent/async.rb +1 -1
  12. data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
  13. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +9 -2
  14. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +1 -0
  15. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +15 -2
  16. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +8 -1
  17. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +1 -2
  18. data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +9 -1
  19. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +23 -20
  20. data/lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb +55 -0
  21. data/lib/concurrent-ruby/concurrent/collection/timeout_queue.rb +18 -0
  22. data/lib/concurrent-ruby/concurrent/concern/logging.rb +17 -12
  23. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  24. data/lib/concurrent-ruby/concurrent/delay.rb +1 -1
  25. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +6 -4
  26. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +5 -7
  27. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +7 -0
  28. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +2 -0
  29. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +61 -31
  30. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +1 -1
  31. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +10 -3
  32. data/lib/concurrent-ruby/concurrent/executors.rb +0 -1
  33. data/lib/concurrent-ruby/concurrent/hash.rb +5 -3
  34. data/lib/concurrent-ruby/concurrent/map.rb +3 -3
  35. data/lib/concurrent-ruby/concurrent/mvar.rb +4 -4
  36. data/lib/concurrent-ruby/concurrent/promise.rb +2 -2
  37. data/lib/concurrent-ruby/concurrent/promises.rb +33 -23
  38. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +1 -1
  39. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +1 -1
  40. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +1 -1
  41. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +1 -1
  42. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +1 -1
  43. data/lib/concurrent-ruby/concurrent/timer_task.rb +65 -10
  44. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +116 -6
  45. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  46. metadata +7 -10
  47. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +0 -927
  48. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +0 -81
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1cad803df2c0fe187f3208dce2972feaa94daefcbd3749770abe3abfc0642fe9
4
- data.tar.gz: 216b0196496fde70f53cc82b83cd593ec5267a53dcac9a312f254ae627f7761e
3
+ metadata.gz: 0d094624e5f9f2dffc45b80892987f339d8b8c09ff2f01cf923e3825dd6409f7
4
+ data.tar.gz: 02adb496e81a3adb7e3c6042f72ec421677851033d57eb77b0e1b3641ef70684
5
5
  SHA512:
6
- metadata.gz: 605762fd9c26a5a783dcb834f9db5d818ba2b52431b382a7abd33aaddfcc0df8c5005abf0e1a7ebacf3aced9ebb61ee7080fd59050805422f4fc5593925ae935
7
- data.tar.gz: ae916f03459f583497ab268aa82cc79d24dcddb2c51a86abf86313ad77faf975d10d22048ca03179c6583832fc0e25f3f3c1618de0668f9a2774486d7e15e41c
6
+ metadata.gz: 43a4af037c64d4ee8e128963db7a3f40213a45898289bb9143aa222e5664982b8c0fa08d2dae8e629fa7766be6a528b74b5ba17fbbb5cb741a9b5c08020eea15
7
+ data.tar.gz: 0203e085d78340af99c8d8cbf2f0aa8793db6bfdbe4c25cd2ff622816581004c2871851c621d23bcfd653be30fd3aca682e98a3117b64e5735d23bb2a3bfb4c0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  ## Current
2
2
 
3
+ ## Release v1.3.7 (16 June 2026)
4
+
5
+ concurrent-ruby:
6
+
7
+ * See the [release notes on GitHub](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.3.7).
8
+
9
+ ## Release v1.3.6 (13 December 2025)
10
+
11
+ concurrent-ruby:
12
+
13
+ * See the [release notes on GitHub](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.3.6).
14
+
15
+ ## Release v1.3.5, edge v0.7.2 (15 January 2025)
16
+
17
+ concurrent-ruby:
18
+
19
+ * (#1062) Remove dependency on logger.
20
+
21
+ concurrent-ruby-edge:
22
+
23
+ * (#1062) Remove dependency on logger.
24
+
25
+ ## Release v1.3.4 (10 August 2024)
26
+
27
+ * (#1060) Fix bug with return value of `Concurrent.available_processor_count` when `cpu.cfs_quota_us` is -1.
28
+ * (#1058) Add `Concurrent.cpu_shares` that is cgroups aware.
29
+
30
+ ## Release v1.3.3 (9 June 2024)
31
+
32
+ * (#1053) Improve the speed of `Concurrent.physical_processor_count` on Windows.
33
+
34
+ ## Release v1.3.2, edge v0.7.1 (7 June 2024)
35
+
36
+ concurrent-ruby:
37
+
38
+ * (#1051) Remove dependency on `win32ole`.
39
+
40
+ concurrent-ruby-edge:
41
+
42
+ * (#1052) Fix dependency on `concurrent-ruby` to allow the latest release.
43
+
44
+ ## Release v1.3.1 (29 May 2024)
45
+
46
+ * Release 1.3.0 was broken when pushed to RubyGems. 1.3.1 is a packaging fix.
47
+
48
+ ## Release v1.3.0 (28 May 2024)
49
+
50
+ * (#1042) Align Java Executor Service behavior for `shuttingdown?`, `shutdown?`
51
+ * (#1038) Add `Concurrent.available_processor_count` that is cgroups aware.
52
+
53
+ ## Release v1.2.3 (16 Jan 2024)
54
+
55
+ * See [the GitHub release](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.2.3) for details.
56
+
3
57
  ## Release v1.2.2 (24 Feb 2023)
4
58
 
5
59
  * (#993) Fix arguments passed to `Concurrent::Map`'s `default_proc`.
@@ -264,7 +318,7 @@ concurrent-ruby-edge:
264
318
  * Simplification of `RubySingleThreadExecutor`
265
319
  * `Async` improvements
266
320
  - Each object uses its own `SingleThreadExecutor` instead of the global thread pool.
267
- - No longers supports executor injection
321
+ - No longer supports executor injection
268
322
  - Much better documentation
269
323
  * `Atom` updates
270
324
  - No longer `Dereferenceable`
@@ -439,7 +493,7 @@ Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/iss
439
493
  * Fixed bug with return value of `Concurrent::Actor::Utils::Pool#ask`
440
494
  * Fixed timing bug in `TimerTask`
441
495
  * Fixed bug when creating a `JavaThreadPoolExecutor` with minimum pool size of zero
442
- * Removed confusing warning when not using native extenstions
496
+ * Removed confusing warning when not using native extensions
443
497
  * Improved documentation
444
498
 
445
499
  ## Release v0.7.0 (13 August 2014)
data/Gemfile CHANGED
@@ -1,18 +1,18 @@
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'
15
- gem 'rake-compiler', '~> 1.0', '>= 1.0.7'
15
+ gem 'rake-compiler', '~> 1.0', '>= 1.0.7', '!= 1.2.4'
16
16
  gem 'rake-compiler-dock', '~> 1.0'
17
17
  gem 'pry', '~> 0.11', platforms: :mri
18
18
  end
data/README.md CHANGED
@@ -207,7 +207,7 @@ Deprecated features are still available and bugs are being fixed, but new featur
207
207
  These are available in the `concurrent-ruby-edge` companion gem.
208
208
 
209
209
  These features are under active development and may change frequently. They are expected not to
210
- keep backward compatibility (there may also lack tests and documentation). Semantic versions will
210
+ keep backward compatibility (they may also lack tests and documentation). Semantic versions will
211
211
  be obeyed though. Features developed in `concurrent-ruby-edge` are expected to move to
212
212
  `concurrent-ruby` when final.
213
213
 
@@ -284,7 +284,7 @@ To use the tools in the Edge gem it must be required separately:
284
284
  require 'concurrent-edge'
285
285
  ```
286
286
 
287
- If the library does not behave as expected, `Concurrent.use_stdlib_logger(Logger::DEBUG)` could
287
+ If the library does not behave as expected, `Concurrent.use_simple_logger(:DEBUG)` could
288
288
  help to reveal the problem.
289
289
 
290
290
  ## Installation
@@ -358,7 +358,8 @@ best practice is to depend on `concurrent-ruby` and let users to decide if they
358
358
  * Recent CRuby
359
359
  * JRuby, `rbenv install jruby-9.2.17.0`
360
360
  * Set env variable `CONCURRENT_JRUBY_HOME` to point to it, e.g. `/usr/local/opt/rbenv/versions/jruby-9.2.17.0`
361
- * Install Docker, required for Windows builds
361
+ * Install Docker or Podman, required for Windows builds
362
+ * If `bundle config get path` is set, use `bundle config set --local path.system true` otherwise the `gem name, path: '.'` gems won't be found (Bundler limitation).
362
363
 
363
364
  ### Publishing the Gem
364
365
 
@@ -375,7 +376,10 @@ best practice is to depend on `concurrent-ruby` and let users to decide if they
375
376
  * [Benoit Daloze](https://github.com/eregon)
376
377
  * [Matthew Draper](https://github.com/matthewd)
377
378
  * [Rafael França](https://github.com/rafaelfranca)
379
+ * [Charles Oliver Nutter](https://github.com/headius)
380
+ * [Ben Sheldon](https://github.com/bensheldon)
378
381
  * [Samuel Williams](https://github.com/ioquatix)
382
+ * [Joshua Young](https://github.com/joshuay03)
379
383
 
380
384
  ### Special Thanks to
381
385
 
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,29 +315,28 @@ 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
 
311
336
  desc '** print post release steps'
312
337
  task :post_steps do
313
- # TODO: (petr 05-Jun-2021) automate and renew the process
314
- puts 'Manually: create a release on GitHub with relevant changelog part'
315
- puts 'Manually: send email same as release with relevant changelog part'
338
+ puts
339
+ puts 'Manually: create a release on GitHub, use the "Generate release notes" button'
316
340
  puts 'Manually: tweet'
317
341
  end
318
342
  end
@@ -1,12 +1,12 @@
1
1
  package com.concurrent_ruby.ext;
2
2
 
3
+ import static org.jruby.runtime.Visibility.PRIVATE;
4
+
3
5
  import java.lang.reflect.Field;
4
6
  import java.io.IOException;
5
- import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
6
7
  import org.jruby.Ruby;
7
8
  import org.jruby.RubyClass;
8
9
  import org.jruby.RubyModule;
9
- import org.jruby.RubyNumeric;
10
10
  import org.jruby.RubyObject;
11
11
  import org.jruby.anno.JRubyClass;
12
12
  import org.jruby.anno.JRubyMethod;
@@ -91,15 +91,9 @@ public class AtomicReferenceLibrary implements Library {
91
91
  return newValue;
92
92
  }
93
93
 
94
- @JRubyMethod(name = {"compare_and_set", "compare_and_swap"})
94
+ @JRubyMethod(name = "_compare_and_set", visibility = PRIVATE)
95
95
  public IRubyObject compare_and_set(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) {
96
96
  Ruby runtime = context.runtime;
97
-
98
- if (expectedValue instanceof RubyNumeric) {
99
- // numerics are not always idempotent in Ruby, so we need to do slower logic
100
- return compareAndSetNumeric(context, expectedValue, newValue);
101
- }
102
-
103
97
  return runtime.newBoolean(UNSAFE.compareAndSwapObject(this, referenceOffset, expectedValue, newValue));
104
98
  }
105
99
 
@@ -113,35 +107,6 @@ public class AtomicReferenceLibrary implements Library {
113
107
  }
114
108
  }
115
109
  }
116
-
117
- private IRubyObject compareAndSetNumeric(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) {
118
- Ruby runtime = context.runtime;
119
-
120
- // loop until:
121
- // * reference CAS would succeed for same-valued objects
122
- // * current and expected have different values as determined by #equals
123
- while (true) {
124
- IRubyObject current = reference;
125
-
126
- if (!(current instanceof RubyNumeric)) {
127
- // old value is not numeric, CAS fails
128
- return runtime.getFalse();
129
- }
130
-
131
- RubyNumeric currentNumber = (RubyNumeric)current;
132
- if (!currentNumber.equals(expectedValue)) {
133
- // current number does not equal expected, fail CAS
134
- return runtime.getFalse();
135
- }
136
-
137
- // check that current has not changed, or else allow loop to repeat
138
- boolean success = UNSAFE.compareAndSwapObject(this, referenceOffset, current, newValue);
139
- if (success) {
140
- // value is same and did not change in interim...success
141
- return runtime.getTrue();
142
- }
143
- }
144
- }
145
110
  }
146
111
 
147
112
  private static final class UnsafeHolder {
@@ -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)
@@ -21,9 +21,9 @@ module Concurrent
21
21
  # @!macro internal_implementation_note
22
22
  ArrayImplementation = case
23
23
  when Concurrent.on_cruby?
24
- # Array is thread-safe in practice because CRuby runs
25
- # threads one at a time and does not do context
26
- # switching during the execution of C functions.
24
+ # Array is not fully thread-safe on CRuby, see
25
+ # https://github.com/ruby-concurrency/concurrent-ruby/issues/929
26
+ # So we will need to add synchronization here
27
27
  ::Array
28
28
 
29
29
  when Concurrent.on_jruby?
@@ -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
  #
@@ -22,7 +22,6 @@ module Concurrent
22
22
  class CAtomicReference
23
23
  include AtomicDirectUpdate
24
24
  include AtomicNumericCompareAndSetWrapper
25
- alias_method :compare_and_swap, :compare_and_set
26
25
  end
27
26
  CAtomicReference
28
27
  when Concurrent.on_jruby?
@@ -30,14 +29,22 @@ module Concurrent
30
29
  # @!macro internal_implementation_note
31
30
  class JavaAtomicReference
32
31
  include AtomicDirectUpdate
32
+ include AtomicNumericCompareAndSetWrapper
33
33
  end
34
34
  JavaAtomicReference
35
35
  when Concurrent.on_truffleruby?
36
36
  class TruffleRubyAtomicReference < TruffleRuby::AtomicReference
37
37
  include AtomicDirectUpdate
38
+ if private_method_defined?(:compare_and_set_reference)
39
+ alias_method :_compare_and_set, :compare_and_set_reference
40
+ private :_compare_and_set
41
+ include AtomicNumericCompareAndSetWrapper
42
+ else
43
+ # AtomicNumericCompareAndSetWrapper behavior already in TruffleRuby::AtomicReference
44
+ alias_method :compare_and_swap, :compare_and_set
45
+ end
38
46
  alias_method :value, :get
39
47
  alias_method :value=, :set
40
- alias_method :compare_and_swap, :compare_and_set
41
48
  alias_method :swap, :get_and_set
42
49
  end
43
50
  TruffleRubyAtomicReference
@@ -6,6 +6,7 @@ module Concurrent
6
6
  # @!visibility private
7
7
  def self.mutex_owned_per_thread?
8
8
  return false if Concurrent.on_jruby? || Concurrent.on_truffleruby?
9
+ return RUBY_VERSION < "3.0" if Concurrent.on_cruby?
9
10
 
10
11
  mutex = Mutex.new
11
12
  # Lock the mutex:
@@ -1,5 +1,6 @@
1
1
  require 'thread'
2
2
  require 'concurrent/atomic/atomic_fixnum'
3
+ require 'concurrent/atomic/atomic_reference'
3
4
  require 'concurrent/errors'
4
5
  require 'concurrent/synchronization/object'
5
6
  require 'concurrent/synchronization/lock'
@@ -58,7 +59,8 @@ module Concurrent
58
59
  # Create a new `ReadWriteLock` in the unlocked state.
59
60
  def initialize
60
61
  super()
61
- @Counter = AtomicFixnum.new(0) # single integer which represents lock state
62
+ @Counter = AtomicFixnum.new(0) # single integer which represents lock state
63
+ @Writer = AtomicReference.new(nil) # the thread currently holding the write lock
62
64
  @ReadLock = Synchronization::Lock.new
63
65
  @WriteLock = Synchronization::Lock.new
64
66
  end
@@ -137,9 +139,13 @@ module Concurrent
137
139
  # Release a previously acquired read lock.
138
140
  #
139
141
  # @return [Boolean] true if the lock is successfully released
142
+ #
143
+ # @raise [Concurrent::IllegalOperationError] if no read lock is currently held.
140
144
  def release_read_lock
141
145
  while true
142
146
  c = @Counter.value
147
+ raise IllegalOperationError, 'Cannot release a read lock which is not held' if running_readers(c) == 0
148
+
143
149
  if @Counter.compare_and_set(c, c-1)
144
150
  # If one or more writers were waiting, and we were the last reader, wake a writer up
145
151
  if waiting_writer?(c) && running_readers(c) == 1
@@ -187,14 +193,21 @@ module Concurrent
187
193
  break
188
194
  end
189
195
  end
196
+ @Writer.set(Thread.current)
190
197
  true
191
198
  end
192
199
 
193
200
  # Release a previously acquired write lock.
194
201
  #
195
202
  # @return [Boolean] true if the lock is successfully released
203
+ #
204
+ # @raise [Concurrent::IllegalOperationError] if the write lock is not held
205
+ # by the current thread.
196
206
  def release_write_lock
197
- return true unless running_writer?
207
+ unless @Writer.compare_and_set(Thread.current, nil)
208
+ raise IllegalOperationError, 'Cannot release a write lock which is not held by the current thread'
209
+ end
210
+
198
211
  c = @Counter.update { |counter| counter - RUNNING_WRITER }
199
212
  @ReadLock.broadcast
200
213
  @WriteLock.signal if waiting_writers(c) > 0
@@ -158,9 +158,11 @@ module Concurrent
158
158
  # @return [Boolean] true if the lock is successfully acquired
159
159
  #
160
160
  # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
161
- # is exceeded.
161
+ # or per-thread reentrant acquires is exceeded.
162
162
  def acquire_read_lock
163
163
  if (held = @HeldCount.value) > 0
164
+ raise ResourceLimitError.new('Too many reader holds on this thread') if (held & READ_LOCK_MASK) == READ_LOCK_MASK
165
+
164
166
  # If we already have a lock, there's no need to wait
165
167
  if held & READ_LOCK_MASK == 0
166
168
  # But we do need to update the counter, if we were holding a write
@@ -212,8 +214,13 @@ module Concurrent
212
214
  # acquired immediately, return false.
213
215
  #
214
216
  # @return [Boolean] true if the lock is successfully acquired
217
+ #
218
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of per-thread
219
+ # reentrant acquires is exceeded.
215
220
  def try_read_lock
216
221
  if (held = @HeldCount.value) > 0
222
+ raise ResourceLimitError.new('Too many reader holds on this thread') if (held & READ_LOCK_MASK) == READ_LOCK_MASK
223
+
217
224
  if held & READ_LOCK_MASK == 0
218
225
  # If we hold a write lock, but not a read lock...
219
226
  @Counter.update { |c| c + 1 }
@@ -10,7 +10,6 @@ module Concurrent
10
10
  extend Concurrent::Synchronization::SafeInitialization
11
11
  include AtomicDirectUpdate
12
12
  include AtomicNumericCompareAndSetWrapper
13
- alias_method :compare_and_swap, :compare_and_set
14
13
 
15
14
  # @!macro atomic_reference_method_initialize
16
15
  def initialize(value = nil)
@@ -42,7 +41,7 @@ module Concurrent
42
41
  alias_method :swap, :get_and_set
43
42
 
44
43
  # @!macro atomic_reference_method_compare_and_set
45
- def _compare_and_set(old_value, new_value)
44
+ private def _compare_and_set(old_value, new_value)
46
45
  synchronize do
47
46
  if @value.equal? old_value
48
47
  @value = new_value
@@ -9,12 +9,18 @@ module Concurrent
9
9
  # @!macro atomic_reference_method_compare_and_set
10
10
  def compare_and_set(old_value, new_value)
11
11
  if old_value.kind_of? Numeric
12
+ # NaN is never == to itself; match it explicitly so #update can terminate.
13
+ expected_nan = old_value.respond_to?(:nan?) && old_value.nan?
12
14
  while true
13
15
  old = get
14
16
 
15
17
  return false unless old.kind_of? Numeric
16
18
 
17
- return false unless old == old_value
19
+ if expected_nan
20
+ return false unless old.respond_to?(:nan?) && old.nan?
21
+ else
22
+ return false unless old == old_value
23
+ end
18
24
 
19
25
  result = _compare_and_set(old, new_value)
20
26
  return result if result
@@ -24,5 +30,7 @@ module Concurrent
24
30
  end
25
31
  end
26
32
 
33
+ alias_method :compare_and_swap, :compare_and_set
34
+
27
35
  end
28
36
  end