concurrent-ruby 1.1.9 → 1.2.0
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 +24 -0
- data/Gemfile +2 -8
- data/README.md +34 -37
- data/Rakefile +48 -69
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +0 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +52 -22
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +10 -25
- data/lib/concurrent-ruby/concurrent/agent.rb +2 -1
- data/lib/concurrent-ruby/concurrent/array.rb +0 -10
- data/lib/concurrent-ruby/concurrent/async.rb +1 -0
- data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +5 -4
- data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +5 -4
- data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +3 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +82 -151
- data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/event.rb +3 -3
- data/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb +109 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +1 -0
- data/lib/concurrent-ruby/concurrent/atomic/locals.rb +188 -0
- data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +28 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +11 -5
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +11 -5
- data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +1 -1
- data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +19 -3
- data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +2 -1
- data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +9 -9
- data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +32 -14
- data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +96 -89
- data/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb +37 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +15 -4
- data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +1 -1
- data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +1 -1
- data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +2 -0
- data/lib/concurrent-ruby/concurrent/concern/logging.rb +86 -2
- data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent-ruby/concurrent/configuration.rb +4 -87
- data/lib/concurrent-ruby/concurrent/delay.rb +2 -2
- data/lib/concurrent-ruby/concurrent/errors.rb +5 -0
- data/lib/concurrent-ruby/concurrent/exchanger.rb +1 -0
- data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +17 -14
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +13 -3
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +3 -3
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +4 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +10 -4
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +26 -37
- data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +6 -6
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
- data/lib/concurrent-ruby/concurrent/hash.rb +0 -9
- data/lib/concurrent-ruby/concurrent/immutable_struct.rb +1 -1
- data/lib/concurrent-ruby/concurrent/ivar.rb +2 -1
- data/lib/concurrent-ruby/concurrent/map.rb +9 -9
- data/lib/concurrent-ruby/concurrent/maybe.rb +1 -1
- data/lib/concurrent-ruby/concurrent/mutable_struct.rb +1 -1
- data/lib/concurrent-ruby/concurrent/mvar.rb +1 -1
- data/lib/concurrent-ruby/concurrent/promise.rb +1 -1
- data/lib/concurrent-ruby/concurrent/promises.rb +7 -6
- data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
- data/lib/concurrent-ruby/concurrent/scheduled_task.rb +30 -17
- data/lib/concurrent-ruby/concurrent/set.rb +0 -10
- data/lib/concurrent-ruby/concurrent/settable_struct.rb +2 -2
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +5 -1
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +1 -3
- data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +2 -0
- data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +3 -1
- data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +2 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +6 -5
- data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +6 -5
- data/lib/concurrent-ruby/concurrent/synchronization/object.rb +12 -44
- data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +77 -12
- data/lib/concurrent-ruby/concurrent/synchronization.rb +1 -18
- data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +36 -39
- data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +2 -39
- data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +1 -37
- data/lib/concurrent-ruby/concurrent/timer_task.rb +11 -33
- data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
- data/lib/concurrent-ruby/concurrent/tvar.rb +22 -61
- data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
- data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +7 -46
- data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
- data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +36 -89
- data/lib/concurrent-ruby/concurrent/version.rb +1 -1
- data/lib/concurrent-ruby/concurrent-ruby.rb +5 -1
- metadata +10 -12
- data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
- data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
- data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
- data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -71
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
- data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
@@ -1,5 +1,6 @@
|
|
1
|
+
require 'concurrent/utility/native_extension_loader' # load native parts first
|
2
|
+
|
1
3
|
require 'concurrent/atomic/mutex_atomic_boolean'
|
2
|
-
require 'concurrent/synchronization'
|
3
4
|
|
4
5
|
module Concurrent
|
5
6
|
|
@@ -79,10 +80,10 @@ module Concurrent
|
|
79
80
|
# @!visibility private
|
80
81
|
# @!macro internal_implementation_note
|
81
82
|
AtomicBooleanImplementation = case
|
82
|
-
when
|
83
|
-
JavaAtomicBoolean
|
84
|
-
when defined?(CAtomicBoolean)
|
83
|
+
when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
|
85
84
|
CAtomicBoolean
|
85
|
+
when Concurrent.on_jruby?
|
86
|
+
JavaAtomicBoolean
|
86
87
|
else
|
87
88
|
MutexAtomicBoolean
|
88
89
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
+
require 'concurrent/utility/native_extension_loader' # load native parts first
|
2
|
+
|
1
3
|
require 'concurrent/atomic/mutex_atomic_fixnum'
|
2
|
-
require 'concurrent/synchronization'
|
3
4
|
|
4
5
|
module Concurrent
|
5
6
|
|
@@ -96,10 +97,10 @@ module Concurrent
|
|
96
97
|
# @!visibility private
|
97
98
|
# @!macro internal_implementation_note
|
98
99
|
AtomicFixnumImplementation = case
|
99
|
-
when
|
100
|
-
JavaAtomicFixnum
|
101
|
-
when defined?(CAtomicFixnum)
|
100
|
+
when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
|
102
101
|
CAtomicFixnum
|
102
|
+
when Concurrent.on_jruby?
|
103
|
+
JavaAtomicFixnum
|
103
104
|
else
|
104
105
|
MutexAtomicFixnum
|
105
106
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
-
require 'concurrent/
|
2
|
-
|
1
|
+
require 'concurrent/utility/native_extension_loader' # load native parts first
|
2
|
+
|
3
|
+
require 'concurrent/atomic_reference/atomic_direct_update'
|
3
4
|
require 'concurrent/atomic_reference/numeric_cas_wrapper'
|
5
|
+
require 'concurrent/atomic_reference/mutex_atomic'
|
4
6
|
|
5
7
|
# Shim for TruffleRuby::AtomicReference
|
6
8
|
if Concurrent.on_truffleruby? && !defined?(TruffleRuby::AtomicReference)
|
@@ -12,138 +14,6 @@ end
|
|
12
14
|
|
13
15
|
module Concurrent
|
14
16
|
|
15
|
-
# Define update methods that use direct paths
|
16
|
-
#
|
17
|
-
# @!visibility private
|
18
|
-
# @!macro internal_implementation_note
|
19
|
-
module AtomicDirectUpdate
|
20
|
-
|
21
|
-
# @!macro atomic_reference_method_update
|
22
|
-
#
|
23
|
-
# Pass the current value to the given block, replacing it
|
24
|
-
# with the block's result. May retry if the value changes
|
25
|
-
# during the block's execution.
|
26
|
-
#
|
27
|
-
# @yield [Object] Calculate a new value for the atomic reference using
|
28
|
-
# given (old) value
|
29
|
-
# @yieldparam [Object] old_value the starting value of the atomic reference
|
30
|
-
# @return [Object] the new value
|
31
|
-
def update
|
32
|
-
true until compare_and_set(old_value = get, new_value = yield(old_value))
|
33
|
-
new_value
|
34
|
-
end
|
35
|
-
|
36
|
-
# @!macro atomic_reference_method_try_update
|
37
|
-
#
|
38
|
-
# Pass the current value to the given block, replacing it
|
39
|
-
# with the block's result. Return nil if the update fails.
|
40
|
-
#
|
41
|
-
# @yield [Object] Calculate a new value for the atomic reference using
|
42
|
-
# given (old) value
|
43
|
-
# @yieldparam [Object] old_value the starting value of the atomic reference
|
44
|
-
# @note This method was altered to avoid raising an exception by default.
|
45
|
-
# Instead, this method now returns `nil` in case of failure. For more info,
|
46
|
-
# please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
|
47
|
-
# @return [Object] the new value, or nil if update failed
|
48
|
-
def try_update
|
49
|
-
old_value = get
|
50
|
-
new_value = yield old_value
|
51
|
-
|
52
|
-
return unless compare_and_set old_value, new_value
|
53
|
-
|
54
|
-
new_value
|
55
|
-
end
|
56
|
-
|
57
|
-
# @!macro atomic_reference_method_try_update!
|
58
|
-
#
|
59
|
-
# Pass the current value to the given block, replacing it
|
60
|
-
# with the block's result. Raise an exception if the update
|
61
|
-
# fails.
|
62
|
-
#
|
63
|
-
# @yield [Object] Calculate a new value for the atomic reference using
|
64
|
-
# given (old) value
|
65
|
-
# @yieldparam [Object] old_value the starting value of the atomic reference
|
66
|
-
# @note This behavior mimics the behavior of the original
|
67
|
-
# `AtomicReference#try_update` API. The reason this was changed was to
|
68
|
-
# avoid raising exceptions (which are inherently slow) by default. For more
|
69
|
-
# info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
|
70
|
-
# @return [Object] the new value
|
71
|
-
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
|
72
|
-
def try_update!
|
73
|
-
old_value = get
|
74
|
-
new_value = yield old_value
|
75
|
-
unless compare_and_set(old_value, new_value)
|
76
|
-
if $VERBOSE
|
77
|
-
raise ConcurrentUpdateError, "Update failed"
|
78
|
-
else
|
79
|
-
raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE
|
80
|
-
end
|
81
|
-
end
|
82
|
-
new_value
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
require 'concurrent/atomic_reference/mutex_atomic'
|
87
|
-
|
88
|
-
# @!macro atomic_reference
|
89
|
-
#
|
90
|
-
# An object reference that may be updated atomically. All read and write
|
91
|
-
# operations have java volatile semantic.
|
92
|
-
#
|
93
|
-
# @!macro thread_safe_variable_comparison
|
94
|
-
#
|
95
|
-
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
|
96
|
-
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
|
97
|
-
#
|
98
|
-
# @!method initialize(value = nil)
|
99
|
-
# @!macro atomic_reference_method_initialize
|
100
|
-
# @param [Object] value The initial value.
|
101
|
-
#
|
102
|
-
# @!method get
|
103
|
-
# @!macro atomic_reference_method_get
|
104
|
-
# Gets the current value.
|
105
|
-
# @return [Object] the current value
|
106
|
-
#
|
107
|
-
# @!method set(new_value)
|
108
|
-
# @!macro atomic_reference_method_set
|
109
|
-
# Sets to the given value.
|
110
|
-
# @param [Object] new_value the new value
|
111
|
-
# @return [Object] the new value
|
112
|
-
#
|
113
|
-
# @!method get_and_set(new_value)
|
114
|
-
# @!macro atomic_reference_method_get_and_set
|
115
|
-
# Atomically sets to the given value and returns the old value.
|
116
|
-
# @param [Object] new_value the new value
|
117
|
-
# @return [Object] the old value
|
118
|
-
#
|
119
|
-
# @!method compare_and_set(old_value, new_value)
|
120
|
-
# @!macro atomic_reference_method_compare_and_set
|
121
|
-
#
|
122
|
-
# Atomically sets the value to the given updated value if
|
123
|
-
# the current value == the expected value.
|
124
|
-
#
|
125
|
-
# @param [Object] old_value the expected value
|
126
|
-
# @param [Object] new_value the new value
|
127
|
-
#
|
128
|
-
# @return [Boolean] `true` if successful. A `false` return indicates
|
129
|
-
# that the actual value was not equal to the expected value.
|
130
|
-
#
|
131
|
-
# @!method update
|
132
|
-
# @!macro atomic_reference_method_update
|
133
|
-
#
|
134
|
-
# @!method try_update
|
135
|
-
# @!macro atomic_reference_method_try_update
|
136
|
-
#
|
137
|
-
# @!method try_update!
|
138
|
-
# @!macro atomic_reference_method_try_update!
|
139
|
-
|
140
|
-
|
141
|
-
# @!macro internal_implementation_note
|
142
|
-
class ConcurrentUpdateError < ThreadError
|
143
|
-
# frozen pre-allocated backtrace to speed ConcurrentUpdateError
|
144
|
-
CONC_UP_ERR_BACKTRACE = ['backtrace elided; set verbose to enable'].freeze
|
145
|
-
end
|
146
|
-
|
147
17
|
# @!macro internal_implementation_note
|
148
18
|
AtomicReferenceImplementation = case
|
149
19
|
when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
|
@@ -170,28 +40,89 @@ module Concurrent
|
|
170
40
|
alias_method :compare_and_swap, :compare_and_set
|
171
41
|
alias_method :swap, :get_and_set
|
172
42
|
end
|
173
|
-
|
174
|
-
# @note Extends `Rubinius::AtomicReference` version adding aliases
|
175
|
-
# and numeric logic.
|
176
|
-
#
|
177
|
-
# @!visibility private
|
178
|
-
# @!macro internal_implementation_note
|
179
|
-
class RbxAtomicReference < Rubinius::AtomicReference
|
180
|
-
alias_method :_compare_and_set, :compare_and_set
|
181
|
-
include AtomicDirectUpdate
|
182
|
-
include AtomicNumericCompareAndSetWrapper
|
183
|
-
alias_method :value, :get
|
184
|
-
alias_method :value=, :set
|
185
|
-
alias_method :swap, :get_and_set
|
186
|
-
alias_method :compare_and_swap, :compare_and_set
|
187
|
-
end
|
188
|
-
RbxAtomicReference
|
43
|
+
TruffleRubyAtomicReference
|
189
44
|
else
|
190
45
|
MutexAtomicReference
|
191
46
|
end
|
192
47
|
private_constant :AtomicReferenceImplementation
|
193
48
|
|
194
|
-
#
|
49
|
+
# An object reference that may be updated atomically. All read and write
|
50
|
+
# operations have java volatile semantic.
|
51
|
+
#
|
52
|
+
# @!macro thread_safe_variable_comparison
|
53
|
+
#
|
54
|
+
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
|
55
|
+
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
|
56
|
+
#
|
57
|
+
# @!method initialize(value = nil)
|
58
|
+
# @!macro atomic_reference_method_initialize
|
59
|
+
# @param [Object] value The initial value.
|
60
|
+
#
|
61
|
+
# @!method get
|
62
|
+
# @!macro atomic_reference_method_get
|
63
|
+
# Gets the current value.
|
64
|
+
# @return [Object] the current value
|
65
|
+
#
|
66
|
+
# @!method set(new_value)
|
67
|
+
# @!macro atomic_reference_method_set
|
68
|
+
# Sets to the given value.
|
69
|
+
# @param [Object] new_value the new value
|
70
|
+
# @return [Object] the new value
|
71
|
+
#
|
72
|
+
# @!method get_and_set(new_value)
|
73
|
+
# @!macro atomic_reference_method_get_and_set
|
74
|
+
# Atomically sets to the given value and returns the old value.
|
75
|
+
# @param [Object] new_value the new value
|
76
|
+
# @return [Object] the old value
|
77
|
+
#
|
78
|
+
# @!method compare_and_set(old_value, new_value)
|
79
|
+
# @!macro atomic_reference_method_compare_and_set
|
80
|
+
#
|
81
|
+
# Atomically sets the value to the given updated value if
|
82
|
+
# the current value == the expected value.
|
83
|
+
#
|
84
|
+
# @param [Object] old_value the expected value
|
85
|
+
# @param [Object] new_value the new value
|
86
|
+
#
|
87
|
+
# @return [Boolean] `true` if successful. A `false` return indicates
|
88
|
+
# that the actual value was not equal to the expected value.
|
89
|
+
#
|
90
|
+
# @!method update
|
91
|
+
# Pass the current value to the given block, replacing it
|
92
|
+
# with the block's result. May retry if the value changes
|
93
|
+
# during the block's execution.
|
94
|
+
#
|
95
|
+
# @yield [Object] Calculate a new value for the atomic reference using
|
96
|
+
# given (old) value
|
97
|
+
# @yieldparam [Object] old_value the starting value of the atomic reference
|
98
|
+
# @return [Object] the new value
|
99
|
+
#
|
100
|
+
# @!method try_update
|
101
|
+
# Pass the current value to the given block, replacing it
|
102
|
+
# with the block's result. Return nil if the update fails.
|
103
|
+
#
|
104
|
+
# @yield [Object] Calculate a new value for the atomic reference using
|
105
|
+
# given (old) value
|
106
|
+
# @yieldparam [Object] old_value the starting value of the atomic reference
|
107
|
+
# @note This method was altered to avoid raising an exception by default.
|
108
|
+
# Instead, this method now returns `nil` in case of failure. For more info,
|
109
|
+
# please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
|
110
|
+
# @return [Object] the new value, or nil if update failed
|
111
|
+
#
|
112
|
+
# @!method try_update!
|
113
|
+
# Pass the current value to the given block, replacing it
|
114
|
+
# with the block's result. Raise an exception if the update
|
115
|
+
# fails.
|
116
|
+
#
|
117
|
+
# @yield [Object] Calculate a new value for the atomic reference using
|
118
|
+
# given (old) value
|
119
|
+
# @yieldparam [Object] old_value the starting value of the atomic reference
|
120
|
+
# @note This behavior mimics the behavior of the original
|
121
|
+
# `AtomicReference#try_update` API. The reason this was changed was to
|
122
|
+
# avoid raising exceptions (which are inherently slow) by default. For more
|
123
|
+
# info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
|
124
|
+
# @return [Object] the new value
|
125
|
+
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
|
195
126
|
class AtomicReference < AtomicReferenceImplementation
|
196
127
|
|
197
128
|
# @return [String] Short string representation.
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require 'concurrent/synchronization'
|
2
|
+
require 'concurrent/synchronization/lockable_object'
|
3
3
|
|
4
4
|
module Concurrent
|
5
5
|
|
@@ -19,7 +19,7 @@ module Concurrent
|
|
19
19
|
# t1 = Thread.new do
|
20
20
|
# puts "t1 is waiting"
|
21
21
|
# event.wait(1)
|
22
|
-
# puts "event
|
22
|
+
# puts "event occurred"
|
23
23
|
# end
|
24
24
|
#
|
25
25
|
# t2 = Thread.new do
|
@@ -30,8 +30,8 @@ module Concurrent
|
|
30
30
|
# [t1, t2].each(&:join)
|
31
31
|
#
|
32
32
|
# # prints:
|
33
|
-
# # t2 calling set
|
34
33
|
# # t1 is waiting
|
34
|
+
# # t2 calling set
|
35
35
|
# # event occurred
|
36
36
|
class Event < Synchronization::LockableObject
|
37
37
|
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'concurrent/constants'
|
2
|
+
require_relative 'locals'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# A `FiberLocalVar` is a variable where the value is different for each fiber.
|
7
|
+
# Each variable may have a default value, but when you modify the variable only
|
8
|
+
# the current fiber will ever see that change.
|
9
|
+
#
|
10
|
+
# This is similar to Ruby's built-in fiber-local variables (`Thread.current[:name]`),
|
11
|
+
# but with these major advantages:
|
12
|
+
# * `FiberLocalVar` has its own identity, it doesn't need a Symbol.
|
13
|
+
# * Each Ruby's built-in fiber-local variable leaks some memory forever (it's a Symbol held forever on the fiber),
|
14
|
+
# so it's only OK to create a small amount of them.
|
15
|
+
# `FiberLocalVar` has no such issue and it is fine to create many of them.
|
16
|
+
# * Ruby's built-in fiber-local variables leak forever the value set on each fiber (unless set to nil explicitly).
|
17
|
+
# `FiberLocalVar` automatically removes the mapping for each fiber once the `FiberLocalVar` instance is GC'd.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# v = FiberLocalVar.new(14)
|
21
|
+
# v.value #=> 14
|
22
|
+
# v.value = 2
|
23
|
+
# v.value #=> 2
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# v = FiberLocalVar.new(14)
|
27
|
+
#
|
28
|
+
# Fiber.new do
|
29
|
+
# v.value #=> 14
|
30
|
+
# v.value = 1
|
31
|
+
# v.value #=> 1
|
32
|
+
# end.resume
|
33
|
+
#
|
34
|
+
# Fiber.new do
|
35
|
+
# v.value #=> 14
|
36
|
+
# v.value = 2
|
37
|
+
# v.value #=> 2
|
38
|
+
# end.resume
|
39
|
+
#
|
40
|
+
# v.value #=> 14
|
41
|
+
class FiberLocalVar
|
42
|
+
LOCALS = FiberLocals.new
|
43
|
+
|
44
|
+
# Creates a fiber local variable.
|
45
|
+
#
|
46
|
+
# @param [Object] default the default value when otherwise unset
|
47
|
+
# @param [Proc] default_block Optional block that gets called to obtain the
|
48
|
+
# default value for each fiber
|
49
|
+
def initialize(default = nil, &default_block)
|
50
|
+
if default && block_given?
|
51
|
+
raise ArgumentError, "Cannot use both value and block as default value"
|
52
|
+
end
|
53
|
+
|
54
|
+
if block_given?
|
55
|
+
@default_block = default_block
|
56
|
+
@default = nil
|
57
|
+
else
|
58
|
+
@default_block = nil
|
59
|
+
@default = default
|
60
|
+
end
|
61
|
+
|
62
|
+
@index = LOCALS.next_index(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the value in the current fiber's copy of this fiber-local variable.
|
66
|
+
#
|
67
|
+
# @return [Object] the current value
|
68
|
+
def value
|
69
|
+
LOCALS.fetch(@index) { default }
|
70
|
+
end
|
71
|
+
|
72
|
+
# Sets the current fiber's copy of this fiber-local variable to the specified value.
|
73
|
+
#
|
74
|
+
# @param [Object] value the value to set
|
75
|
+
# @return [Object] the new value
|
76
|
+
def value=(value)
|
77
|
+
LOCALS.set(@index, value)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Bind the given value to fiber local storage during
|
81
|
+
# execution of the given block.
|
82
|
+
#
|
83
|
+
# @param [Object] value the value to bind
|
84
|
+
# @yield the operation to be performed with the bound variable
|
85
|
+
# @return [Object] the value
|
86
|
+
def bind(value)
|
87
|
+
if block_given?
|
88
|
+
old_value = self.value
|
89
|
+
self.value = value
|
90
|
+
begin
|
91
|
+
yield
|
92
|
+
ensure
|
93
|
+
self.value = old_value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
# @!visibility private
|
101
|
+
def default
|
102
|
+
if @default_block
|
103
|
+
self.value = @default_block.call
|
104
|
+
else
|
105
|
+
@default
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
require 'concurrent/constants'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
# @!visibility private
|
6
|
+
# @!macro internal_implementation_note
|
7
|
+
#
|
8
|
+
# An abstract implementation of local storage, with sub-classes for
|
9
|
+
# per-thread and per-fiber locals.
|
10
|
+
#
|
11
|
+
# Each execution context (EC, thread or fiber) has a lazily initialized array
|
12
|
+
# of local variable values. Each time a new local variable is created, we
|
13
|
+
# allocate an "index" for it.
|
14
|
+
#
|
15
|
+
# For example, if the allocated index is 1, that means slot #1 in EVERY EC's
|
16
|
+
# locals array will be used for the value of that variable.
|
17
|
+
#
|
18
|
+
# The good thing about using a per-EC structure to hold values, rather than
|
19
|
+
# a global, is that no synchronization is needed when reading and writing
|
20
|
+
# those values (since the structure is only ever accessed by a single
|
21
|
+
# thread).
|
22
|
+
#
|
23
|
+
# Of course, when a local variable is GC'd, 1) we need to recover its index
|
24
|
+
# for use by other new local variables (otherwise the locals arrays could
|
25
|
+
# get bigger and bigger with time), and 2) we need to null out all the
|
26
|
+
# references held in the now-unused slots (both to avoid blocking GC of those
|
27
|
+
# objects, and also to prevent "stale" values from being passed on to a new
|
28
|
+
# local when the index is reused).
|
29
|
+
#
|
30
|
+
# Because we need to null out freed slots, we need to keep references to
|
31
|
+
# ALL the locals arrays, so we can null out the appropriate slots in all of
|
32
|
+
# them. This is why we need to use a finalizer to clean up the locals array
|
33
|
+
# when the EC goes out of scope.
|
34
|
+
class AbstractLocals
|
35
|
+
def initialize
|
36
|
+
@free = []
|
37
|
+
@lock = Mutex.new
|
38
|
+
@all_arrays = {}
|
39
|
+
@next = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def synchronize
|
43
|
+
@lock.synchronize { yield }
|
44
|
+
end
|
45
|
+
|
46
|
+
if Concurrent.on_cruby?
|
47
|
+
def weak_synchronize
|
48
|
+
yield
|
49
|
+
end
|
50
|
+
else
|
51
|
+
alias_method :weak_synchronize, :synchronize
|
52
|
+
end
|
53
|
+
|
54
|
+
def next_index(local)
|
55
|
+
index = synchronize do
|
56
|
+
if @free.empty?
|
57
|
+
@next += 1
|
58
|
+
else
|
59
|
+
@free.pop
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# When the local goes out of scope, we should free the associated index
|
64
|
+
# and all values stored into it.
|
65
|
+
ObjectSpace.define_finalizer(local, local_finalizer(index))
|
66
|
+
|
67
|
+
index
|
68
|
+
end
|
69
|
+
|
70
|
+
def free_index(index)
|
71
|
+
weak_synchronize do
|
72
|
+
# The cost of GC'ing a TLV is linear in the number of ECs using local
|
73
|
+
# variables. But that is natural! More ECs means more storage is used
|
74
|
+
# per local variable. So naturally more CPU time is required to free
|
75
|
+
# more storage.
|
76
|
+
#
|
77
|
+
# DO NOT use each_value which might conflict with new pair assignment
|
78
|
+
# into the hash in #set method.
|
79
|
+
@all_arrays.values.each do |locals|
|
80
|
+
locals[index] = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# free index has to be published after the arrays are cleared:
|
84
|
+
@free << index
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def fetch(index)
|
89
|
+
locals = self.locals
|
90
|
+
value = locals ? locals[index] : nil
|
91
|
+
|
92
|
+
if nil == value
|
93
|
+
yield
|
94
|
+
elsif NULL.equal?(value)
|
95
|
+
nil
|
96
|
+
else
|
97
|
+
value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def set(index, value)
|
102
|
+
locals = self.locals!
|
103
|
+
locals[index] = (nil == value ? NULL : value)
|
104
|
+
|
105
|
+
value
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# When the local goes out of scope, clean up that slot across all locals currently assigned.
|
111
|
+
def local_finalizer(index)
|
112
|
+
proc do
|
113
|
+
free_index(index)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# When a thread/fiber goes out of scope, remove the array from @all_arrays.
|
118
|
+
def thread_fiber_finalizer(array_object_id)
|
119
|
+
proc do
|
120
|
+
weak_synchronize do
|
121
|
+
@all_arrays.delete(array_object_id)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the locals for the current scope, or nil if none exist.
|
127
|
+
def locals
|
128
|
+
raise NotImplementedError
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns the locals for the current scope, creating them if necessary.
|
132
|
+
def locals!
|
133
|
+
raise NotImplementedError
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @!visibility private
|
138
|
+
# @!macro internal_implementation_note
|
139
|
+
# An array-backed storage of indexed variables per thread.
|
140
|
+
class ThreadLocals < AbstractLocals
|
141
|
+
def locals
|
142
|
+
Thread.current.thread_variable_get(:concurrent_thread_locals)
|
143
|
+
end
|
144
|
+
|
145
|
+
def locals!
|
146
|
+
thread = Thread.current
|
147
|
+
locals = thread.thread_variable_get(:concurrent_thread_locals)
|
148
|
+
|
149
|
+
unless locals
|
150
|
+
locals = thread.thread_variable_set(:concurrent_thread_locals, [])
|
151
|
+
weak_synchronize do
|
152
|
+
@all_arrays[locals.object_id] = locals
|
153
|
+
end
|
154
|
+
# When the thread goes out of scope, we should delete the associated locals:
|
155
|
+
ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id))
|
156
|
+
end
|
157
|
+
|
158
|
+
locals
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# @!visibility private
|
163
|
+
# @!macro internal_implementation_note
|
164
|
+
# An array-backed storage of indexed variables per fiber.
|
165
|
+
class FiberLocals < AbstractLocals
|
166
|
+
def locals
|
167
|
+
Thread.current[:concurrent_fiber_locals]
|
168
|
+
end
|
169
|
+
|
170
|
+
def locals!
|
171
|
+
thread = Thread.current
|
172
|
+
locals = thread[:concurrent_fiber_locals]
|
173
|
+
|
174
|
+
unless locals
|
175
|
+
locals = thread[:concurrent_fiber_locals] = []
|
176
|
+
weak_synchronize do
|
177
|
+
@all_arrays[locals.object_id] = locals
|
178
|
+
end
|
179
|
+
# When the fiber goes out of scope, we should delete the associated locals:
|
180
|
+
ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id))
|
181
|
+
end
|
182
|
+
|
183
|
+
locals
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
private_constant :AbstractLocals, :ThreadLocals, :FiberLocals
|
188
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
require_relative 'fiber_local_var'
|
3
|
+
require_relative 'thread_local_var'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
# @!visibility private
|
7
|
+
def self.mutex_owned_per_thread?
|
8
|
+
return false if Concurrent.on_jruby? || Concurrent.on_truffleruby?
|
9
|
+
|
10
|
+
mutex = Mutex.new
|
11
|
+
# Lock the mutex:
|
12
|
+
mutex.synchronize do
|
13
|
+
# Check if the mutex is still owned in a child fiber:
|
14
|
+
Fiber.new { mutex.owned? }.resume
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if mutex_owned_per_thread?
|
19
|
+
LockLocalVar = ThreadLocalVar
|
20
|
+
else
|
21
|
+
LockLocalVar = FiberLocalVar
|
22
|
+
end
|
23
|
+
|
24
|
+
# Either {FiberLocalVar} or {ThreadLocalVar} depending on whether Mutex (and Monitor)
|
25
|
+
# are held, respectively, per Fiber or per Thread.
|
26
|
+
class LockLocalVar
|
27
|
+
end
|
28
|
+
end
|