o-concurrent-ruby 1.1.11
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 +7 -0
- data/CHANGELOG.md +542 -0
- data/Gemfile +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +404 -0
- data/Rakefile +307 -0
- data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +189 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
- data/lib/concurrent-ruby/concurrent/agent.rb +587 -0
- data/lib/concurrent-ruby/concurrent/array.rb +66 -0
- data/lib/concurrent-ruby/concurrent/async.rb +449 -0
- data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
- data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
- data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
- data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
- data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
- data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
- data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
- data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
- data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
- data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
- data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
- data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
- data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
- data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
- data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
- data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
- data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
- data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
- data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
- data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
- data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
- data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
- data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
- data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
- data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
- data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
- data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
- data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
- data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
- data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
- data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
- data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
- data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
- data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
- data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
- data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
- data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
- data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
- data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
- data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
- data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
- data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
- data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
- data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
- data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
- data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
- data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
- data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
- data/lib/concurrent-ruby/concurrent/future.rb +141 -0
- data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
- data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
- data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
- data/lib/concurrent-ruby/concurrent/map.rb +346 -0
- data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
- data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
- data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
- data/lib/concurrent-ruby/concurrent/options.rb +42 -0
- data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
- data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
- data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
- data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
- data/lib/concurrent-ruby/concurrent/set.rb +74 -0
- data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
- data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
- data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
- data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
- data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
- data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
- data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
- data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
- data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
- data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
- data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
- data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
- data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
- data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
- data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
- data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
- data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
- data/lib/concurrent-ruby/concurrent/version.rb +3 -0
- data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
- data/lib/concurrent-ruby/concurrent.rb +134 -0
- metadata +192 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Synchronization
|
3
|
+
|
4
|
+
# @!visibility private
|
5
|
+
# @!macro internal_implementation_note
|
6
|
+
ObjectImplementation = case
|
7
|
+
when Concurrent.on_cruby?
|
8
|
+
MriObject
|
9
|
+
when Concurrent.on_jruby?
|
10
|
+
JRubyObject
|
11
|
+
when Concurrent.on_rbx?
|
12
|
+
RbxObject
|
13
|
+
when Concurrent.on_truffleruby?
|
14
|
+
TruffleRubyObject
|
15
|
+
else
|
16
|
+
warn 'Possibly unsupported Ruby implementation'
|
17
|
+
MriObject
|
18
|
+
end
|
19
|
+
private_constant :ObjectImplementation
|
20
|
+
|
21
|
+
# Abstract object providing final, volatile, ans CAS extensions to build other concurrent abstractions.
|
22
|
+
# - final instance variables see {Object.safe_initialization!}
|
23
|
+
# - volatile instance variables see {Object.attr_volatile}
|
24
|
+
# - volatile instance variables see {Object.attr_atomic}
|
25
|
+
class Object < ObjectImplementation
|
26
|
+
# TODO make it a module if possible
|
27
|
+
|
28
|
+
# @!method self.attr_volatile(*names)
|
29
|
+
# Creates methods for reading and writing (as `attr_accessor` does) to a instance variable with
|
30
|
+
# volatile (Java) semantic. The instance variable should be accessed only through generated methods.
|
31
|
+
#
|
32
|
+
# @param [::Array<Symbol>] names of the instance variables to be volatile
|
33
|
+
# @return [::Array<Symbol>] names of defined method names
|
34
|
+
|
35
|
+
# Has to be called by children.
|
36
|
+
def initialize
|
37
|
+
super
|
38
|
+
__initialize_atomic_fields__
|
39
|
+
end
|
40
|
+
|
41
|
+
# By calling this method on a class, it and all its children are marked to be constructed safely. Meaning that
|
42
|
+
# all writes (ivar initializations) are made visible to all readers of newly constructed object. It ensures
|
43
|
+
# same behaviour as Java's final fields.
|
44
|
+
# @example
|
45
|
+
# class AClass < Concurrent::Synchronization::Object
|
46
|
+
# safe_initialization!
|
47
|
+
#
|
48
|
+
# def initialize
|
49
|
+
# @AFinalValue = 'value' # published safely, does not have to be synchronized
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
# @return [true]
|
53
|
+
def self.safe_initialization!
|
54
|
+
# define only once, and not again in children
|
55
|
+
return if safe_initialization?
|
56
|
+
|
57
|
+
# @!visibility private
|
58
|
+
def self.new(*args, &block)
|
59
|
+
object = super(*args, &block)
|
60
|
+
ensure
|
61
|
+
object.full_memory_barrier if object
|
62
|
+
end
|
63
|
+
|
64
|
+
@safe_initialization = true
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [true, false] if this class is safely initialized.
|
68
|
+
def self.safe_initialization?
|
69
|
+
@safe_initialization = false unless defined? @safe_initialization
|
70
|
+
@safe_initialization || (superclass.respond_to?(:safe_initialization?) && superclass.safe_initialization?)
|
71
|
+
end
|
72
|
+
|
73
|
+
# For testing purposes, quite slow. Injects assert code to new method which will raise if class instance contains
|
74
|
+
# any instance variables with CamelCase names and isn't {.safe_initialization?}.
|
75
|
+
# @raise when offend found
|
76
|
+
# @return [true]
|
77
|
+
def self.ensure_safe_initialization_when_final_fields_are_present
|
78
|
+
Object.class_eval do
|
79
|
+
def self.new(*args, &block)
|
80
|
+
object = super(*args, &block)
|
81
|
+
ensure
|
82
|
+
has_final_field = object.instance_variables.any? { |v| v.to_s =~ /^@[A-Z]/ }
|
83
|
+
if has_final_field && !safe_initialization?
|
84
|
+
raise "there was an instance of #{object.class} with final field but not marked with safe_initialization!"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
# Creates methods for reading and writing to a instance variable with
|
92
|
+
# volatile (Java) semantic as {.attr_volatile} does.
|
93
|
+
# The instance variable should be accessed oly through generated methods.
|
94
|
+
# This method generates following methods: `value`, `value=(new_value) #=> new_value`,
|
95
|
+
# `swap_value(new_value) #=> old_value`,
|
96
|
+
# `compare_and_set_value(expected, value) #=> true || false`, `update_value(&block)`.
|
97
|
+
# @param [::Array<Symbol>] names of the instance variables to be volatile with CAS.
|
98
|
+
# @return [::Array<Symbol>] names of defined method names.
|
99
|
+
# @!macro attr_atomic
|
100
|
+
# @!method $1
|
101
|
+
# @return [Object] The $1.
|
102
|
+
# @!method $1=(new_$1)
|
103
|
+
# Set the $1.
|
104
|
+
# @return [Object] new_$1.
|
105
|
+
# @!method swap_$1(new_$1)
|
106
|
+
# Set the $1 to new_$1 and return the old $1.
|
107
|
+
# @return [Object] old $1
|
108
|
+
# @!method compare_and_set_$1(expected_$1, new_$1)
|
109
|
+
# Sets the $1 to new_$1 if the current $1 is expected_$1
|
110
|
+
# @return [true, false]
|
111
|
+
# @!method update_$1(&block)
|
112
|
+
# Updates the $1 using the block.
|
113
|
+
# @yield [Object] Calculate a new $1 using given (old) $1
|
114
|
+
# @yieldparam [Object] old $1
|
115
|
+
# @return [Object] new $1
|
116
|
+
def self.attr_atomic(*names)
|
117
|
+
@__atomic_fields__ ||= []
|
118
|
+
@__atomic_fields__ += names
|
119
|
+
safe_initialization!
|
120
|
+
define_initialize_atomic_fields
|
121
|
+
|
122
|
+
names.each do |name|
|
123
|
+
ivar = :"@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }}"
|
124
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
125
|
+
def #{name}
|
126
|
+
#{ivar}.get
|
127
|
+
end
|
128
|
+
|
129
|
+
def #{name}=(value)
|
130
|
+
#{ivar}.set value
|
131
|
+
end
|
132
|
+
|
133
|
+
def swap_#{name}(value)
|
134
|
+
#{ivar}.swap value
|
135
|
+
end
|
136
|
+
|
137
|
+
def compare_and_set_#{name}(expected, value)
|
138
|
+
#{ivar}.compare_and_set expected, value
|
139
|
+
end
|
140
|
+
|
141
|
+
def update_#{name}(&block)
|
142
|
+
#{ivar}.update(&block)
|
143
|
+
end
|
144
|
+
RUBY
|
145
|
+
end
|
146
|
+
names.flat_map { |n| [n, :"#{n}=", :"swap_#{n}", :"compare_and_set_#{n}", :"update_#{n}"] }
|
147
|
+
end
|
148
|
+
|
149
|
+
# @param [true, false] inherited should inherited volatile with CAS fields be returned?
|
150
|
+
# @return [::Array<Symbol>] Returns defined volatile with CAS fields on this class.
|
151
|
+
def self.atomic_attributes(inherited = true)
|
152
|
+
@__atomic_fields__ ||= []
|
153
|
+
((superclass.atomic_attributes if superclass.respond_to?(:atomic_attributes) && inherited) || []) + @__atomic_fields__
|
154
|
+
end
|
155
|
+
|
156
|
+
# @return [true, false] is the attribute with name atomic?
|
157
|
+
def self.atomic_attribute?(name)
|
158
|
+
atomic_attributes.include? name
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def self.define_initialize_atomic_fields
|
164
|
+
assignments = @__atomic_fields__.map do |name|
|
165
|
+
"@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }} = Concurrent::AtomicReference.new(nil)"
|
166
|
+
end.join("\n")
|
167
|
+
|
168
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
169
|
+
def __initialize_atomic_fields__
|
170
|
+
super
|
171
|
+
#{assignments}
|
172
|
+
end
|
173
|
+
RUBY
|
174
|
+
end
|
175
|
+
|
176
|
+
private_class_method :define_initialize_atomic_fields
|
177
|
+
|
178
|
+
def __initialize_atomic_fields__
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Synchronization
|
3
|
+
|
4
|
+
# @!visibility private
|
5
|
+
# @!macro internal_implementation_note
|
6
|
+
class RbxLockableObject < AbstractLockableObject
|
7
|
+
safe_initialization!
|
8
|
+
|
9
|
+
def initialize(*defaults)
|
10
|
+
super(*defaults)
|
11
|
+
@__Waiters__ = []
|
12
|
+
@__owner__ = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_copy(other)
|
16
|
+
super
|
17
|
+
@__Waiters__ = []
|
18
|
+
@__owner__ = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def synchronize(&block)
|
24
|
+
if @__owner__ == Thread.current
|
25
|
+
yield
|
26
|
+
else
|
27
|
+
result = nil
|
28
|
+
Rubinius.synchronize(self) do
|
29
|
+
begin
|
30
|
+
@__owner__ = Thread.current
|
31
|
+
result = yield
|
32
|
+
ensure
|
33
|
+
@__owner__ = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def ns_wait(timeout = nil)
|
41
|
+
wchan = Rubinius::Channel.new
|
42
|
+
|
43
|
+
begin
|
44
|
+
@__Waiters__.push wchan
|
45
|
+
Rubinius.unlock(self)
|
46
|
+
signaled = wchan.receive_timeout timeout
|
47
|
+
ensure
|
48
|
+
Rubinius.lock(self)
|
49
|
+
|
50
|
+
if !signaled && !@__Waiters__.delete(wchan)
|
51
|
+
# we timed out, but got signaled afterwards,
|
52
|
+
# so pass that signal on to the next waiter
|
53
|
+
@__Waiters__.shift << true unless @__Waiters__.empty?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def ns_signal
|
61
|
+
@__Waiters__.shift << true unless @__Waiters__.empty?
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def ns_broadcast
|
66
|
+
@__Waiters__.shift << true until @__Waiters__.empty?
|
67
|
+
self
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Synchronization
|
3
|
+
|
4
|
+
# @!visibility private
|
5
|
+
module RbxAttrVolatile
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def attr_volatile(*names)
|
13
|
+
names.each do |name|
|
14
|
+
ivar = :"@volatile_#{name}"
|
15
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
16
|
+
def #{name}
|
17
|
+
Rubinius.memory_barrier
|
18
|
+
#{ivar}
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(value)
|
22
|
+
#{ivar} = value
|
23
|
+
Rubinius.memory_barrier
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
names.map { |n| [n, :"#{n}="] }.flatten
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def full_memory_barrier
|
33
|
+
# Rubinius instance variables are not volatile so we need to insert barrier
|
34
|
+
# TODO (pitr 26-Nov-2015): check comments like ^
|
35
|
+
Rubinius.memory_barrier
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @!visibility private
|
40
|
+
# @!macro internal_implementation_note
|
41
|
+
class RbxObject < AbstractObject
|
42
|
+
include RbxAttrVolatile
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
# nothing to do
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Synchronization
|
3
|
+
|
4
|
+
# @!visibility private
|
5
|
+
module TruffleRubyAttrVolatile
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def attr_volatile(*names)
|
12
|
+
names.each do |name|
|
13
|
+
ivar = :"@volatile_#{name}"
|
14
|
+
|
15
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
16
|
+
def #{name}
|
17
|
+
full_memory_barrier
|
18
|
+
#{ivar}
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(value)
|
22
|
+
#{ivar} = value
|
23
|
+
full_memory_barrier
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
|
28
|
+
names.map { |n| [n, :"#{n}="] }.flatten
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def full_memory_barrier
|
33
|
+
TruffleRuby.full_memory_barrier
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
# @!macro internal_implementation_note
|
39
|
+
class TruffleRubyObject < AbstractObject
|
40
|
+
include TruffleRubyAttrVolatile
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
# nothing to do
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Synchronization
|
3
|
+
|
4
|
+
# Volatile adds the attr_volatile class method when included.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class Foo
|
8
|
+
# include Concurrent::Synchronization::Volatile
|
9
|
+
#
|
10
|
+
# attr_volatile :bar
|
11
|
+
#
|
12
|
+
# def initialize
|
13
|
+
# self.bar = 1
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# foo = Foo.new
|
18
|
+
# foo.bar
|
19
|
+
# => 1
|
20
|
+
# foo.bar = 2
|
21
|
+
# => 2
|
22
|
+
|
23
|
+
Volatile = case
|
24
|
+
when Concurrent.on_cruby?
|
25
|
+
MriAttrVolatile
|
26
|
+
when Concurrent.on_jruby?
|
27
|
+
JRubyAttrVolatile
|
28
|
+
when Concurrent.on_rbx?
|
29
|
+
RbxAttrVolatile
|
30
|
+
when Concurrent.on_truffleruby?
|
31
|
+
TruffleRubyAttrVolatile
|
32
|
+
else
|
33
|
+
MriAttrVolatile
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
|
3
|
+
require 'concurrent/synchronization/abstract_object'
|
4
|
+
require 'concurrent/utility/native_extension_loader' # load native parts first
|
5
|
+
Concurrent.load_native_extensions
|
6
|
+
|
7
|
+
require 'concurrent/synchronization/mri_object'
|
8
|
+
require 'concurrent/synchronization/jruby_object'
|
9
|
+
require 'concurrent/synchronization/rbx_object'
|
10
|
+
require 'concurrent/synchronization/truffleruby_object'
|
11
|
+
require 'concurrent/synchronization/object'
|
12
|
+
require 'concurrent/synchronization/volatile'
|
13
|
+
|
14
|
+
require 'concurrent/synchronization/abstract_lockable_object'
|
15
|
+
require 'concurrent/synchronization/mutex_lockable_object'
|
16
|
+
require 'concurrent/synchronization/jruby_lockable_object'
|
17
|
+
require 'concurrent/synchronization/rbx_lockable_object'
|
18
|
+
|
19
|
+
require 'concurrent/synchronization/lockable_object'
|
20
|
+
|
21
|
+
require 'concurrent/synchronization/condition'
|
22
|
+
require 'concurrent/synchronization/lock'
|
23
|
+
|
24
|
+
module Concurrent
|
25
|
+
# {include:file:docs-source/synchronization.md}
|
26
|
+
# {include:file:docs-source/synchronization-notes.md}
|
27
|
+
module Synchronization
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'monitor'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
unless defined?(SynchronizedDelegator)
|
6
|
+
|
7
|
+
# This class provides a trivial way to synchronize all calls to a given object
|
8
|
+
# by wrapping it with a `Delegator` that performs `Monitor#enter/exit` calls
|
9
|
+
# around the delegated `#send`. Example:
|
10
|
+
#
|
11
|
+
# array = [] # not thread-safe on many impls
|
12
|
+
# array = SynchronizedDelegator.new([]) # thread-safe
|
13
|
+
#
|
14
|
+
# A simple `Monitor` provides a very coarse-grained way to synchronize a given
|
15
|
+
# object, in that it will cause synchronization for methods that have no need
|
16
|
+
# for it, but this is a trivial way to get thread-safety where none may exist
|
17
|
+
# currently on some implementations.
|
18
|
+
#
|
19
|
+
# This class is currently being considered for inclusion into stdlib, via
|
20
|
+
# https://bugs.ruby-lang.org/issues/8556
|
21
|
+
#
|
22
|
+
# @!visibility private
|
23
|
+
class SynchronizedDelegator < SimpleDelegator
|
24
|
+
def setup
|
25
|
+
@old_abort = Thread.abort_on_exception
|
26
|
+
Thread.abort_on_exception = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def teardown
|
30
|
+
Thread.abort_on_exception = @old_abort
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(obj)
|
34
|
+
__setobj__(obj)
|
35
|
+
@monitor = Monitor.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(method, *args, &block)
|
39
|
+
monitor = @monitor
|
40
|
+
begin
|
41
|
+
monitor.enter
|
42
|
+
super
|
43
|
+
ensure
|
44
|
+
monitor.exit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'concurrent/thread_safe/util'
|
2
|
+
require 'concurrent/thread_safe/util/striped64'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# @!visibility private
|
7
|
+
module ThreadSafe
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
module Util
|
11
|
+
|
12
|
+
# A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8
|
13
|
+
# available in public domain.
|
14
|
+
#
|
15
|
+
# Original source code available here:
|
16
|
+
# http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8
|
17
|
+
#
|
18
|
+
# One or more variables that together maintain an initially zero
|
19
|
+
# sum. When updates (method +add+) are contended across threads,
|
20
|
+
# the set of variables may grow dynamically to reduce contention.
|
21
|
+
# Method +sum+ returns the current total combined across the
|
22
|
+
# variables maintaining the sum.
|
23
|
+
#
|
24
|
+
# This class is usually preferable to single +Atomic+ reference when
|
25
|
+
# multiple threads update a common sum that is used for purposes such
|
26
|
+
# as collecting statistics, not for fine-grained synchronization
|
27
|
+
# control. Under low update contention, the two classes have similar
|
28
|
+
# characteristics. But under high contention, expected throughput of
|
29
|
+
# this class is significantly higher, at the expense of higher space
|
30
|
+
# consumption.
|
31
|
+
#
|
32
|
+
# @!visibility private
|
33
|
+
class Adder < Striped64
|
34
|
+
# Adds the given value.
|
35
|
+
def add(x)
|
36
|
+
if (current_cells = cells) || !cas_base_computed {|current_base| current_base + x}
|
37
|
+
was_uncontended = true
|
38
|
+
hash = hash_code
|
39
|
+
unless current_cells && (cell = current_cells.volatile_get_by_hash(hash)) && (was_uncontended = cell.cas_computed {|current_value| current_value + x})
|
40
|
+
retry_update(x, hash, was_uncontended) {|current_value| current_value + x}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def increment
|
46
|
+
add(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
def decrement
|
50
|
+
add(-1)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the current sum. The returned value is _NOT_ an
|
54
|
+
# atomic snapshot: Invocation in the absence of concurrent
|
55
|
+
# updates returns an accurate result, but concurrent updates that
|
56
|
+
# occur while the sum is being calculated might not be
|
57
|
+
# incorporated.
|
58
|
+
def sum
|
59
|
+
x = base
|
60
|
+
if current_cells = cells
|
61
|
+
current_cells.each do |cell|
|
62
|
+
x += cell.value if cell
|
63
|
+
end
|
64
|
+
end
|
65
|
+
x
|
66
|
+
end
|
67
|
+
|
68
|
+
def reset
|
69
|
+
internal_reset(0)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'concurrent/thread_safe/util'
|
2
|
+
require 'concurrent/thread_safe/util/volatile'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# @!visibility private
|
7
|
+
module ThreadSafe
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
module Util
|
11
|
+
|
12
|
+
# Provides a cheapest possible (mainly in terms of memory usage) +Mutex+
|
13
|
+
# with the +ConditionVariable+ bundled in.
|
14
|
+
#
|
15
|
+
# Usage:
|
16
|
+
# class A
|
17
|
+
# include CheapLockable
|
18
|
+
#
|
19
|
+
# def do_exlusively
|
20
|
+
# cheap_synchronize { yield }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def wait_for_something
|
24
|
+
# cheap_synchronize do
|
25
|
+
# cheap_wait until resource_available?
|
26
|
+
# do_something
|
27
|
+
# cheap_broadcast # wake up others
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @!visibility private
|
33
|
+
module CheapLockable
|
34
|
+
private
|
35
|
+
engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
36
|
+
if engine == 'rbx'
|
37
|
+
# Making use of the Rubinius' ability to lock via object headers to avoid the overhead of the extra Mutex objects.
|
38
|
+
def cheap_synchronize
|
39
|
+
Rubinius.lock(self)
|
40
|
+
begin
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
Rubinius.unlock(self)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def cheap_wait
|
48
|
+
wchan = Rubinius::Channel.new
|
49
|
+
|
50
|
+
begin
|
51
|
+
waiters = @waiters ||= []
|
52
|
+
waiters.push wchan
|
53
|
+
Rubinius.unlock(self)
|
54
|
+
signaled = wchan.receive_timeout nil
|
55
|
+
ensure
|
56
|
+
Rubinius.lock(self)
|
57
|
+
|
58
|
+
unless signaled or waiters.delete(wchan)
|
59
|
+
# we timed out, but got signaled afterwards (e.g. while waiting to
|
60
|
+
# acquire @lock), so pass that signal on to the next waiter
|
61
|
+
waiters.shift << true unless waiters.empty?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def cheap_broadcast
|
69
|
+
waiters = @waiters ||= []
|
70
|
+
waiters.shift << true until waiters.empty?
|
71
|
+
self
|
72
|
+
end
|
73
|
+
elsif engine == 'jruby'
|
74
|
+
# Use Java's native synchronized (this) { wait(); notifyAll(); } to avoid the overhead of the extra Mutex objects
|
75
|
+
require 'jruby'
|
76
|
+
|
77
|
+
def cheap_synchronize
|
78
|
+
JRuby.reference0(self).synchronized { yield }
|
79
|
+
end
|
80
|
+
|
81
|
+
def cheap_wait
|
82
|
+
JRuby.reference0(self).wait
|
83
|
+
end
|
84
|
+
|
85
|
+
def cheap_broadcast
|
86
|
+
JRuby.reference0(self).notify_all
|
87
|
+
end
|
88
|
+
else
|
89
|
+
require 'thread'
|
90
|
+
|
91
|
+
extend Volatile
|
92
|
+
attr_volatile :mutex
|
93
|
+
|
94
|
+
# Non-reentrant Mutex#syncrhonize
|
95
|
+
def cheap_synchronize
|
96
|
+
true until (my_mutex = mutex) || cas_mutex(nil, my_mutex = Mutex.new)
|
97
|
+
my_mutex.synchronize { yield }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Releases this object's +cheap_synchronize+ lock and goes to sleep waiting for other threads to +cheap_broadcast+, reacquires the lock on wakeup.
|
101
|
+
# Must only be called in +cheap_broadcast+'s block.
|
102
|
+
def cheap_wait
|
103
|
+
conditional_variable = @conditional_variable ||= ConditionVariable.new
|
104
|
+
conditional_variable.wait(mutex)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Wakes up all threads waiting for this object's +cheap_synchronize+ lock.
|
108
|
+
# Must only be called in +cheap_broadcast+'s block.
|
109
|
+
def cheap_broadcast
|
110
|
+
if conditional_variable = @conditional_variable
|
111
|
+
conditional_variable.broadcast
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|