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 +4 -4
- data/CHANGELOG.md +6 -0
- data/Rakefile +2 -3
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +3 -38
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +9 -2
- data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +15 -2
- data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +8 -1
- data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +1 -2
- data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +9 -1
- data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent-ruby/concurrent/version.rb +1 -1
- metadata +5 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d094624e5f9f2dffc45b80892987f339d8b8c09ff2f01cf923e3825dd6409f7
|
|
4
|
+
data.tar.gz: 02adb496e81a3adb7e3c6042f72ec421677851033d57eb77b0e1b3641ef70684
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43a4af037c64d4ee8e128963db7a3f40213a45898289bb9143aa222e5664982b8c0fa08d2dae8e629fa7766be6a528b74b5ba17fbbb5cb741a9b5c08020eea15
|
|
7
|
+
data.tar.gz: 0203e085d78340af99c8d8cbf2f0aa8793db6bfdbe4c25cd2ff622816581004c2871851c621d23bcfd653be30fd3aca682e98a3117b64e5735d23bb2a3bfb4c0
|
data/CHANGELOG.md
CHANGED
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
|
-
|
|
339
|
-
puts 'Manually: create a release on GitHub
|
|
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 =
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
Binary file
|
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.
|
|
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:
|
|
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:
|
|
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.
|