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,449 @@
|
|
1
|
+
require 'concurrent/configuration'
|
2
|
+
require 'concurrent/ivar'
|
3
|
+
require 'concurrent/synchronization/lockable_object'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
# A mixin module that provides simple asynchronous behavior to a class,
|
8
|
+
# turning it into a simple actor. Loosely based on Erlang's
|
9
|
+
# [gen_server](http://www.erlang.org/doc/man/gen_server.html), but without
|
10
|
+
# supervision or linking.
|
11
|
+
#
|
12
|
+
# A more feature-rich {Concurrent::Actor} is also available when the
|
13
|
+
# capabilities of `Async` are too limited.
|
14
|
+
#
|
15
|
+
# ```cucumber
|
16
|
+
# Feature:
|
17
|
+
# As a stateful, plain old Ruby class
|
18
|
+
# I want safe, asynchronous behavior
|
19
|
+
# So my long-running methods don't block the main thread
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# The `Async` module is a way to mix simple yet powerful asynchronous
|
23
|
+
# capabilities into any plain old Ruby object or class, turning each object
|
24
|
+
# into a simple Actor. Method calls are processed on a background thread. The
|
25
|
+
# caller is free to perform other actions while processing occurs in the
|
26
|
+
# background.
|
27
|
+
#
|
28
|
+
# Method calls to the asynchronous object are made via two proxy methods:
|
29
|
+
# `async` (alias `cast`) and `await` (alias `call`). These proxy methods post
|
30
|
+
# the method call to the object's background thread and return a "future"
|
31
|
+
# which will eventually contain the result of the method call.
|
32
|
+
#
|
33
|
+
# This behavior is loosely patterned after Erlang's `gen_server` behavior.
|
34
|
+
# When an Erlang module implements the `gen_server` behavior it becomes
|
35
|
+
# inherently asynchronous. The `start` or `start_link` function spawns a
|
36
|
+
# process (similar to a thread but much more lightweight and efficient) and
|
37
|
+
# returns the ID of the process. Using the process ID, other processes can
|
38
|
+
# send messages to the `gen_server` via the `cast` and `call` methods. Unlike
|
39
|
+
# Erlang's `gen_server`, however, `Async` classes do not support linking or
|
40
|
+
# supervision trees.
|
41
|
+
#
|
42
|
+
# ## Basic Usage
|
43
|
+
#
|
44
|
+
# When this module is mixed into a class, objects of the class become inherently
|
45
|
+
# asynchronous. Each object gets its own background thread on which to post
|
46
|
+
# asynchronous method calls. Asynchronous method calls are executed in the
|
47
|
+
# background one at a time in the order they are received.
|
48
|
+
#
|
49
|
+
# To create an asynchronous class, simply mix in the `Concurrent::Async` module:
|
50
|
+
#
|
51
|
+
# ```
|
52
|
+
# class Hello
|
53
|
+
# include Concurrent::Async
|
54
|
+
#
|
55
|
+
# def hello(name)
|
56
|
+
# "Hello, #{name}!"
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
# ```
|
60
|
+
#
|
61
|
+
# Mixing this module into a class provides each object two proxy methods:
|
62
|
+
# `async` and `await`. These methods are thread safe with respect to the
|
63
|
+
# enclosing object. The former proxy allows methods to be called
|
64
|
+
# asynchronously by posting to the object's internal thread. The latter proxy
|
65
|
+
# allows a method to be called synchronously but does so safely with respect
|
66
|
+
# to any pending asynchronous method calls and ensures proper ordering. Both
|
67
|
+
# methods return a {Concurrent::IVar} which can be inspected for the result
|
68
|
+
# of the proxied method call. Calling a method with `async` will return a
|
69
|
+
# `:pending` `IVar` whereas `await` will return a `:complete` `IVar`.
|
70
|
+
#
|
71
|
+
# ```
|
72
|
+
# class Echo
|
73
|
+
# include Concurrent::Async
|
74
|
+
#
|
75
|
+
# def echo(msg)
|
76
|
+
# print "#{msg}\n"
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# horn = Echo.new
|
81
|
+
# horn.echo('zero') # synchronous, not thread-safe
|
82
|
+
# # returns the actual return value of the method
|
83
|
+
#
|
84
|
+
# horn.async.echo('one') # asynchronous, non-blocking, thread-safe
|
85
|
+
# # returns an IVar in the :pending state
|
86
|
+
#
|
87
|
+
# horn.await.echo('two') # synchronous, blocking, thread-safe
|
88
|
+
# # returns an IVar in the :complete state
|
89
|
+
# ```
|
90
|
+
#
|
91
|
+
# ## Let It Fail
|
92
|
+
#
|
93
|
+
# The `async` and `await` proxy methods have built-in error protection based
|
94
|
+
# on Erlang's famous "let it fail" philosophy. Instance methods should not be
|
95
|
+
# programmed defensively. When an exception is raised by a delegated method
|
96
|
+
# the proxy will rescue the exception, expose it to the caller as the `reason`
|
97
|
+
# attribute of the returned future, then process the next method call.
|
98
|
+
#
|
99
|
+
# ## Calling Methods Internally
|
100
|
+
#
|
101
|
+
# External method calls should *always* use the `async` and `await` proxy
|
102
|
+
# methods. When one method calls another method, the `async` proxy should
|
103
|
+
# rarely be used and the `await` proxy should *never* be used.
|
104
|
+
#
|
105
|
+
# When an object calls one of its own methods using the `await` proxy the
|
106
|
+
# second call will be enqueued *behind* the currently running method call.
|
107
|
+
# Any attempt to wait on the result will fail as the second call will never
|
108
|
+
# run until after the current call completes.
|
109
|
+
#
|
110
|
+
# Calling a method using the `await` proxy from within a method that was
|
111
|
+
# itself called using `async` or `await` will irreversibly deadlock the
|
112
|
+
# object. Do *not* do this, ever.
|
113
|
+
#
|
114
|
+
# ## Instance Variables and Attribute Accessors
|
115
|
+
#
|
116
|
+
# Instance variables do not need to be thread-safe so long as they are private.
|
117
|
+
# Asynchronous method calls are processed in the order they are received and
|
118
|
+
# are processed one at a time. Therefore private instance variables can only
|
119
|
+
# be accessed by one thread at a time. This is inherently thread-safe.
|
120
|
+
#
|
121
|
+
# When using private instance variables within asynchronous methods, the best
|
122
|
+
# practice is to read the instance variable into a local variable at the start
|
123
|
+
# of the method then update the instance variable at the *end* of the method.
|
124
|
+
# This way, should an exception be raised during method execution the internal
|
125
|
+
# state of the object will not have been changed.
|
126
|
+
#
|
127
|
+
# ### Reader Attributes
|
128
|
+
#
|
129
|
+
# The use of `attr_reader` is discouraged. Internal state exposed externally,
|
130
|
+
# when necessary, should be done through accessor methods. The instance
|
131
|
+
# variables exposed by these methods *must* be thread-safe, or they must be
|
132
|
+
# called using the `async` and `await` proxy methods. These two approaches are
|
133
|
+
# subtly different.
|
134
|
+
#
|
135
|
+
# When internal state is accessed via the `async` and `await` proxy methods,
|
136
|
+
# the returned value represents the object's state *at the time the call is
|
137
|
+
# processed*, which may *not* be the state of the object at the time the call
|
138
|
+
# is made.
|
139
|
+
#
|
140
|
+
# To get the state *at the current* time, irrespective of an enqueued method
|
141
|
+
# calls, a reader method must be called directly. This is inherently unsafe
|
142
|
+
# unless the instance variable is itself thread-safe, preferably using one
|
143
|
+
# of the thread-safe classes within this library. Because the thread-safe
|
144
|
+
# classes within this library are internally-locking or non-locking, they can
|
145
|
+
# be safely used from within asynchronous methods without causing deadlocks.
|
146
|
+
#
|
147
|
+
# Generally speaking, the best practice is to *not* expose internal state via
|
148
|
+
# reader methods. The best practice is to simply use the method's return value.
|
149
|
+
#
|
150
|
+
# ### Writer Attributes
|
151
|
+
#
|
152
|
+
# Writer attributes should never be used with asynchronous classes. Changing
|
153
|
+
# the state externally, even when done in the thread-safe way, is not logically
|
154
|
+
# consistent. Changes to state need to be timed with respect to all asynchronous
|
155
|
+
# method calls which my be in-process or enqueued. The only safe practice is to
|
156
|
+
# pass all necessary data to each method as arguments and let the method update
|
157
|
+
# the internal state as necessary.
|
158
|
+
#
|
159
|
+
# ## Class Constants, Variables, and Methods
|
160
|
+
#
|
161
|
+
# ### Class Constants
|
162
|
+
#
|
163
|
+
# Class constants do not need to be thread-safe. Since they are read-only and
|
164
|
+
# immutable they may be safely read both externally and from within
|
165
|
+
# asynchronous methods.
|
166
|
+
#
|
167
|
+
# ### Class Variables
|
168
|
+
#
|
169
|
+
# Class variables should be avoided. Class variables represent shared state.
|
170
|
+
# Shared state is anathema to concurrency. Should there be a need to share
|
171
|
+
# state using class variables they *must* be thread-safe, preferably
|
172
|
+
# using the thread-safe classes within this library. When updating class
|
173
|
+
# variables, never assign a new value/object to the variable itself. Assignment
|
174
|
+
# is not thread-safe in Ruby. Instead, use the thread-safe update functions
|
175
|
+
# of the variable itself to change the value.
|
176
|
+
#
|
177
|
+
# The best practice is to *never* use class variables with `Async` classes.
|
178
|
+
#
|
179
|
+
# ### Class Methods
|
180
|
+
#
|
181
|
+
# Class methods which are pure functions are safe. Class methods which modify
|
182
|
+
# class variables should be avoided, for all the reasons listed above.
|
183
|
+
#
|
184
|
+
# ## An Important Note About Thread Safe Guarantees
|
185
|
+
#
|
186
|
+
# > Thread safe guarantees can only be made when asynchronous method calls
|
187
|
+
# > are not mixed with direct method calls. Use only direct method calls
|
188
|
+
# > when the object is used exclusively on a single thread. Use only
|
189
|
+
# > `async` and `await` when the object is shared between threads. Once you
|
190
|
+
# > call a method using `async` or `await`, you should no longer call methods
|
191
|
+
# > directly on the object. Use `async` and `await` exclusively from then on.
|
192
|
+
#
|
193
|
+
# @example
|
194
|
+
#
|
195
|
+
# class Echo
|
196
|
+
# include Concurrent::Async
|
197
|
+
#
|
198
|
+
# def echo(msg)
|
199
|
+
# print "#{msg}\n"
|
200
|
+
# end
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# horn = Echo.new
|
204
|
+
# horn.echo('zero') # synchronous, not thread-safe
|
205
|
+
# # returns the actual return value of the method
|
206
|
+
#
|
207
|
+
# horn.async.echo('one') # asynchronous, non-blocking, thread-safe
|
208
|
+
# # returns an IVar in the :pending state
|
209
|
+
#
|
210
|
+
# horn.await.echo('two') # synchronous, blocking, thread-safe
|
211
|
+
# # returns an IVar in the :complete state
|
212
|
+
#
|
213
|
+
# @see Concurrent::Actor
|
214
|
+
# @see https://en.wikipedia.org/wiki/Actor_model "Actor Model" at Wikipedia
|
215
|
+
# @see http://www.erlang.org/doc/man/gen_server.html Erlang gen_server
|
216
|
+
# @see http://c2.com/cgi/wiki?LetItCrash "Let It Crash" at http://c2.com/
|
217
|
+
module Async
|
218
|
+
|
219
|
+
# @!method self.new(*args, &block)
|
220
|
+
#
|
221
|
+
# Instanciate a new object and ensure proper initialization of the
|
222
|
+
# synchronization mechanisms.
|
223
|
+
#
|
224
|
+
# @param [Array<Object>] args Zero or more arguments to be passed to the
|
225
|
+
# object's initializer.
|
226
|
+
# @param [Proc] block Optional block to pass to the object's initializer.
|
227
|
+
# @return [Object] A properly initialized object of the asynchronous class.
|
228
|
+
|
229
|
+
# Check for the presence of a method on an object and determine if a given
|
230
|
+
# set of arguments matches the required arity.
|
231
|
+
#
|
232
|
+
# @param [Object] obj the object to check against
|
233
|
+
# @param [Symbol] method the method to check the object for
|
234
|
+
# @param [Array] args zero or more arguments for the arity check
|
235
|
+
#
|
236
|
+
# @raise [NameError] the object does not respond to `method` method
|
237
|
+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
238
|
+
#
|
239
|
+
# @note This check is imperfect because of the way Ruby reports the arity of
|
240
|
+
# methods with a variable number of arguments. It is possible to determine
|
241
|
+
# if too few arguments are given but impossible to determine if too many
|
242
|
+
# arguments are given. This check may also fail to recognize dynamic behavior
|
243
|
+
# of the object, such as methods simulated with `method_missing`.
|
244
|
+
#
|
245
|
+
# @see http://www.ruby-doc.org/core-2.1.1/Method.html#method-i-arity Method#arity
|
246
|
+
# @see http://ruby-doc.org/core-2.1.0/Object.html#method-i-respond_to-3F Object#respond_to?
|
247
|
+
# @see http://www.ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing BasicObject#method_missing
|
248
|
+
#
|
249
|
+
# @!visibility private
|
250
|
+
def self.validate_argc(obj, method, *args)
|
251
|
+
argc = args.length
|
252
|
+
arity = obj.method(method).arity
|
253
|
+
|
254
|
+
if arity >= 0 && argc != arity
|
255
|
+
raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity})")
|
256
|
+
elsif arity < 0 && (arity = (arity + 1).abs) > argc
|
257
|
+
raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity}..*)")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# @!visibility private
|
262
|
+
def self.included(base)
|
263
|
+
base.singleton_class.send(:alias_method, :original_new, :new)
|
264
|
+
base.extend(ClassMethods)
|
265
|
+
super(base)
|
266
|
+
end
|
267
|
+
|
268
|
+
# @!visibility private
|
269
|
+
module ClassMethods
|
270
|
+
def new(*args, &block)
|
271
|
+
obj = original_new(*args, &block)
|
272
|
+
obj.send(:init_synchronization)
|
273
|
+
obj
|
274
|
+
end
|
275
|
+
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
276
|
+
end
|
277
|
+
private_constant :ClassMethods
|
278
|
+
|
279
|
+
# Delegates asynchronous, thread-safe method calls to the wrapped object.
|
280
|
+
#
|
281
|
+
# @!visibility private
|
282
|
+
class AsyncDelegator < Synchronization::LockableObject
|
283
|
+
safe_initialization!
|
284
|
+
|
285
|
+
# Create a new delegator object wrapping the given delegate.
|
286
|
+
#
|
287
|
+
# @param [Object] delegate the object to wrap and delegate method calls to
|
288
|
+
def initialize(delegate)
|
289
|
+
super()
|
290
|
+
@delegate = delegate
|
291
|
+
@queue = []
|
292
|
+
@executor = Concurrent.global_io_executor
|
293
|
+
@ruby_pid = $$
|
294
|
+
end
|
295
|
+
|
296
|
+
# Delegates method calls to the wrapped object.
|
297
|
+
#
|
298
|
+
# @param [Symbol] method the method being called
|
299
|
+
# @param [Array] args zero or more arguments to the method
|
300
|
+
#
|
301
|
+
# @return [IVar] the result of the method call
|
302
|
+
#
|
303
|
+
# @raise [NameError] the object does not respond to `method` method
|
304
|
+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
305
|
+
def method_missing(method, *args, &block)
|
306
|
+
super unless @delegate.respond_to?(method)
|
307
|
+
Async::validate_argc(@delegate, method, *args)
|
308
|
+
|
309
|
+
ivar = Concurrent::IVar.new
|
310
|
+
synchronize do
|
311
|
+
reset_if_forked
|
312
|
+
@queue.push [ivar, method, args, block]
|
313
|
+
@executor.post { perform } if @queue.length == 1
|
314
|
+
end
|
315
|
+
|
316
|
+
ivar
|
317
|
+
end
|
318
|
+
|
319
|
+
# Check whether the method is responsive
|
320
|
+
#
|
321
|
+
# @param [Symbol] method the method being called
|
322
|
+
def respond_to_missing?(method, include_private = false)
|
323
|
+
@delegate.respond_to?(method) || super
|
324
|
+
end
|
325
|
+
|
326
|
+
# Perform all enqueued tasks.
|
327
|
+
#
|
328
|
+
# This method must be called from within the executor. It must not be
|
329
|
+
# called while already running. It will loop until the queue is empty.
|
330
|
+
def perform
|
331
|
+
loop do
|
332
|
+
ivar, method, args, block = synchronize { @queue.first }
|
333
|
+
break unless ivar # queue is empty
|
334
|
+
|
335
|
+
begin
|
336
|
+
ivar.set(@delegate.send(method, *args, &block))
|
337
|
+
rescue => error
|
338
|
+
ivar.fail(error)
|
339
|
+
end
|
340
|
+
|
341
|
+
synchronize do
|
342
|
+
@queue.shift
|
343
|
+
return if @queue.empty?
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def reset_if_forked
|
349
|
+
if $$ != @ruby_pid
|
350
|
+
@queue.clear
|
351
|
+
@ruby_pid = $$
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
private_constant :AsyncDelegator
|
356
|
+
|
357
|
+
# Delegates synchronous, thread-safe method calls to the wrapped object.
|
358
|
+
#
|
359
|
+
# @!visibility private
|
360
|
+
class AwaitDelegator
|
361
|
+
|
362
|
+
# Create a new delegator object wrapping the given delegate.
|
363
|
+
#
|
364
|
+
# @param [AsyncDelegator] delegate the object to wrap and delegate method calls to
|
365
|
+
def initialize(delegate)
|
366
|
+
@delegate = delegate
|
367
|
+
end
|
368
|
+
|
369
|
+
# Delegates method calls to the wrapped object.
|
370
|
+
#
|
371
|
+
# @param [Symbol] method the method being called
|
372
|
+
# @param [Array] args zero or more arguments to the method
|
373
|
+
#
|
374
|
+
# @return [IVar] the result of the method call
|
375
|
+
#
|
376
|
+
# @raise [NameError] the object does not respond to `method` method
|
377
|
+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
378
|
+
def method_missing(method, *args, &block)
|
379
|
+
ivar = @delegate.send(method, *args, &block)
|
380
|
+
ivar.wait
|
381
|
+
ivar
|
382
|
+
end
|
383
|
+
|
384
|
+
# Check whether the method is responsive
|
385
|
+
#
|
386
|
+
# @param [Symbol] method the method being called
|
387
|
+
def respond_to_missing?(method, include_private = false)
|
388
|
+
@delegate.respond_to?(method) || super
|
389
|
+
end
|
390
|
+
end
|
391
|
+
private_constant :AwaitDelegator
|
392
|
+
|
393
|
+
# Causes the chained method call to be performed asynchronously on the
|
394
|
+
# object's thread. The delegated method will return a future in the
|
395
|
+
# `:pending` state and the method call will have been scheduled on the
|
396
|
+
# object's thread. The final disposition of the method call can be obtained
|
397
|
+
# by inspecting the returned future.
|
398
|
+
#
|
399
|
+
# @!macro async_thread_safety_warning
|
400
|
+
# @note The method call is guaranteed to be thread safe with respect to
|
401
|
+
# all other method calls against the same object that are called with
|
402
|
+
# either `async` or `await`. The mutable nature of Ruby references
|
403
|
+
# (and object orientation in general) prevent any other thread safety
|
404
|
+
# guarantees. Do NOT mix direct method calls with delegated method calls.
|
405
|
+
# Use *only* delegated method calls when sharing the object between threads.
|
406
|
+
#
|
407
|
+
# @return [Concurrent::IVar] the pending result of the asynchronous operation
|
408
|
+
#
|
409
|
+
# @raise [NameError] the object does not respond to the requested method
|
410
|
+
# @raise [ArgumentError] the given `args` do not match the arity of
|
411
|
+
# the requested method
|
412
|
+
def async
|
413
|
+
@__async_delegator__
|
414
|
+
end
|
415
|
+
alias_method :cast, :async
|
416
|
+
|
417
|
+
# Causes the chained method call to be performed synchronously on the
|
418
|
+
# current thread. The delegated will return a future in either the
|
419
|
+
# `:fulfilled` or `:rejected` state and the delegated method will have
|
420
|
+
# completed. The final disposition of the delegated method can be obtained
|
421
|
+
# by inspecting the returned future.
|
422
|
+
#
|
423
|
+
# @!macro async_thread_safety_warning
|
424
|
+
#
|
425
|
+
# @return [Concurrent::IVar] the completed result of the synchronous operation
|
426
|
+
#
|
427
|
+
# @raise [NameError] the object does not respond to the requested method
|
428
|
+
# @raise [ArgumentError] the given `args` do not match the arity of the
|
429
|
+
# requested method
|
430
|
+
def await
|
431
|
+
@__await_delegator__
|
432
|
+
end
|
433
|
+
alias_method :call, :await
|
434
|
+
|
435
|
+
# Initialize the internal serializer and other stnchronization mechanisms.
|
436
|
+
#
|
437
|
+
# @note This method *must* be called immediately upon object construction.
|
438
|
+
# This is the only way thread-safe initialization can be guaranteed.
|
439
|
+
#
|
440
|
+
# @!visibility private
|
441
|
+
def init_synchronization
|
442
|
+
return self if defined?(@__async_initialized__) && @__async_initialized__
|
443
|
+
@__async_initialized__ = true
|
444
|
+
@__async_delegator__ = AsyncDelegator.new(self)
|
445
|
+
@__await_delegator__ = AwaitDelegator.new(@__async_delegator__)
|
446
|
+
self
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'concurrent/atomic/atomic_reference'
|
2
|
+
require 'concurrent/collection/copy_on_notify_observer_set'
|
3
|
+
require 'concurrent/concern/observable'
|
4
|
+
require 'concurrent/synchronization'
|
5
|
+
|
6
|
+
# @!macro thread_safe_variable_comparison
|
7
|
+
#
|
8
|
+
# ## Thread-safe Variable Classes
|
9
|
+
#
|
10
|
+
# Each of the thread-safe variable classes is designed to solve a different
|
11
|
+
# problem. In general:
|
12
|
+
#
|
13
|
+
# * *{Concurrent::Agent}:* Shared, mutable variable providing independent,
|
14
|
+
# uncoordinated, *asynchronous* change of individual values. Best used when
|
15
|
+
# the value will undergo frequent, complex updates. Suitable when the result
|
16
|
+
# of an update does not need to be known immediately.
|
17
|
+
# * *{Concurrent::Atom}:* Shared, mutable variable providing independent,
|
18
|
+
# uncoordinated, *synchronous* change of individual values. Best used when
|
19
|
+
# the value will undergo frequent reads but only occasional, though complex,
|
20
|
+
# updates. Suitable when the result of an update must be known immediately.
|
21
|
+
# * *{Concurrent::AtomicReference}:* A simple object reference that can be updated
|
22
|
+
# atomically. Updates are synchronous but fast. Best used when updates a
|
23
|
+
# simple set operations. Not suitable when updates are complex.
|
24
|
+
# {Concurrent::AtomicBoolean} and {Concurrent::AtomicFixnum} are similar
|
25
|
+
# but optimized for the given data type.
|
26
|
+
# * *{Concurrent::Exchanger}:* Shared, stateless synchronization point. Used
|
27
|
+
# when two or more threads need to exchange data. The threads will pair then
|
28
|
+
# block on each other until the exchange is complete.
|
29
|
+
# * *{Concurrent::MVar}:* Shared synchronization point. Used when one thread
|
30
|
+
# must give a value to another, which must take the value. The threads will
|
31
|
+
# block on each other until the exchange is complete.
|
32
|
+
# * *{Concurrent::ThreadLocalVar}:* Shared, mutable, isolated variable which
|
33
|
+
# holds a different value for each thread which has access. Often used as
|
34
|
+
# an instance variable in objects which must maintain different state
|
35
|
+
# for different threads.
|
36
|
+
# * *{Concurrent::TVar}:* Shared, mutable variables which provide
|
37
|
+
# *coordinated*, *synchronous*, change of *many* stated. Used when multiple
|
38
|
+
# value must change together, in an all-or-nothing transaction.
|
39
|
+
|
40
|
+
|
41
|
+
module Concurrent
|
42
|
+
|
43
|
+
# Atoms provide a way to manage shared, synchronous, independent state.
|
44
|
+
#
|
45
|
+
# An atom is initialized with an initial value and an optional validation
|
46
|
+
# proc. At any time the value of the atom can be synchronously and safely
|
47
|
+
# changed. If a validator is given at construction then any new value
|
48
|
+
# will be checked against the validator and will be rejected if the
|
49
|
+
# validator returns false or raises an exception.
|
50
|
+
#
|
51
|
+
# There are two ways to change the value of an atom: {#compare_and_set} and
|
52
|
+
# {#swap}. The former will set the new value if and only if it validates and
|
53
|
+
# the current value matches the new value. The latter will atomically set the
|
54
|
+
# new value to the result of running the given block if and only if that
|
55
|
+
# value validates.
|
56
|
+
#
|
57
|
+
# ## Example
|
58
|
+
#
|
59
|
+
# ```
|
60
|
+
# def next_fibonacci(set = nil)
|
61
|
+
# return [0, 1] if set.nil?
|
62
|
+
# set + [set[-2..-1].reduce{|sum,x| sum + x }]
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# # create an atom with an initial value
|
66
|
+
# atom = Concurrent::Atom.new(next_fibonacci)
|
67
|
+
#
|
68
|
+
# # send a few update requests
|
69
|
+
# 5.times do
|
70
|
+
# atom.swap{|set| next_fibonacci(set) }
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# # get the current value
|
74
|
+
# atom.value #=> [0, 1, 1, 2, 3, 5, 8]
|
75
|
+
# ```
|
76
|
+
#
|
77
|
+
# ## Observation
|
78
|
+
#
|
79
|
+
# Atoms support observers through the {Concurrent::Observable} mixin module.
|
80
|
+
# Notification of observers occurs every time the value of the Atom changes.
|
81
|
+
# When notified the observer will receive three arguments: `time`, `old_value`,
|
82
|
+
# and `new_value`. The `time` argument is the time at which the value change
|
83
|
+
# occurred. The `old_value` is the value of the Atom when the change began
|
84
|
+
# The `new_value` is the value to which the Atom was set when the change
|
85
|
+
# completed. Note that `old_value` and `new_value` may be the same. This is
|
86
|
+
# not an error. It simply means that the change operation returned the same
|
87
|
+
# value.
|
88
|
+
#
|
89
|
+
# Unlike in Clojure, `Atom` cannot participate in {Concurrent::TVar} transactions.
|
90
|
+
#
|
91
|
+
# @!macro thread_safe_variable_comparison
|
92
|
+
#
|
93
|
+
# @see http://clojure.org/atoms Clojure Atoms
|
94
|
+
# @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State
|
95
|
+
class Atom < Synchronization::Object
|
96
|
+
include Concern::Observable
|
97
|
+
|
98
|
+
safe_initialization!
|
99
|
+
attr_atomic(:value)
|
100
|
+
private :value=, :swap_value, :compare_and_set_value, :update_value
|
101
|
+
public :value
|
102
|
+
alias_method :deref, :value
|
103
|
+
|
104
|
+
# @!method value
|
105
|
+
# The current value of the atom.
|
106
|
+
#
|
107
|
+
# @return [Object] The current value.
|
108
|
+
|
109
|
+
# Create a new atom with the given initial value.
|
110
|
+
#
|
111
|
+
# @param [Object] value The initial value
|
112
|
+
# @param [Hash] opts The options used to configure the atom
|
113
|
+
# @option opts [Proc] :validator (nil) Optional proc used to validate new
|
114
|
+
# values. It must accept one and only one argument which will be the
|
115
|
+
# intended new value. The validator will return true if the new value
|
116
|
+
# is acceptable else return false (preferrably) or raise an exception.
|
117
|
+
#
|
118
|
+
# @!macro deref_options
|
119
|
+
#
|
120
|
+
# @raise [ArgumentError] if the validator is not a `Proc` (when given)
|
121
|
+
def initialize(value, opts = {})
|
122
|
+
super()
|
123
|
+
@Validator = opts.fetch(:validator, -> v { true })
|
124
|
+
self.observers = Collection::CopyOnNotifyObserverSet.new
|
125
|
+
self.value = value
|
126
|
+
end
|
127
|
+
|
128
|
+
# Atomically swaps the value of atom using the given block. The current
|
129
|
+
# value will be passed to the block, as will any arguments passed as
|
130
|
+
# arguments to the function. The new value will be validated against the
|
131
|
+
# (optional) validator proc given at construction. If validation fails the
|
132
|
+
# value will not be changed.
|
133
|
+
#
|
134
|
+
# Internally, {#swap} reads the current value, applies the block to it, and
|
135
|
+
# attempts to compare-and-set it in. Since another thread may have changed
|
136
|
+
# the value in the intervening time, it may have to retry, and does so in a
|
137
|
+
# spin loop. The net effect is that the value will always be the result of
|
138
|
+
# the application of the supplied block to a current value, atomically.
|
139
|
+
# However, because the block might be called multiple times, it must be free
|
140
|
+
# of side effects.
|
141
|
+
#
|
142
|
+
# @note The given block may be called multiple times, and thus should be free
|
143
|
+
# of side effects.
|
144
|
+
#
|
145
|
+
# @param [Object] args Zero or more arguments passed to the block.
|
146
|
+
#
|
147
|
+
# @yield [value, args] Calculates a new value for the atom based on the
|
148
|
+
# current value and any supplied arguments.
|
149
|
+
# @yieldparam value [Object] The current value of the atom.
|
150
|
+
# @yieldparam args [Object] All arguments passed to the function, in order.
|
151
|
+
# @yieldreturn [Object] The intended new value of the atom.
|
152
|
+
#
|
153
|
+
# @return [Object] The final value of the atom after all operations and
|
154
|
+
# validations are complete.
|
155
|
+
#
|
156
|
+
# @raise [ArgumentError] When no block is given.
|
157
|
+
def swap(*args)
|
158
|
+
raise ArgumentError.new('no block given') unless block_given?
|
159
|
+
|
160
|
+
loop do
|
161
|
+
old_value = value
|
162
|
+
new_value = yield(old_value, *args)
|
163
|
+
begin
|
164
|
+
break old_value unless valid?(new_value)
|
165
|
+
break new_value if compare_and_set(old_value, new_value)
|
166
|
+
rescue
|
167
|
+
break old_value
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Atomically sets the value of atom to the new value if and only if the
|
173
|
+
# current value of the atom is identical to the old value and the new
|
174
|
+
# value successfully validates against the (optional) validator given
|
175
|
+
# at construction.
|
176
|
+
#
|
177
|
+
# @param [Object] old_value The expected current value.
|
178
|
+
# @param [Object] new_value The intended new value.
|
179
|
+
#
|
180
|
+
# @return [Boolean] True if the value is changed else false.
|
181
|
+
def compare_and_set(old_value, new_value)
|
182
|
+
if valid?(new_value) && compare_and_set_value(old_value, new_value)
|
183
|
+
observers.notify_observers(Time.now, old_value, new_value)
|
184
|
+
true
|
185
|
+
else
|
186
|
+
false
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Atomically sets the value of atom to the new value without regard for the
|
191
|
+
# current value so long as the new value successfully validates against the
|
192
|
+
# (optional) validator given at construction.
|
193
|
+
#
|
194
|
+
# @param [Object] new_value The intended new value.
|
195
|
+
#
|
196
|
+
# @return [Object] The final value of the atom after all operations and
|
197
|
+
# validations are complete.
|
198
|
+
def reset(new_value)
|
199
|
+
old_value = value
|
200
|
+
if valid?(new_value)
|
201
|
+
self.value = new_value
|
202
|
+
observers.notify_observers(Time.now, old_value, new_value)
|
203
|
+
new_value
|
204
|
+
else
|
205
|
+
old_value
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
# Is the new value valid?
|
212
|
+
#
|
213
|
+
# @param [Object] new_value The intended new value.
|
214
|
+
# @return [Boolean] false if the validator function returns false or raises
|
215
|
+
# an exception else true
|
216
|
+
def valid?(new_value)
|
217
|
+
@Validator.call(new_value)
|
218
|
+
rescue
|
219
|
+
false
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|