concurrent-ruby 1.3.6 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e27f11f8dd5047c9ca365ca8babf3241defab1d707be5055720d1b905e5aa188
4
- data.tar.gz: 3c5c5e998d8bd76b3b1faf813b870e779d9546149bb6a88a229659dfd741dd13
3
+ metadata.gz: 0d094624e5f9f2dffc45b80892987f339d8b8c09ff2f01cf923e3825dd6409f7
4
+ data.tar.gz: 02adb496e81a3adb7e3c6042f72ec421677851033d57eb77b0e1b3641ef70684
5
5
  SHA512:
6
- metadata.gz: c5cf562bf20ba3f20c5ed974a90b55d5f9f08e70fb4af2b722b296ebc1f2726daf431b7410199f19b310b22da260d85d6508c472ef59356903b51ab1123d4d1d
7
- data.tar.gz: e187fd07e9e10852e408a6c371be9b734b1821fbc42b7c820440caafa5febef3786cf4fd473bd81f1e28c59e1cb4f177ebeec47ef913ed9445425003022c0a65
6
+ metadata.gz: 43a4af037c64d4ee8e128963db7a3f40213a45898289bb9143aa222e5664982b8c0fa08d2dae8e629fa7766be6a528b74b5ba17fbbb5cb741a9b5c08020eea15
7
+ data.tar.gz: 0203e085d78340af99c8d8cbf2f0aa8793db6bfdbe4c25cd2ff622816581004c2871851c621d23bcfd653be30fd3aca682e98a3117b64e5735d23bb2a3bfb4c0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## Release v1.3.6 (13 December 2025)
4
10
 
5
11
  concurrent-ruby:
data/Rakefile CHANGED
@@ -335,9 +335,8 @@ namespace :release do
335
335
 
336
336
  desc '** print post release steps'
337
337
  task :post_steps do
338
- # TODO: (petr 05-Jun-2021) automate and renew the process
339
- puts 'Manually: create a release on GitHub with relevant changelog part'
340
- 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'
341
340
  puts 'Manually: tweet'
342
341
  end
343
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 {
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '1.3.6'
2
+ VERSION = '1.3.7'
3
3
  end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: concurrent-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.6
4
+ version: 1.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerry D'Antonio
8
8
  - Petr Chalupa
9
9
  - The Ruby Concurrency Team
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2025-12-13 00:00:00.000000000 Z
12
+ date: 2026-06-16 00:00:00.000000000 Z
14
13
  dependencies: []
15
14
  description: |
16
15
  Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more.
@@ -19,9 +18,9 @@ email: concurrent-ruby@googlegroups.com
19
18
  executables: []
20
19
  extensions: []
21
20
  extra_rdoc_files:
22
- - README.md
23
- - LICENSE.txt
24
21
  - CHANGELOG.md
22
+ - LICENSE.txt
23
+ - README.md
25
24
  files:
26
25
  - CHANGELOG.md
27
26
  - Gemfile
@@ -168,7 +167,6 @@ licenses:
168
167
  metadata:
169
168
  source_code_uri: https://github.com/ruby-concurrency/concurrent-ruby
170
169
  changelog_uri: https://github.com/ruby-concurrency/concurrent-ruby/blob/master/CHANGELOG.md
171
- post_install_message:
172
170
  rdoc_options: []
173
171
  require_paths:
174
172
  - lib/concurrent-ruby
@@ -183,8 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
181
  - !ruby/object:Gem::Version
184
182
  version: '0'
185
183
  requirements: []
186
- rubygems_version: 3.3.27
187
- signing_key:
184
+ rubygems_version: 4.0.6
188
185
  specification_version: 4
189
186
  summary: Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,
190
187
  F#, C#, Java, and classic concurrency patterns.