concurrent-ruby 0.9.2-java → 1.0.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -1
- data/README.md +86 -120
- data/lib/concurrent.rb +14 -5
- data/lib/concurrent/agent.rb +587 -0
- data/lib/concurrent/array.rb +39 -0
- data/lib/concurrent/async.rb +296 -149
- data/lib/concurrent/atom.rb +135 -45
- data/lib/concurrent/atomic/abstract_thread_local_var.rb +38 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +83 -118
- data/lib/concurrent/atomic/atomic_fixnum.rb +101 -163
- data/lib/concurrent/atomic/atomic_reference.rb +1 -8
- data/lib/concurrent/atomic/count_down_latch.rb +62 -103
- data/lib/concurrent/atomic/cyclic_barrier.rb +3 -1
- data/lib/concurrent/atomic/event.rb +1 -1
- data/lib/concurrent/atomic/java_count_down_latch.rb +39 -0
- data/lib/concurrent/atomic/java_thread_local_var.rb +50 -0
- data/lib/concurrent/atomic/mutex_atomic_boolean.rb +60 -0
- data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +91 -0
- data/lib/concurrent/atomic/mutex_count_down_latch.rb +43 -0
- data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
- data/lib/concurrent/atomic/read_write_lock.rb +5 -4
- data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
- data/lib/concurrent/atomic/ruby_thread_local_var.rb +172 -0
- data/lib/concurrent/atomic/semaphore.rb +84 -178
- data/lib/concurrent/atomic/thread_local_var.rb +65 -294
- data/lib/concurrent/atomic_reference/jruby+truffle.rb +1 -0
- data/lib/concurrent/atomic_reference/jruby.rb +1 -1
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +14 -8
- data/lib/concurrent/atomic_reference/ruby.rb +1 -1
- data/lib/concurrent/atomics.rb +7 -37
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +7 -15
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +7 -15
- data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -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 +144 -0
- data/lib/concurrent/collection/map/synchronized_map_backend.rb +86 -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/dereferenceable.rb +9 -24
- data/lib/concurrent/concern/logging.rb +1 -1
- data/lib/concurrent/concern/obligation.rb +11 -20
- data/lib/concurrent/concern/observable.rb +38 -13
- data/lib/concurrent/configuration.rb +23 -152
- data/lib/concurrent/constants.rb +8 -0
- data/lib/concurrent/delay.rb +14 -12
- data/lib/concurrent/exchanger.rb +339 -41
- data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
- data/lib/concurrent/executor/executor_service.rb +23 -359
- data/lib/concurrent/executor/immediate_executor.rb +3 -2
- data/lib/concurrent/executor/java_executor_service.rb +100 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +3 -3
- data/lib/concurrent/executor/java_thread_pool_executor.rb +3 -4
- data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +10 -66
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +25 -22
- data/lib/concurrent/executor/safe_task_executor.rb +6 -7
- data/lib/concurrent/executor/serial_executor_service.rb +34 -0
- data/lib/concurrent/executor/serialized_execution.rb +10 -33
- data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
- data/lib/concurrent/executor/simple_executor_service.rb +1 -10
- data/lib/concurrent/executor/single_thread_executor.rb +20 -10
- data/lib/concurrent/executor/timer_set.rb +8 -10
- data/lib/concurrent/executors.rb +12 -2
- data/lib/concurrent/future.rb +6 -4
- data/lib/concurrent/hash.rb +35 -0
- data/lib/concurrent/immutable_struct.rb +5 -1
- data/lib/concurrent/ivar.rb +12 -16
- data/lib/concurrent/lazy_register.rb +11 -8
- data/lib/concurrent/map.rb +180 -0
- data/lib/concurrent/maybe.rb +6 -3
- data/lib/concurrent/mutable_struct.rb +7 -6
- data/lib/concurrent/mvar.rb +26 -2
- data/lib/concurrent/{executor/executor.rb → options.rb} +5 -29
- data/lib/concurrent/promise.rb +7 -5
- data/lib/concurrent/scheduled_task.rb +13 -71
- data/lib/concurrent/settable_struct.rb +5 -4
- data/lib/concurrent/synchronization.rb +15 -3
- data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
- data/lib/concurrent/synchronization/abstract_object.rb +7 -146
- data/lib/concurrent/synchronization/abstract_struct.rb +2 -3
- data/lib/concurrent/synchronization/condition.rb +6 -4
- data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
- data/lib/concurrent/synchronization/jruby_object.rb +44 -0
- data/lib/concurrent/synchronization/lock.rb +3 -2
- data/lib/concurrent/synchronization/lockable_object.rb +72 -0
- data/lib/concurrent/synchronization/mri_lockable_object.rb +71 -0
- data/lib/concurrent/synchronization/mri_object.rb +43 -0
- data/lib/concurrent/synchronization/object.rb +140 -73
- data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
- data/lib/concurrent/synchronization/rbx_object.rb +30 -73
- data/lib/concurrent/synchronization/volatile.rb +34 -0
- data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
- data/lib/concurrent/thread_safe/util.rb +14 -0
- data/lib/concurrent/thread_safe/util/adder.rb +74 -0
- data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +30 -0
- data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
- data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
- data/lib/concurrent/thread_safe/util/striped64.rb +241 -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 +3 -4
- data/lib/concurrent/tuple.rb +86 -0
- data/lib/concurrent/tvar.rb +5 -1
- data/lib/concurrent/utility/at_exit.rb +1 -1
- data/lib/concurrent/utility/engine.rb +4 -0
- data/lib/concurrent/utility/monotonic_time.rb +3 -4
- data/lib/concurrent/utility/native_extension_loader.rb +50 -30
- data/lib/concurrent/version.rb +2 -2
- data/lib/concurrent_ruby_ext.jar +0 -0
- metadata +47 -12
- data/lib/concurrent/atomic/condition.rb +0 -78
- data/lib/concurrent/collection/priority_queue.rb +0 -360
- data/lib/concurrent/synchronization/java_object.rb +0 -34
- data/lib/concurrent/synchronization/monitor_object.rb +0 -27
- data/lib/concurrent/synchronization/mutex_object.rb +0 -43
- data/lib/concurrent/utilities.rb +0 -5
- data/lib/concurrent/utility/timeout.rb +0 -39
- data/lib/concurrent/utility/timer.rb +0 -26
- data/lib/concurrent_ruby.rb +0 -2
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'concurrent/utility/engine'
|
2
|
+
require 'concurrent/thread_safe/util'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
if Concurrent.on_cruby?
|
6
|
+
|
7
|
+
# Because MRI never runs code in parallel, the existing
|
8
|
+
# non-thread-safe structures should usually work fine.
|
9
|
+
|
10
|
+
# @!macro [attach] concurrent_array
|
11
|
+
#
|
12
|
+
# A thread-safe subclass of Array. This version locks against the object
|
13
|
+
# itself for every method call, ensuring only one thread can be reading
|
14
|
+
# or writing at a time. This includes iteration methods like `#each`.
|
15
|
+
#
|
16
|
+
# @see http://ruby-doc.org/core-2.2.0/Array.html Ruby standard library `Array`
|
17
|
+
class Array < ::Array;
|
18
|
+
end
|
19
|
+
|
20
|
+
elsif Concurrent.on_jruby?
|
21
|
+
require 'jruby/synchronized'
|
22
|
+
|
23
|
+
# @!macro concurrent_array
|
24
|
+
class Array < ::Array
|
25
|
+
include JRuby::Synchronized
|
26
|
+
end
|
27
|
+
|
28
|
+
elsif Concurrent.on_rbx?
|
29
|
+
require 'monitor'
|
30
|
+
require 'concurrent/thread_safe/util/array_hash_rbx'
|
31
|
+
|
32
|
+
# @!macro concurrent_array
|
33
|
+
class Array < ::Array
|
34
|
+
end
|
35
|
+
|
36
|
+
ThreadSafe::Util.make_synchronized_on_rbx Array
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
data/lib/concurrent/async.rb
CHANGED
@@ -1,73 +1,239 @@
|
|
1
|
-
require 'thread'
|
2
1
|
require 'concurrent/configuration'
|
3
|
-
require 'concurrent/delay'
|
4
|
-
require 'concurrent/errors'
|
5
2
|
require 'concurrent/ivar'
|
6
|
-
require 'concurrent/
|
7
|
-
require 'concurrent/executor/serialized_execution'
|
8
|
-
require 'concurrent/concern/deprecation'
|
3
|
+
require 'concurrent/synchronization/lockable_object'
|
9
4
|
|
10
5
|
module Concurrent
|
11
6
|
|
12
|
-
# A mixin module that provides simple asynchronous behavior to
|
13
|
-
#
|
14
|
-
#
|
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
15
|
# ```cucumber
|
16
16
|
# Feature:
|
17
|
-
# As a stateful, plain old Ruby class
|
17
|
+
# As a stateful, plain old Ruby class
|
18
18
|
# I want safe, asynchronous behavior
|
19
19
|
# So my long-running methods don't block the main thread
|
20
20
|
# ```
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
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
|
+
# reurns 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 critica 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
|
36
89
|
# `:pending` `IVar` whereas `await` will return a `:complete` `IVar`.
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
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 boject 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 sate *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, preferrably 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, preferrably
|
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
|
+
#
|
42
206
|
# > Thread safe guarantees can only be made when asynchronous method calls
|
43
|
-
# > are not mixed with
|
207
|
+
# > are not mixed with direct method calls. Use only direct method calls
|
44
208
|
# > when the object is used exclusively on a single thread. Use only
|
45
209
|
# > `async` and `await` when the object is shared between threads. Once you
|
46
|
-
# > call a method using `async`, you should no longer call
|
210
|
+
# > call a method using `async` or `await`, you should no longer call methods
|
47
211
|
# > directly on the object. Use `async` and `await` exclusively from then on.
|
48
|
-
#
|
49
|
-
# > also very easy to create race conditions and break your application.
|
50
|
-
# > Basically, it's "async all the way down."
|
51
|
-
#
|
212
|
+
#
|
52
213
|
# @example
|
53
|
-
#
|
214
|
+
#
|
54
215
|
# class Echo
|
55
216
|
# include Concurrent::Async
|
56
|
-
#
|
217
|
+
#
|
57
218
|
# def echo(msg)
|
58
|
-
# sleep(rand)
|
59
219
|
# print "#{msg}\n"
|
60
|
-
# nil
|
61
220
|
# end
|
62
221
|
# end
|
63
|
-
#
|
222
|
+
#
|
64
223
|
# horn = Echo.new
|
65
224
|
# horn.echo('zero') # synchronous, not thread-safe
|
66
|
-
#
|
225
|
+
# # returns the actual return value of the method
|
226
|
+
#
|
67
227
|
# horn.async.echo('one') # asynchronous, non-blocking, thread-safe
|
228
|
+
# # returns an IVar in the :pending state
|
229
|
+
#
|
68
230
|
# horn.await.echo('two') # synchronous, blocking, thread-safe
|
231
|
+
# # returns an IVar in the :complete state
|
69
232
|
#
|
70
|
-
# @see Concurrent::
|
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/
|
71
237
|
module Async
|
72
238
|
|
73
239
|
# @!method self.new(*args, &block)
|
@@ -77,7 +243,7 @@ module Concurrent
|
|
77
243
|
#
|
78
244
|
# @param [Array<Object>] args Zero or more arguments to be passed to the
|
79
245
|
# object's initializer.
|
80
|
-
# @param [Proc]
|
246
|
+
# @param [Proc] block Optional block to pass to the object's initializer.
|
81
247
|
# @return [Object] A properly initialized object of the asynchronous class.
|
82
248
|
|
83
249
|
# Check for the presence of a method on an object and determine if a given
|
@@ -132,26 +298,20 @@ module Concurrent
|
|
132
298
|
# Delegates asynchronous, thread-safe method calls to the wrapped object.
|
133
299
|
#
|
134
300
|
# @!visibility private
|
135
|
-
class AsyncDelegator
|
301
|
+
class AsyncDelegator < Synchronization::LockableObject
|
302
|
+
safe_initialization!
|
136
303
|
|
137
|
-
# Create a new delegator object wrapping the given delegate
|
138
|
-
# protecting it with the given serializer, and executing it on the
|
139
|
-
# given executor. Block if necessary.
|
304
|
+
# Create a new delegator object wrapping the given delegate.
|
140
305
|
#
|
141
306
|
# @param [Object] delegate the object to wrap and delegate method calls to
|
142
|
-
|
143
|
-
|
144
|
-
# @param [Boolean] blocking will block awaiting result when `true`
|
145
|
-
def initialize(delegate, executor, serializer, blocking = false)
|
307
|
+
def initialize(delegate)
|
308
|
+
super()
|
146
309
|
@delegate = delegate
|
147
|
-
@
|
148
|
-
@
|
149
|
-
@blocking = blocking
|
310
|
+
@queue = []
|
311
|
+
@executor = Concurrent.global_io_executor
|
150
312
|
end
|
151
313
|
|
152
|
-
# Delegates method calls to the wrapped object.
|
153
|
-
# dynamically defines the given method on the delegator so that
|
154
|
-
# all future calls to `method` will not be directed here.
|
314
|
+
# Delegates method calls to the wrapped object.
|
155
315
|
#
|
156
316
|
# @param [Symbol] method the method being called
|
157
317
|
# @param [Array] args zero or more arguments to the method
|
@@ -164,134 +324,121 @@ module Concurrent
|
|
164
324
|
super unless @delegate.respond_to?(method)
|
165
325
|
Async::validate_argc(@delegate, method, *args)
|
166
326
|
|
167
|
-
|
168
|
-
|
169
|
-
ivar
|
170
|
-
@
|
171
|
-
begin
|
172
|
-
ivar.set(@delegate.send(method, *args2, &block))
|
173
|
-
rescue => reason
|
174
|
-
ivar.fail(reason)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
ivar.value if @blocking
|
178
|
-
ivar
|
327
|
+
ivar = Concurrent::IVar.new
|
328
|
+
synchronize do
|
329
|
+
@queue.push [ivar, method, args, block]
|
330
|
+
@executor.post { perform } if @queue.length == 1
|
179
331
|
end
|
180
332
|
|
181
|
-
|
333
|
+
ivar
|
334
|
+
end
|
335
|
+
|
336
|
+
# Perform all enqueued tasks.
|
337
|
+
#
|
338
|
+
# This method must be called from within the executor. It must not be
|
339
|
+
# called while already running. It will loop until the queue is empty.
|
340
|
+
def perform
|
341
|
+
loop do
|
342
|
+
ivar, method, args, block = synchronize { @queue.first }
|
343
|
+
break unless ivar # queue is empty
|
344
|
+
|
345
|
+
begin
|
346
|
+
ivar.set(@delegate.send(method, *args, &block))
|
347
|
+
rescue => error
|
348
|
+
ivar.fail(error)
|
349
|
+
end
|
350
|
+
|
351
|
+
synchronize do
|
352
|
+
@queue.shift
|
353
|
+
return if @queue.empty?
|
354
|
+
end
|
355
|
+
end
|
182
356
|
end
|
183
357
|
end
|
184
358
|
private_constant :AsyncDelegator
|
185
359
|
|
186
|
-
#
|
187
|
-
# global thread pool. The method called by this method will return a
|
188
|
-
# future object in the `:pending` state and the method call will have
|
189
|
-
# been scheduled on the global thread pool. The final disposition of the
|
190
|
-
# method call can be obtained by inspecting the returned future.
|
360
|
+
# Delegates synchronous, thread-safe method calls to the wrapped object.
|
191
361
|
#
|
192
|
-
#
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
362
|
+
# @!visibility private
|
363
|
+
class AwaitDelegator
|
364
|
+
|
365
|
+
# Create a new delegator object wrapping the given delegate.
|
366
|
+
#
|
367
|
+
# @param [AsyncDelegator] delegate the object to wrap and delegate method calls to
|
368
|
+
def initialize(delegate)
|
369
|
+
@delegate = delegate
|
370
|
+
end
|
371
|
+
|
372
|
+
# Delegates method calls to the wrapped object.
|
373
|
+
#
|
374
|
+
# @param [Symbol] method the method being called
|
375
|
+
# @param [Array] args zero or more arguments to the method
|
376
|
+
#
|
377
|
+
# @return [IVar] the result of the method call
|
378
|
+
#
|
379
|
+
# @raise [NameError] the object does not respond to `method` method
|
380
|
+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
|
381
|
+
def method_missing(method, *args, &block)
|
382
|
+
ivar = @delegate.send(method, *args, &block)
|
383
|
+
ivar.wait
|
384
|
+
ivar
|
385
|
+
end
|
386
|
+
end
|
387
|
+
private_constant :AwaitDelegator
|
388
|
+
|
389
|
+
# Causes the chained method call to be performed asynchronously on the
|
390
|
+
# object's thread. The delegated method will return a future in the
|
391
|
+
# `:pending` state and the method call will have been scheduled on the
|
392
|
+
# object's thread. The final disposition of the method call can be obtained
|
393
|
+
# by inspecting the returned future.
|
198
394
|
#
|
199
395
|
# @!macro [attach] async_thread_safety_warning
|
200
396
|
# @note The method call is guaranteed to be thread safe with respect to
|
201
397
|
# all other method calls against the same object that are called with
|
202
398
|
# either `async` or `await`. The mutable nature of Ruby references
|
203
399
|
# (and object orientation in general) prevent any other thread safety
|
204
|
-
# guarantees. Do NOT mix
|
205
|
-
#
|
206
|
-
# between threads.
|
400
|
+
# guarantees. Do NOT mix direct method calls with delegated method calls.
|
401
|
+
# Use *only* delegated method calls when sharing the object between threads.
|
207
402
|
#
|
208
403
|
# @return [Concurrent::IVar] the pending result of the asynchronous operation
|
209
404
|
#
|
210
|
-
# @raise [NameError] the object does not respond to
|
211
|
-
# @raise [ArgumentError] the given `args` do not match the arity of
|
212
|
-
#
|
213
|
-
# @see Concurrent::IVar
|
405
|
+
# @raise [NameError] the object does not respond to the requested method
|
406
|
+
# @raise [ArgumentError] the given `args` do not match the arity of
|
407
|
+
# the requested method
|
214
408
|
def async
|
215
|
-
@__async_delegator__
|
409
|
+
@__async_delegator__
|
216
410
|
end
|
411
|
+
alias_method :cast, :async
|
217
412
|
|
218
413
|
# Causes the chained method call to be performed synchronously on the
|
219
|
-
# current thread. The
|
220
|
-
#
|
221
|
-
#
|
222
|
-
#
|
223
|
-
#
|
224
|
-
# Before scheduling the method on the global thread pool a best-effort
|
225
|
-
# attempt will be made to validate that the method exists on the object
|
226
|
-
# and that the given arguments match the arity of the requested function.
|
227
|
-
# Due to the dynamic nature of Ruby and limitations of its reflection
|
228
|
-
# library, some edge cases will be missed. For more information see
|
229
|
-
# the documentation for the `validate_argc` method.
|
414
|
+
# current thread. The delegated will return a future in either the
|
415
|
+
# `:fulfilled` or `:rejected` state and the delegated method will have
|
416
|
+
# completed. The final disposition of the delegated method can be obtained
|
417
|
+
# by inspecting the returned future.
|
230
418
|
#
|
231
419
|
# @!macro async_thread_safety_warning
|
232
420
|
#
|
233
421
|
# @return [Concurrent::IVar] the completed result of the synchronous operation
|
234
422
|
#
|
235
|
-
# @raise [NameError] the object does not respond to
|
236
|
-
# @raise [ArgumentError] the given `args` do not match the arity of
|
237
|
-
#
|
238
|
-
# @see Concurrent::IVar
|
423
|
+
# @raise [NameError] the object does not respond to the requested method
|
424
|
+
# @raise [ArgumentError] the given `args` do not match the arity of the
|
425
|
+
# requested method
|
239
426
|
def await
|
240
|
-
@__await_delegator__
|
241
|
-
end
|
242
|
-
|
243
|
-
# Set a new executor.
|
244
|
-
#
|
245
|
-
# @raise [ArgumentError] executor has already been set.
|
246
|
-
def executor=(executor)
|
247
|
-
@__async_executor__.reconfigure { executor } or
|
248
|
-
raise ArgumentError.new('executor has already been set')
|
427
|
+
@__await_delegator__
|
249
428
|
end
|
429
|
+
alias_method :call, :await
|
250
430
|
|
251
431
|
# Initialize the internal serializer and other stnchronization mechanisms.
|
252
432
|
#
|
253
433
|
# @note This method *must* be called immediately upon object construction.
|
254
434
|
# This is the only way thread-safe initialization can be guaranteed.
|
255
435
|
#
|
256
|
-
# @raise [Concurrent::InitializationError] when called more than once
|
257
|
-
#
|
258
|
-
# @!visibility private
|
259
|
-
# @deprecated
|
260
|
-
def init_mutex
|
261
|
-
deprecated 'mutex synchronization now happens automatically'
|
262
|
-
init_synchronization
|
263
|
-
rescue InitializationError
|
264
|
-
# suppress
|
265
|
-
end
|
266
|
-
|
267
|
-
private
|
268
|
-
|
269
|
-
# Initialize the internal serializer and other stnchronization mechanisms.
|
270
|
-
#
|
271
|
-
# @note This method *must* be called immediately upon object construction.
|
272
|
-
# This is the only way thread-safe initialization can be guaranteed.
|
273
|
-
#
|
274
|
-
# @raise [Concurrent::InitializationError] when called more than once
|
275
|
-
#
|
276
436
|
# @!visibility private
|
277
437
|
def init_synchronization
|
278
438
|
return self if @__async_initialized__
|
279
|
-
|
280
439
|
@__async_initialized__ = true
|
281
|
-
|
282
|
-
|
283
|
-
@__async_executor__ = Delay.new {
|
284
|
-
Concurrent.global_io_executor
|
285
|
-
}
|
286
|
-
|
287
|
-
@__await_delegator__ = Delay.new {
|
288
|
-
AsyncDelegator.new(self, Delay.new{ Concurrent::ImmediateExecutor.new }, serializer, true)
|
289
|
-
}
|
290
|
-
|
291
|
-
@__async_delegator__ = Delay.new {
|
292
|
-
AsyncDelegator.new(self, @__async_executor__, serializer, false)
|
293
|
-
}
|
294
|
-
|
440
|
+
@__async_delegator__ = AsyncDelegator.new(self)
|
441
|
+
@__await_delegator__ = AwaitDelegator.new(@__async_delegator__)
|
295
442
|
self
|
296
443
|
end
|
297
444
|
end
|