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,73 @@
|
|
1
|
+
module Concurrent
|
2
|
+
module Concern
|
3
|
+
|
4
|
+
# Object references in Ruby are mutable. This can lead to serious problems when
|
5
|
+
# the `#value` of a concurrent object is a mutable reference. Which is always the
|
6
|
+
# case unless the value is a `Fixnum`, `Symbol`, or similar "primitive" data type.
|
7
|
+
# Most classes in this library that expose a `#value` getter method do so using the
|
8
|
+
# `Dereferenceable` mixin module.
|
9
|
+
#
|
10
|
+
# @!macro copy_options
|
11
|
+
module Dereferenceable
|
12
|
+
# NOTE: This module is going away in 2.0. In the mean time we need it to
|
13
|
+
# play nicely with the synchronization layer. This means that the
|
14
|
+
# including class SHOULD be synchronized and it MUST implement a
|
15
|
+
# `#synchronize` method. Not doing so will lead to runtime errors.
|
16
|
+
|
17
|
+
# Return the value this object represents after applying the options specified
|
18
|
+
# by the `#set_deref_options` method.
|
19
|
+
#
|
20
|
+
# @return [Object] the current value of the object
|
21
|
+
def value
|
22
|
+
synchronize { apply_deref_options(@value) }
|
23
|
+
end
|
24
|
+
alias_method :deref, :value
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# Set the internal value of this object
|
29
|
+
#
|
30
|
+
# @param [Object] value the new value
|
31
|
+
def value=(value)
|
32
|
+
synchronize{ @value = value }
|
33
|
+
end
|
34
|
+
|
35
|
+
# @!macro dereferenceable_set_deref_options
|
36
|
+
# Set the options which define the operations #value performs before
|
37
|
+
# returning data to the caller (dereferencing).
|
38
|
+
#
|
39
|
+
# @note Most classes that include this module will call `#set_deref_options`
|
40
|
+
# from within the constructor, thus allowing these options to be set at
|
41
|
+
# object creation.
|
42
|
+
#
|
43
|
+
# @param [Hash] opts the options defining dereference behavior.
|
44
|
+
# @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
|
45
|
+
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
|
46
|
+
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
|
47
|
+
# the internal value and returning the value returned from the proc
|
48
|
+
def set_deref_options(opts = {})
|
49
|
+
synchronize{ ns_set_deref_options(opts) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!macro dereferenceable_set_deref_options
|
53
|
+
# @!visibility private
|
54
|
+
def ns_set_deref_options(opts)
|
55
|
+
@dup_on_deref = opts[:dup_on_deref] || opts[:dup]
|
56
|
+
@freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze]
|
57
|
+
@copy_on_deref = opts[:copy_on_deref] || opts[:copy]
|
58
|
+
@do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref)
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!visibility private
|
63
|
+
def apply_deref_options(value)
|
64
|
+
return nil if value.nil?
|
65
|
+
return value if @do_nothing_on_deref
|
66
|
+
value = @copy_on_deref.call(value) if @copy_on_deref
|
67
|
+
value = value.dup if @dup_on_deref
|
68
|
+
value = value.freeze if @freeze_on_deref
|
69
|
+
value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Concern
|
5
|
+
|
6
|
+
# Include where logging is needed
|
7
|
+
#
|
8
|
+
# @!visibility private
|
9
|
+
module Logging
|
10
|
+
include Logger::Severity
|
11
|
+
|
12
|
+
# Logs through {Concurrent.global_logger}, it can be overridden by setting @logger
|
13
|
+
# @param [Integer] level one of Logger::Severity constants
|
14
|
+
# @param [String] progname e.g. a path of an Actor
|
15
|
+
# @param [String, nil] message when nil block is used to generate the message
|
16
|
+
# @yieldreturn [String] a message
|
17
|
+
def log(level, progname, message = nil, &block)
|
18
|
+
#NOTE: Cannot require 'concurrent/configuration' above due to circular references.
|
19
|
+
# Assume that the gem has been initialized if we've gotten this far.
|
20
|
+
logger = if defined?(@logger) && @logger
|
21
|
+
@logger
|
22
|
+
else
|
23
|
+
Concurrent.global_logger
|
24
|
+
end
|
25
|
+
logger.call level, progname, message, &block
|
26
|
+
rescue => error
|
27
|
+
$stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
|
28
|
+
"#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
require 'concurrent/atomic/event'
|
5
|
+
require 'concurrent/concern/dereferenceable'
|
6
|
+
|
7
|
+
module Concurrent
|
8
|
+
module Concern
|
9
|
+
|
10
|
+
module Obligation
|
11
|
+
include Concern::Dereferenceable
|
12
|
+
# NOTE: The Dereferenceable module is going away in 2.0. In the mean time
|
13
|
+
# we need it to place nicely with the synchronization layer. This means
|
14
|
+
# that the including class SHOULD be synchronized and it MUST implement a
|
15
|
+
# `#synchronize` method. Not doing so will lead to runtime errors.
|
16
|
+
|
17
|
+
# Has the obligation been fulfilled?
|
18
|
+
#
|
19
|
+
# @return [Boolean]
|
20
|
+
def fulfilled?
|
21
|
+
state == :fulfilled
|
22
|
+
end
|
23
|
+
alias_method :realized?, :fulfilled?
|
24
|
+
|
25
|
+
# Has the obligation been rejected?
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
def rejected?
|
29
|
+
state == :rejected
|
30
|
+
end
|
31
|
+
|
32
|
+
# Is obligation completion still pending?
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def pending?
|
36
|
+
state == :pending
|
37
|
+
end
|
38
|
+
|
39
|
+
# Is the obligation still unscheduled?
|
40
|
+
#
|
41
|
+
# @return [Boolean]
|
42
|
+
def unscheduled?
|
43
|
+
state == :unscheduled
|
44
|
+
end
|
45
|
+
|
46
|
+
# Has the obligation completed processing?
|
47
|
+
#
|
48
|
+
# @return [Boolean]
|
49
|
+
def complete?
|
50
|
+
[:fulfilled, :rejected].include? state
|
51
|
+
end
|
52
|
+
|
53
|
+
# Is the obligation still awaiting completion of processing?
|
54
|
+
#
|
55
|
+
# @return [Boolean]
|
56
|
+
def incomplete?
|
57
|
+
! complete?
|
58
|
+
end
|
59
|
+
|
60
|
+
# The current value of the obligation. Will be `nil` while the state is
|
61
|
+
# pending or the operation has been rejected.
|
62
|
+
#
|
63
|
+
# @param [Numeric] timeout the maximum time in seconds to wait.
|
64
|
+
# @return [Object] see Dereferenceable#deref
|
65
|
+
def value(timeout = nil)
|
66
|
+
wait timeout
|
67
|
+
deref
|
68
|
+
end
|
69
|
+
|
70
|
+
# Wait until obligation is complete or the timeout has been reached.
|
71
|
+
#
|
72
|
+
# @param [Numeric] timeout the maximum time in seconds to wait.
|
73
|
+
# @return [Obligation] self
|
74
|
+
def wait(timeout = nil)
|
75
|
+
event.wait(timeout) if timeout != 0 && incomplete?
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
# Wait until obligation is complete or the timeout is reached. Will re-raise
|
80
|
+
# any exceptions raised during processing (but will not raise an exception
|
81
|
+
# on timeout).
|
82
|
+
#
|
83
|
+
# @param [Numeric] timeout the maximum time in seconds to wait.
|
84
|
+
# @return [Obligation] self
|
85
|
+
# @raise [Exception] raises the reason when rejected
|
86
|
+
def wait!(timeout = nil)
|
87
|
+
wait(timeout).tap { raise self if rejected? }
|
88
|
+
end
|
89
|
+
alias_method :no_error!, :wait!
|
90
|
+
|
91
|
+
# The current value of the obligation. Will be `nil` while the state is
|
92
|
+
# pending or the operation has been rejected. Will re-raise any exceptions
|
93
|
+
# raised during processing (but will not raise an exception on timeout).
|
94
|
+
#
|
95
|
+
# @param [Numeric] timeout the maximum time in seconds to wait.
|
96
|
+
# @return [Object] see Dereferenceable#deref
|
97
|
+
# @raise [Exception] raises the reason when rejected
|
98
|
+
def value!(timeout = nil)
|
99
|
+
wait(timeout)
|
100
|
+
if rejected?
|
101
|
+
raise self
|
102
|
+
else
|
103
|
+
deref
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# The current state of the obligation.
|
108
|
+
#
|
109
|
+
# @return [Symbol] the current state
|
110
|
+
def state
|
111
|
+
synchronize { @state }
|
112
|
+
end
|
113
|
+
|
114
|
+
# If an exception was raised during processing this will return the
|
115
|
+
# exception object. Will return `nil` when the state is pending or if
|
116
|
+
# the obligation has been successfully fulfilled.
|
117
|
+
#
|
118
|
+
# @return [Exception] the exception raised during processing or `nil`
|
119
|
+
def reason
|
120
|
+
synchronize { @reason }
|
121
|
+
end
|
122
|
+
|
123
|
+
# @example allows Obligation to be risen
|
124
|
+
# rejected_ivar = Ivar.new.fail
|
125
|
+
# raise rejected_ivar
|
126
|
+
def exception(*args)
|
127
|
+
raise 'obligation is not rejected' unless rejected?
|
128
|
+
reason.exception(*args)
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
# @!visibility private
|
134
|
+
def get_arguments_from(opts = {})
|
135
|
+
[*opts.fetch(:args, [])]
|
136
|
+
end
|
137
|
+
|
138
|
+
# @!visibility private
|
139
|
+
def init_obligation
|
140
|
+
@event = Event.new
|
141
|
+
@value = @reason = nil
|
142
|
+
end
|
143
|
+
|
144
|
+
# @!visibility private
|
145
|
+
def event
|
146
|
+
@event
|
147
|
+
end
|
148
|
+
|
149
|
+
# @!visibility private
|
150
|
+
def set_state(success, value, reason)
|
151
|
+
if success
|
152
|
+
@value = value
|
153
|
+
@state = :fulfilled
|
154
|
+
else
|
155
|
+
@reason = reason
|
156
|
+
@state = :rejected
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# @!visibility private
|
161
|
+
def state=(value)
|
162
|
+
synchronize { ns_set_state(value) }
|
163
|
+
end
|
164
|
+
|
165
|
+
# Atomic compare and set operation
|
166
|
+
# State is set to `next_state` only if `current state == expected_current`.
|
167
|
+
#
|
168
|
+
# @param [Symbol] next_state
|
169
|
+
# @param [Symbol] expected_current
|
170
|
+
#
|
171
|
+
# @return [Boolean] true is state is changed, false otherwise
|
172
|
+
#
|
173
|
+
# @!visibility private
|
174
|
+
def compare_and_set_state(next_state, *expected_current)
|
175
|
+
synchronize do
|
176
|
+
if expected_current.include? @state
|
177
|
+
@state = next_state
|
178
|
+
true
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Executes the block within mutex if current state is included in expected_states
|
186
|
+
#
|
187
|
+
# @return block value if executed, false otherwise
|
188
|
+
#
|
189
|
+
# @!visibility private
|
190
|
+
def if_state(*expected_states)
|
191
|
+
synchronize do
|
192
|
+
raise ArgumentError.new('no block given') unless block_given?
|
193
|
+
|
194
|
+
if expected_states.include? @state
|
195
|
+
yield
|
196
|
+
else
|
197
|
+
false
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
|
204
|
+
# Am I in the current state?
|
205
|
+
#
|
206
|
+
# @param [Symbol] expected The state to check against
|
207
|
+
# @return [Boolean] true if in the expected state else false
|
208
|
+
#
|
209
|
+
# @!visibility private
|
210
|
+
def ns_check_state?(expected)
|
211
|
+
@state == expected
|
212
|
+
end
|
213
|
+
|
214
|
+
# @!visibility private
|
215
|
+
def ns_set_state(value)
|
216
|
+
@state = value
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'concurrent/collection/copy_on_notify_observer_set'
|
2
|
+
require 'concurrent/collection/copy_on_write_observer_set'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
module Concern
|
6
|
+
|
7
|
+
# The [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) is one
|
8
|
+
# of the most useful design patterns.
|
9
|
+
#
|
10
|
+
# The workflow is very simple:
|
11
|
+
# - an `observer` can register itself to a `subject` via a callback
|
12
|
+
# - many `observers` can be registered to the same `subject`
|
13
|
+
# - the `subject` notifies all registered observers when its status changes
|
14
|
+
# - an `observer` can deregister itself when is no more interested to receive
|
15
|
+
# event notifications
|
16
|
+
#
|
17
|
+
# In a single threaded environment the whole pattern is very easy: the
|
18
|
+
# `subject` can use a simple data structure to manage all its subscribed
|
19
|
+
# `observer`s and every `observer` can react directly to every event without
|
20
|
+
# caring about synchronization.
|
21
|
+
#
|
22
|
+
# In a multi threaded environment things are more complex. The `subject` must
|
23
|
+
# synchronize the access to its data structure and to do so currently we're
|
24
|
+
# using two specialized ObserverSet: {Concurrent::Concern::CopyOnWriteObserverSet}
|
25
|
+
# and {Concurrent::Concern::CopyOnNotifyObserverSet}.
|
26
|
+
#
|
27
|
+
# When implementing and `observer` there's a very important rule to remember:
|
28
|
+
# **there are no guarantees about the thread that will execute the callback**
|
29
|
+
#
|
30
|
+
# Let's take this example
|
31
|
+
# ```
|
32
|
+
# class Observer
|
33
|
+
# def initialize
|
34
|
+
# @count = 0
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def update
|
38
|
+
# @count += 1
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# obs = Observer.new
|
43
|
+
# [obj1, obj2, obj3, obj4].each { |o| o.add_observer(obs) }
|
44
|
+
# # execute [obj1, obj2, obj3, obj4]
|
45
|
+
# ```
|
46
|
+
#
|
47
|
+
# `obs` is wrong because the variable `@count` can be accessed by different
|
48
|
+
# threads at the same time, so it should be synchronized (using either a Mutex
|
49
|
+
# or an AtomicFixum)
|
50
|
+
module Observable
|
51
|
+
|
52
|
+
# @!macro observable_add_observer
|
53
|
+
#
|
54
|
+
# Adds an observer to this set. If a block is passed, the observer will be
|
55
|
+
# created by this method and no other params should be passed.
|
56
|
+
#
|
57
|
+
# @param [Object] observer the observer to add
|
58
|
+
# @param [Symbol] func the function to call on the observer during notification.
|
59
|
+
# Default is :update
|
60
|
+
# @return [Object] the added observer
|
61
|
+
def add_observer(observer = nil, func = :update, &block)
|
62
|
+
observers.add_observer(observer, func, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
# As `#add_observer` but can be used for chaining.
|
66
|
+
#
|
67
|
+
# @param [Object] observer the observer to add
|
68
|
+
# @param [Symbol] func the function to call on the observer during notification.
|
69
|
+
# @return [Observable] self
|
70
|
+
def with_observer(observer = nil, func = :update, &block)
|
71
|
+
add_observer(observer, func, &block)
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!macro observable_delete_observer
|
76
|
+
#
|
77
|
+
# Remove `observer` as an observer on this object so that it will no
|
78
|
+
# longer receive notifications.
|
79
|
+
#
|
80
|
+
# @param [Object] observer the observer to remove
|
81
|
+
# @return [Object] the deleted observer
|
82
|
+
def delete_observer(observer)
|
83
|
+
observers.delete_observer(observer)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!macro observable_delete_observers
|
87
|
+
#
|
88
|
+
# Remove all observers associated with this object.
|
89
|
+
#
|
90
|
+
# @return [Observable] self
|
91
|
+
def delete_observers
|
92
|
+
observers.delete_observers
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# @!macro observable_count_observers
|
97
|
+
#
|
98
|
+
# Return the number of observers associated with this object.
|
99
|
+
#
|
100
|
+
# @return [Integer] the observers count
|
101
|
+
def count_observers
|
102
|
+
observers.count_observers
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
attr_accessor :observers
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'concurrent/delay'
|
3
|
+
require 'concurrent/errors'
|
4
|
+
require 'concurrent/atomic/atomic_reference'
|
5
|
+
require 'concurrent/concern/logging'
|
6
|
+
require 'concurrent/concern/deprecation'
|
7
|
+
require 'concurrent/executor/immediate_executor'
|
8
|
+
require 'concurrent/executor/cached_thread_pool'
|
9
|
+
require 'concurrent/utility/processor_counter'
|
10
|
+
|
11
|
+
module Concurrent
|
12
|
+
extend Concern::Logging
|
13
|
+
extend Concern::Deprecation
|
14
|
+
|
15
|
+
autoload :Options, 'concurrent/options'
|
16
|
+
autoload :TimerSet, 'concurrent/executor/timer_set'
|
17
|
+
autoload :ThreadPoolExecutor, 'concurrent/executor/thread_pool_executor'
|
18
|
+
|
19
|
+
# @return [Logger] Logger with provided level and output.
|
20
|
+
def self.create_simple_logger(level = Logger::FATAL, output = $stderr)
|
21
|
+
# TODO (pitr-ch 24-Dec-2016): figure out why it had to be replaced, stdlogger was deadlocking
|
22
|
+
lambda do |severity, progname, message = nil, &block|
|
23
|
+
return false if severity < level
|
24
|
+
|
25
|
+
message = block ? block.call : message
|
26
|
+
formatted_message = case message
|
27
|
+
when String
|
28
|
+
message
|
29
|
+
when Exception
|
30
|
+
format "%s (%s)\n%s",
|
31
|
+
message.message, message.class, (message.backtrace || []).join("\n")
|
32
|
+
else
|
33
|
+
message.inspect
|
34
|
+
end
|
35
|
+
|
36
|
+
output.print format "[%s] %5s -- %s: %s\n",
|
37
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S.%L'),
|
38
|
+
Logger::SEV_LABEL[severity],
|
39
|
+
progname,
|
40
|
+
formatted_message
|
41
|
+
true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Use logger created by #create_simple_logger to log concurrent-ruby messages.
|
46
|
+
def self.use_simple_logger(level = Logger::FATAL, output = $stderr)
|
47
|
+
Concurrent.global_logger = create_simple_logger level, output
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Logger] Logger with provided level and output.
|
51
|
+
# @deprecated
|
52
|
+
def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
|
53
|
+
logger = Logger.new(output)
|
54
|
+
logger.level = level
|
55
|
+
logger.formatter = lambda do |severity, datetime, progname, msg|
|
56
|
+
formatted_message = case msg
|
57
|
+
when String
|
58
|
+
msg
|
59
|
+
when Exception
|
60
|
+
format "%s (%s)\n%s",
|
61
|
+
msg.message, msg.class, (msg.backtrace || []).join("\n")
|
62
|
+
else
|
63
|
+
msg.inspect
|
64
|
+
end
|
65
|
+
format "[%s] %5s -- %s: %s\n",
|
66
|
+
datetime.strftime('%Y-%m-%d %H:%M:%S.%L'),
|
67
|
+
severity,
|
68
|
+
progname,
|
69
|
+
formatted_message
|
70
|
+
end
|
71
|
+
|
72
|
+
lambda do |loglevel, progname, message = nil, &block|
|
73
|
+
logger.add loglevel, message, progname, &block
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Use logger created by #create_stdlib_logger to log concurrent-ruby messages.
|
78
|
+
# @deprecated
|
79
|
+
def self.use_stdlib_logger(level = Logger::FATAL, output = $stderr)
|
80
|
+
Concurrent.global_logger = create_stdlib_logger level, output
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO (pitr-ch 27-Dec-2016): remove deadlocking stdlib_logger methods
|
84
|
+
|
85
|
+
# Suppresses all output when used for logging.
|
86
|
+
NULL_LOGGER = lambda { |level, progname, message = nil, &block| }
|
87
|
+
|
88
|
+
# @!visibility private
|
89
|
+
GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(Logger::WARN))
|
90
|
+
private_constant :GLOBAL_LOGGER
|
91
|
+
|
92
|
+
def self.global_logger
|
93
|
+
GLOBAL_LOGGER.value
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.global_logger=(value)
|
97
|
+
GLOBAL_LOGGER.value = value
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!visibility private
|
101
|
+
GLOBAL_FAST_EXECUTOR = Delay.new { Concurrent.new_fast_executor }
|
102
|
+
private_constant :GLOBAL_FAST_EXECUTOR
|
103
|
+
|
104
|
+
# @!visibility private
|
105
|
+
GLOBAL_IO_EXECUTOR = Delay.new { Concurrent.new_io_executor }
|
106
|
+
private_constant :GLOBAL_IO_EXECUTOR
|
107
|
+
|
108
|
+
# @!visibility private
|
109
|
+
GLOBAL_TIMER_SET = Delay.new { TimerSet.new }
|
110
|
+
private_constant :GLOBAL_TIMER_SET
|
111
|
+
|
112
|
+
# @!visibility private
|
113
|
+
GLOBAL_IMMEDIATE_EXECUTOR = ImmediateExecutor.new
|
114
|
+
private_constant :GLOBAL_IMMEDIATE_EXECUTOR
|
115
|
+
|
116
|
+
# Disables AtExit handlers including pool auto-termination handlers.
|
117
|
+
# When disabled it will be the application programmer's responsibility
|
118
|
+
# to ensure that the handlers are shutdown properly prior to application
|
119
|
+
# exit by calling `AtExit.run` method.
|
120
|
+
#
|
121
|
+
# @note this option should be needed only because of `at_exit` ordering
|
122
|
+
# issues which may arise when running some of the testing frameworks.
|
123
|
+
# E.g. Minitest's test-suite runs itself in `at_exit` callback which
|
124
|
+
# executes after the pools are already terminated. Then auto termination
|
125
|
+
# needs to be disabled and called manually after test-suite ends.
|
126
|
+
# @note This method should *never* be called
|
127
|
+
# from within a gem. It should *only* be used from within the main
|
128
|
+
# application and even then it should be used only when necessary.
|
129
|
+
# @deprecated Has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841.
|
130
|
+
#
|
131
|
+
def self.disable_at_exit_handlers!
|
132
|
+
deprecated "Method #disable_at_exit_handlers! has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841."
|
133
|
+
end
|
134
|
+
|
135
|
+
# Global thread pool optimized for short, fast *operations*.
|
136
|
+
#
|
137
|
+
# @return [ThreadPoolExecutor] the thread pool
|
138
|
+
def self.global_fast_executor
|
139
|
+
GLOBAL_FAST_EXECUTOR.value
|
140
|
+
end
|
141
|
+
|
142
|
+
# Global thread pool optimized for long, blocking (IO) *tasks*.
|
143
|
+
#
|
144
|
+
# @return [ThreadPoolExecutor] the thread pool
|
145
|
+
def self.global_io_executor
|
146
|
+
GLOBAL_IO_EXECUTOR.value
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.global_immediate_executor
|
150
|
+
GLOBAL_IMMEDIATE_EXECUTOR
|
151
|
+
end
|
152
|
+
|
153
|
+
# Global thread pool user for global *timers*.
|
154
|
+
#
|
155
|
+
# @return [Concurrent::TimerSet] the thread pool
|
156
|
+
def self.global_timer_set
|
157
|
+
GLOBAL_TIMER_SET.value
|
158
|
+
end
|
159
|
+
|
160
|
+
# General access point to global executors.
|
161
|
+
# @param [Symbol, Executor] executor_identifier symbols:
|
162
|
+
# - :fast - {Concurrent.global_fast_executor}
|
163
|
+
# - :io - {Concurrent.global_io_executor}
|
164
|
+
# - :immediate - {Concurrent.global_immediate_executor}
|
165
|
+
# @return [Executor]
|
166
|
+
def self.executor(executor_identifier)
|
167
|
+
Options.executor(executor_identifier)
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.new_fast_executor(opts = {})
|
171
|
+
FixedThreadPool.new(
|
172
|
+
[2, Concurrent.processor_count].max,
|
173
|
+
auto_terminate: opts.fetch(:auto_terminate, true),
|
174
|
+
idletime: 60, # 1 minute
|
175
|
+
max_queue: 0, # unlimited
|
176
|
+
fallback_policy: :abort, # shouldn't matter -- 0 max queue
|
177
|
+
name: "fast"
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.new_io_executor(opts = {})
|
182
|
+
CachedThreadPool.new(
|
183
|
+
auto_terminate: opts.fetch(:auto_terminate, true),
|
184
|
+
fallback_policy: :abort, # shouldn't matter -- 0 max queue
|
185
|
+
name: "io"
|
186
|
+
)
|
187
|
+
end
|
188
|
+
end
|