concurrent-ruby 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +478 -0
- data/Gemfile +41 -0
- data/LICENSE.md +23 -0
- data/README.md +381 -0
- data/Rakefile +327 -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 +159 -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.rb +1 -0
- data/lib/concurrent.rb +134 -0
- data/lib/concurrent/agent.rb +587 -0
- data/lib/concurrent/array.rb +66 -0
- data/lib/concurrent/async.rb +459 -0
- data/lib/concurrent/atom.rb +222 -0
- data/lib/concurrent/atomic/abstract_thread_local_var.rb +66 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +126 -0
- data/lib/concurrent/atomic/atomic_fixnum.rb +143 -0
- data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
- data/lib/concurrent/atomic/atomic_reference.rb +204 -0
- data/lib/concurrent/atomic/count_down_latch.rb +100 -0
- data/lib/concurrent/atomic/cyclic_barrier.rb +128 -0
- data/lib/concurrent/atomic/event.rb +109 -0
- data/lib/concurrent/atomic/java_count_down_latch.rb +42 -0
- data/lib/concurrent/atomic/java_thread_local_var.rb +37 -0
- data/lib/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
- data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
- data/lib/concurrent/atomic/mutex_count_down_latch.rb +44 -0
- data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
- data/lib/concurrent/atomic/read_write_lock.rb +254 -0
- data/lib/concurrent/atomic/reentrant_read_write_lock.rb +379 -0
- data/lib/concurrent/atomic/ruby_thread_local_var.rb +161 -0
- data/lib/concurrent/atomic/semaphore.rb +145 -0
- data/lib/concurrent/atomic/thread_local_var.rb +104 -0
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +56 -0
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
- data/lib/concurrent/atomics.rb +10 -0
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +111 -0
- data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
- data/lib/concurrent/collection/lock_free_stack.rb +158 -0
- data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
- data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
- data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
- data/lib/concurrent/collection/map/synchronized_map_backend.rb +82 -0
- data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
- data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
- data/lib/concurrent/concern/deprecation.rb +34 -0
- data/lib/concurrent/concern/dereferenceable.rb +73 -0
- data/lib/concurrent/concern/logging.rb +32 -0
- data/lib/concurrent/concern/obligation.rb +220 -0
- data/lib/concurrent/concern/observable.rb +110 -0
- data/lib/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent/configuration.rb +184 -0
- data/lib/concurrent/constants.rb +8 -0
- data/lib/concurrent/dataflow.rb +81 -0
- data/lib/concurrent/delay.rb +199 -0
- data/lib/concurrent/errors.rb +69 -0
- data/lib/concurrent/exchanger.rb +352 -0
- data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
- data/lib/concurrent/executor/cached_thread_pool.rb +62 -0
- data/lib/concurrent/executor/executor_service.rb +185 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +206 -0
- data/lib/concurrent/executor/immediate_executor.rb +66 -0
- data/lib/concurrent/executor/indirect_immediate_executor.rb +44 -0
- data/lib/concurrent/executor/java_executor_service.rb +91 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +29 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +123 -0
- data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +22 -0
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +362 -0
- data/lib/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent/executor/serial_executor_service.rb +34 -0
- data/lib/concurrent/executor/serialized_execution.rb +107 -0
- data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
- data/lib/concurrent/executor/simple_executor_service.rb +100 -0
- data/lib/concurrent/executor/single_thread_executor.rb +56 -0
- data/lib/concurrent/executor/thread_pool_executor.rb +87 -0
- data/lib/concurrent/executor/timer_set.rb +173 -0
- data/lib/concurrent/executors.rb +20 -0
- data/lib/concurrent/future.rb +141 -0
- data/lib/concurrent/hash.rb +59 -0
- data/lib/concurrent/immutable_struct.rb +93 -0
- data/lib/concurrent/ivar.rb +207 -0
- data/lib/concurrent/map.rb +337 -0
- data/lib/concurrent/maybe.rb +229 -0
- data/lib/concurrent/mutable_struct.rb +229 -0
- data/lib/concurrent/mvar.rb +242 -0
- data/lib/concurrent/options.rb +42 -0
- data/lib/concurrent/promise.rb +579 -0
- data/lib/concurrent/promises.rb +2167 -0
- data/lib/concurrent/re_include.rb +58 -0
- data/lib/concurrent/scheduled_task.rb +318 -0
- data/lib/concurrent/set.rb +66 -0
- data/lib/concurrent/settable_struct.rb +129 -0
- data/lib/concurrent/synchronization.rb +30 -0
- data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
- data/lib/concurrent/synchronization/abstract_object.rb +24 -0
- data/lib/concurrent/synchronization/abstract_struct.rb +160 -0
- data/lib/concurrent/synchronization/condition.rb +60 -0
- data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
- data/lib/concurrent/synchronization/jruby_object.rb +45 -0
- data/lib/concurrent/synchronization/lock.rb +36 -0
- data/lib/concurrent/synchronization/lockable_object.rb +74 -0
- data/lib/concurrent/synchronization/mri_object.rb +44 -0
- data/lib/concurrent/synchronization/mutex_lockable_object.rb +76 -0
- data/lib/concurrent/synchronization/object.rb +183 -0
- data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
- data/lib/concurrent/synchronization/rbx_object.rb +49 -0
- data/lib/concurrent/synchronization/truffleruby_object.rb +47 -0
- data/lib/concurrent/synchronization/volatile.rb +36 -0
- data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
- data/lib/concurrent/thread_safe/util.rb +16 -0
- data/lib/concurrent/thread_safe/util/adder.rb +74 -0
- data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
- data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
- data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
- data/lib/concurrent/thread_safe/util/striped64.rb +246 -0
- data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
- data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
- data/lib/concurrent/timer_task.rb +334 -0
- data/lib/concurrent/tuple.rb +86 -0
- data/lib/concurrent/tvar.rb +258 -0
- data/lib/concurrent/utility/at_exit.rb +97 -0
- data/lib/concurrent/utility/engine.rb +56 -0
- data/lib/concurrent/utility/monotonic_time.rb +58 -0
- data/lib/concurrent/utility/native_extension_loader.rb +79 -0
- data/lib/concurrent/utility/native_integer.rb +53 -0
- data/lib/concurrent/utility/processor_counter.rb +158 -0
- data/lib/concurrent/version.rb +3 -0
- metadata +193 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
require 'concurrent/thread_safe/util'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# @!macro concurrent_array
|
7
|
+
#
|
8
|
+
# A thread-safe subclass of Array. This version locks against the object
|
9
|
+
# itself for every method call, ensuring only one thread can be reading
|
10
|
+
# or writing at a time. This includes iteration methods like `#each`.
|
11
|
+
#
|
12
|
+
# @note `a += b` is **not** a **thread-safe** operation on
|
13
|
+
# `Concurrent::Array`. It reads array `a`, then it creates new `Concurrent::Array`
|
14
|
+
# which is concatenation of `a` and `b`, then it writes the concatenation to `a`.
|
15
|
+
# The read and write are independent operations they do not form a single atomic
|
16
|
+
# operation therefore when two `+=` operations are executed concurrently updates
|
17
|
+
# may be lost. Use `#concat` instead.
|
18
|
+
#
|
19
|
+
# @see http://ruby-doc.org/core-2.2.0/Array.html Ruby standard library `Array`
|
20
|
+
|
21
|
+
# @!macro internal_implementation_note
|
22
|
+
ArrayImplementation = case
|
23
|
+
when Concurrent.on_cruby?
|
24
|
+
# Array is thread-safe in practice because CRuby runs
|
25
|
+
# threads one at a time and does not do context
|
26
|
+
# switching during the execution of C functions.
|
27
|
+
::Array
|
28
|
+
|
29
|
+
when Concurrent.on_jruby?
|
30
|
+
require 'jruby/synchronized'
|
31
|
+
|
32
|
+
class JRubyArray < ::Array
|
33
|
+
include JRuby::Synchronized
|
34
|
+
end
|
35
|
+
JRubyArray
|
36
|
+
|
37
|
+
when Concurrent.on_rbx?
|
38
|
+
require 'monitor'
|
39
|
+
require 'concurrent/thread_safe/util/data_structures'
|
40
|
+
|
41
|
+
class RbxArray < ::Array
|
42
|
+
end
|
43
|
+
|
44
|
+
ThreadSafe::Util.make_synchronized_on_rbx RbxArray
|
45
|
+
RbxArray
|
46
|
+
|
47
|
+
when Concurrent.on_truffleruby?
|
48
|
+
require 'concurrent/thread_safe/util/data_structures'
|
49
|
+
|
50
|
+
class TruffleRubyArray < ::Array
|
51
|
+
end
|
52
|
+
|
53
|
+
ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyArray
|
54
|
+
TruffleRubyArray
|
55
|
+
|
56
|
+
else
|
57
|
+
warn 'Possibly unsupported Ruby implementation'
|
58
|
+
::Array
|
59
|
+
end
|
60
|
+
private_constant :ArrayImplementation
|
61
|
+
|
62
|
+
# @!macro concurrent_array
|
63
|
+
class Array < ArrayImplementation
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,459 @@
|
|
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
|
+
# When defining a constructor it is critical that the first line be a call to
|
62
|
+
# `super` with no arguments. The `super` method initializes the background
|
63
|
+
# thread and other asynchronous components.
|
64
|
+
#
|
65
|
+
# ```
|
66
|
+
# class BackgroundLogger
|
67
|
+
# include Concurrent::Async
|
68
|
+
#
|
69
|
+
# def initialize(level)
|
70
|
+
# super()
|
71
|
+
# @logger = Logger.new(STDOUT)
|
72
|
+
# @logger.level = level
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# def info(msg)
|
76
|
+
# @logger.info(msg)
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
# ```
|
80
|
+
#
|
81
|
+
# Mixing this module into a class provides each object two proxy methods:
|
82
|
+
# `async` and `await`. These methods are thread safe with respect to the
|
83
|
+
# enclosing object. The former proxy allows methods to be called
|
84
|
+
# asynchronously by posting to the object's internal thread. The latter proxy
|
85
|
+
# allows a method to be called synchronously but does so safely with respect
|
86
|
+
# to any pending asynchronous method calls and ensures proper ordering. Both
|
87
|
+
# methods return a {Concurrent::IVar} which can be inspected for the result
|
88
|
+
# of the proxied method call. Calling a method with `async` will return a
|
89
|
+
# `:pending` `IVar` whereas `await` will return a `:complete` `IVar`.
|
90
|
+
#
|
91
|
+
# ```
|
92
|
+
# class Echo
|
93
|
+
# include Concurrent::Async
|
94
|
+
#
|
95
|
+
# def echo(msg)
|
96
|
+
# print "#{msg}\n"
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# horn = Echo.new
|
101
|
+
# horn.echo('zero') # synchronous, not thread-safe
|
102
|
+
# # returns the actual return value of the method
|
103
|
+
#
|
104
|
+
# horn.async.echo('one') # asynchronous, non-blocking, thread-safe
|
105
|
+
# # returns an IVar in the :pending state
|
106
|
+
#
|
107
|
+
# horn.await.echo('two') # synchronous, blocking, thread-safe
|
108
|
+
# # returns an IVar in the :complete state
|
109
|
+
# ```
|
110
|
+
#
|
111
|
+
# ## Let It Fail
|
112
|
+
#
|
113
|
+
# The `async` and `await` proxy methods have built-in error protection based
|
114
|
+
# on Erlang's famous "let it fail" philosophy. Instance methods should not be
|
115
|
+
# programmed defensively. When an exception is raised by a delegated method
|
116
|
+
# the proxy will rescue the exception, expose it to the caller as the `reason`
|
117
|
+
# attribute of the returned future, then process the next method call.
|
118
|
+
#
|
119
|
+
# ## Calling Methods Internally
|
120
|
+
#
|
121
|
+
# External method calls should *always* use the `async` and `await` proxy
|
122
|
+
# methods. When one method calls another method, the `async` proxy should
|
123
|
+
# rarely be used and the `await` proxy should *never* be used.
|
124
|
+
#
|
125
|
+
# When an object calls one of its own methods using the `await` proxy the
|
126
|
+
# second call will be enqueued *behind* the currently running method call.
|
127
|
+
# Any attempt to wait on the result will fail as the second call will never
|
128
|
+
# run until after the current call completes.
|
129
|
+
#
|
130
|
+
# Calling a method using the `await` proxy from within a method that was
|
131
|
+
# itself called using `async` or `await` will irreversibly deadlock the
|
132
|
+
# object. Do *not* do this, ever.
|
133
|
+
#
|
134
|
+
# ## Instance Variables and Attribute Accessors
|
135
|
+
#
|
136
|
+
# Instance variables do not need to be thread-safe so long as they are private.
|
137
|
+
# Asynchronous method calls are processed in the order they are received and
|
138
|
+
# are processed one at a time. Therefore private instance variables can only
|
139
|
+
# be accessed by one thread at a time. This is inherently thread-safe.
|
140
|
+
#
|
141
|
+
# When using private instance variables within asynchronous methods, the best
|
142
|
+
# practice is to read the instance variable into a local variable at the start
|
143
|
+
# of the method then update the instance variable at the *end* of the method.
|
144
|
+
# This way, should an exception be raised during method execution the internal
|
145
|
+
# state of the object will not have been changed.
|
146
|
+
#
|
147
|
+
# ### Reader Attributes
|
148
|
+
#
|
149
|
+
# The use of `attr_reader` is discouraged. Internal state exposed externally,
|
150
|
+
# when necessary, should be done through accessor methods. The instance
|
151
|
+
# variables exposed by these methods *must* be thread-safe, or they must be
|
152
|
+
# called using the `async` and `await` proxy methods. These two approaches are
|
153
|
+
# subtly different.
|
154
|
+
#
|
155
|
+
# When internal state is accessed via the `async` and `await` proxy methods,
|
156
|
+
# the returned value represents the object's state *at the time the call is
|
157
|
+
# processed*, which may *not* be the state of the object at the time the call
|
158
|
+
# is made.
|
159
|
+
#
|
160
|
+
# To get the state *at the current* time, irrespective of an enqueued method
|
161
|
+
# calls, a reader method must be called directly. This is inherently unsafe
|
162
|
+
# unless the instance variable is itself thread-safe, preferably using one
|
163
|
+
# of the thread-safe classes within this library. Because the thread-safe
|
164
|
+
# classes within this library are internally-locking or non-locking, they can
|
165
|
+
# be safely used from within asynchronous methods without causing deadlocks.
|
166
|
+
#
|
167
|
+
# Generally speaking, the best practice is to *not* expose internal state via
|
168
|
+
# reader methods. The best practice is to simply use the method's return value.
|
169
|
+
#
|
170
|
+
# ### Writer Attributes
|
171
|
+
#
|
172
|
+
# Writer attributes should never be used with asynchronous classes. Changing
|
173
|
+
# the state externally, even when done in the thread-safe way, is not logically
|
174
|
+
# consistent. Changes to state need to be timed with respect to all asynchronous
|
175
|
+
# method calls which my be in-process or enqueued. The only safe practice is to
|
176
|
+
# pass all necessary data to each method as arguments and let the method update
|
177
|
+
# the internal state as necessary.
|
178
|
+
#
|
179
|
+
# ## Class Constants, Variables, and Methods
|
180
|
+
#
|
181
|
+
# ### Class Constants
|
182
|
+
#
|
183
|
+
# Class constants do not need to be thread-safe. Since they are read-only and
|
184
|
+
# immutable they may be safely read both externally and from within
|
185
|
+
# asynchronous methods.
|
186
|
+
#
|
187
|
+
# ### Class Variables
|
188
|
+
#
|
189
|
+
# Class variables should be avoided. Class variables represent shared state.
|
190
|
+
# Shared state is anathema to concurrency. Should there be a need to share
|
191
|
+
# state using class variables they *must* be thread-safe, preferably
|
192
|
+
# using the thread-safe classes within this library. When updating class
|
193
|
+
# variables, never assign a new value/object to the variable itself. Assignment
|
194
|
+
# is not thread-safe in Ruby. Instead, use the thread-safe update functions
|
195
|
+
# of the variable itself to change the value.
|
196
|
+
#
|
197
|
+
# The best practice is to *never* use class variables with `Async` classes.
|
198
|
+
#
|
199
|
+
# ### Class Methods
|
200
|
+
#
|
201
|
+
# Class methods which are pure functions are safe. Class methods which modify
|
202
|
+
# class variables should be avoided, for all the reasons listed above.
|
203
|
+
#
|
204
|
+
# ## An Important Note About Thread Safe Guarantees
|
205
|
+
#
|
206
|
+
# > Thread safe guarantees can only be made when asynchronous method calls
|
207
|
+
# > are not mixed with direct method calls. Use only direct method calls
|
208
|
+
# > when the object is used exclusively on a single thread. Use only
|
209
|
+
# > `async` and `await` when the object is shared between threads. Once you
|
210
|
+
# > call a method using `async` or `await`, you should no longer call methods
|
211
|
+
# > directly on the object. Use `async` and `await` exclusively from then on.
|
212
|
+
#
|
213
|
+
# @example
|
214
|
+
#
|
215
|
+
# class Echo
|
216
|
+
# include Concurrent::Async
|
217
|
+
#
|
218
|
+
# def echo(msg)
|
219
|
+
# print "#{msg}\n"
|
220
|
+
# end
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# horn = Echo.new
|
224
|
+
# horn.echo('zero') # synchronous, not thread-safe
|
225
|
+
# # returns the actual return value of the method
|
226
|
+
#
|
227
|
+
# horn.async.echo('one') # asynchronous, non-blocking, thread-safe
|
228
|
+
# # returns an IVar in the :pending state
|
229
|
+
#
|
230
|
+
# horn.await.echo('two') # synchronous, blocking, thread-safe
|
231
|
+
# # returns an IVar in the :complete state
|
232
|
+
#
|
233
|
+
# @see Concurrent::Actor
|
234
|
+
# @see https://en.wikipedia.org/wiki/Actor_model "Actor Model" at Wikipedia
|
235
|
+
# @see http://www.erlang.org/doc/man/gen_server.html Erlang gen_server
|
236
|
+
# @see http://c2.com/cgi/wiki?LetItCrash "Let It Crash" at http://c2.com/
|
237
|
+
module Async
|
238
|
+
|
239
|
+
# @!method self.new(*args, &block)
|
240
|
+
#
|
241
|
+
# Instanciate a new object and ensure proper initialization of the
|
242
|
+
# synchronization mechanisms.
|
243
|
+
#
|
244
|
+
# @param [Array<Object>] args Zero or more arguments to be passed to the
|
245
|
+
# object's initializer.
|
246
|
+
# @param [Proc] block Optional block to pass to the object's initializer.
|
247
|
+
# @return [Object] A properly initialized object of the asynchronous class.
|
248
|
+
|
249
|
+
# Check for the presence of a method on an object and determine if a given
|
250
|
+
# set of arguments matches the required arity.
|
251
|
+
#
|
252
|
+
# @param [Object] obj the object to check against
|
253
|
+
# @param [Symbol] method the method to check the object for
|
254
|
+
# @param [Array] args zero or more arguments for the arity check
|
255
|
+
#
|
256
|
+
# @raise [NameError] the object does not respond to `method` method
|
257
|
+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
258
|
+
#
|
259
|
+
# @note This check is imperfect because of the way Ruby reports the arity of
|
260
|
+
# methods with a variable number of arguments. It is possible to determine
|
261
|
+
# if too few arguments are given but impossible to determine if too many
|
262
|
+
# arguments are given. This check may also fail to recognize dynamic behavior
|
263
|
+
# of the object, such as methods simulated with `method_missing`.
|
264
|
+
#
|
265
|
+
# @see http://www.ruby-doc.org/core-2.1.1/Method.html#method-i-arity Method#arity
|
266
|
+
# @see http://ruby-doc.org/core-2.1.0/Object.html#method-i-respond_to-3F Object#respond_to?
|
267
|
+
# @see http://www.ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing BasicObject#method_missing
|
268
|
+
#
|
269
|
+
# @!visibility private
|
270
|
+
def self.validate_argc(obj, method, *args)
|
271
|
+
argc = args.length
|
272
|
+
arity = obj.method(method).arity
|
273
|
+
|
274
|
+
if arity >= 0 && argc != arity
|
275
|
+
raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity})")
|
276
|
+
elsif arity < 0 && (arity = (arity + 1).abs) > argc
|
277
|
+
raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity}..*)")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# @!visibility private
|
282
|
+
def self.included(base)
|
283
|
+
base.singleton_class.send(:alias_method, :original_new, :new)
|
284
|
+
base.extend(ClassMethods)
|
285
|
+
super(base)
|
286
|
+
end
|
287
|
+
|
288
|
+
# @!visibility private
|
289
|
+
module ClassMethods
|
290
|
+
def new(*args, &block)
|
291
|
+
obj = original_new(*args, &block)
|
292
|
+
obj.send(:init_synchronization)
|
293
|
+
obj
|
294
|
+
end
|
295
|
+
end
|
296
|
+
private_constant :ClassMethods
|
297
|
+
|
298
|
+
# Delegates asynchronous, thread-safe method calls to the wrapped object.
|
299
|
+
#
|
300
|
+
# @!visibility private
|
301
|
+
class AsyncDelegator < Synchronization::LockableObject
|
302
|
+
safe_initialization!
|
303
|
+
|
304
|
+
# Create a new delegator object wrapping the given delegate.
|
305
|
+
#
|
306
|
+
# @param [Object] delegate the object to wrap and delegate method calls to
|
307
|
+
def initialize(delegate)
|
308
|
+
super()
|
309
|
+
@delegate = delegate
|
310
|
+
@queue = []
|
311
|
+
@executor = Concurrent.global_io_executor
|
312
|
+
end
|
313
|
+
|
314
|
+
# Delegates method calls to the wrapped object.
|
315
|
+
#
|
316
|
+
# @param [Symbol] method the method being called
|
317
|
+
# @param [Array] args zero or more arguments to the method
|
318
|
+
#
|
319
|
+
# @return [IVar] the result of the method call
|
320
|
+
#
|
321
|
+
# @raise [NameError] the object does not respond to `method` method
|
322
|
+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
323
|
+
def method_missing(method, *args, &block)
|
324
|
+
super unless @delegate.respond_to?(method)
|
325
|
+
Async::validate_argc(@delegate, method, *args)
|
326
|
+
|
327
|
+
ivar = Concurrent::IVar.new
|
328
|
+
synchronize do
|
329
|
+
@queue.push [ivar, method, args, block]
|
330
|
+
@executor.post { perform } if @queue.length == 1
|
331
|
+
end
|
332
|
+
|
333
|
+
ivar
|
334
|
+
end
|
335
|
+
|
336
|
+
# Check whether the method is responsive
|
337
|
+
#
|
338
|
+
# @param [Symbol] method the method being called
|
339
|
+
def respond_to_missing?(method, include_private = false)
|
340
|
+
@delegate.respond_to?(method) || super
|
341
|
+
end
|
342
|
+
|
343
|
+
# Perform all enqueued tasks.
|
344
|
+
#
|
345
|
+
# This method must be called from within the executor. It must not be
|
346
|
+
# called while already running. It will loop until the queue is empty.
|
347
|
+
def perform
|
348
|
+
loop do
|
349
|
+
ivar, method, args, block = synchronize { @queue.first }
|
350
|
+
break unless ivar # queue is empty
|
351
|
+
|
352
|
+
begin
|
353
|
+
ivar.set(@delegate.send(method, *args, &block))
|
354
|
+
rescue => error
|
355
|
+
ivar.fail(error)
|
356
|
+
end
|
357
|
+
|
358
|
+
synchronize do
|
359
|
+
@queue.shift
|
360
|
+
return if @queue.empty?
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
private_constant :AsyncDelegator
|
366
|
+
|
367
|
+
# Delegates synchronous, thread-safe method calls to the wrapped object.
|
368
|
+
#
|
369
|
+
# @!visibility private
|
370
|
+
class AwaitDelegator
|
371
|
+
|
372
|
+
# Create a new delegator object wrapping the given delegate.
|
373
|
+
#
|
374
|
+
# @param [AsyncDelegator] delegate the object to wrap and delegate method calls to
|
375
|
+
def initialize(delegate)
|
376
|
+
@delegate = delegate
|
377
|
+
end
|
378
|
+
|
379
|
+
# Delegates method calls to the wrapped object.
|
380
|
+
#
|
381
|
+
# @param [Symbol] method the method being called
|
382
|
+
# @param [Array] args zero or more arguments to the method
|
383
|
+
#
|
384
|
+
# @return [IVar] the result of the method call
|
385
|
+
#
|
386
|
+
# @raise [NameError] the object does not respond to `method` method
|
387
|
+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
388
|
+
def method_missing(method, *args, &block)
|
389
|
+
ivar = @delegate.send(method, *args, &block)
|
390
|
+
ivar.wait
|
391
|
+
ivar
|
392
|
+
end
|
393
|
+
|
394
|
+
# Check whether the method is responsive
|
395
|
+
#
|
396
|
+
# @param [Symbol] method the method being called
|
397
|
+
def respond_to_missing?(method, include_private = false)
|
398
|
+
@delegate.respond_to?(method) || super
|
399
|
+
end
|
400
|
+
end
|
401
|
+
private_constant :AwaitDelegator
|
402
|
+
|
403
|
+
# Causes the chained method call to be performed asynchronously on the
|
404
|
+
# object's thread. The delegated method will return a future in the
|
405
|
+
# `:pending` state and the method call will have been scheduled on the
|
406
|
+
# object's thread. The final disposition of the method call can be obtained
|
407
|
+
# by inspecting the returned future.
|
408
|
+
#
|
409
|
+
# @!macro async_thread_safety_warning
|
410
|
+
# @note The method call is guaranteed to be thread safe with respect to
|
411
|
+
# all other method calls against the same object that are called with
|
412
|
+
# either `async` or `await`. The mutable nature of Ruby references
|
413
|
+
# (and object orientation in general) prevent any other thread safety
|
414
|
+
# guarantees. Do NOT mix direct method calls with delegated method calls.
|
415
|
+
# Use *only* delegated method calls when sharing the object between threads.
|
416
|
+
#
|
417
|
+
# @return [Concurrent::IVar] the pending result of the asynchronous operation
|
418
|
+
#
|
419
|
+
# @raise [NameError] the object does not respond to the requested method
|
420
|
+
# @raise [ArgumentError] the given `args` do not match the arity of
|
421
|
+
# the requested method
|
422
|
+
def async
|
423
|
+
@__async_delegator__
|
424
|
+
end
|
425
|
+
alias_method :cast, :async
|
426
|
+
|
427
|
+
# Causes the chained method call to be performed synchronously on the
|
428
|
+
# current thread. The delegated will return a future in either the
|
429
|
+
# `:fulfilled` or `:rejected` state and the delegated method will have
|
430
|
+
# completed. The final disposition of the delegated method can be obtained
|
431
|
+
# by inspecting the returned future.
|
432
|
+
#
|
433
|
+
# @!macro async_thread_safety_warning
|
434
|
+
#
|
435
|
+
# @return [Concurrent::IVar] the completed result of the synchronous operation
|
436
|
+
#
|
437
|
+
# @raise [NameError] the object does not respond to the requested method
|
438
|
+
# @raise [ArgumentError] the given `args` do not match the arity of the
|
439
|
+
# requested method
|
440
|
+
def await
|
441
|
+
@__await_delegator__
|
442
|
+
end
|
443
|
+
alias_method :call, :await
|
444
|
+
|
445
|
+
# Initialize the internal serializer and other stnchronization mechanisms.
|
446
|
+
#
|
447
|
+
# @note This method *must* be called immediately upon object construction.
|
448
|
+
# This is the only way thread-safe initialization can be guaranteed.
|
449
|
+
#
|
450
|
+
# @!visibility private
|
451
|
+
def init_synchronization
|
452
|
+
return self if defined?(@__async_initialized__) && @__async_initialized__
|
453
|
+
@__async_initialized__ = true
|
454
|
+
@__async_delegator__ = AsyncDelegator.new(self)
|
455
|
+
@__await_delegator__ = AwaitDelegator.new(@__async_delegator__)
|
456
|
+
self
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|