concurrent-ruby 0.8.0 → 0.9.0.pre2
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 +97 -2
- data/README.md +103 -54
- data/lib/concurrent.rb +34 -14
- data/lib/concurrent/async.rb +164 -50
- data/lib/concurrent/atom.rb +171 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
- data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
- data/lib/concurrent/atomic/atomic_reference.rb +49 -0
- data/lib/concurrent/atomic/condition.rb +23 -12
- data/lib/concurrent/atomic/count_down_latch.rb +23 -21
- data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
- data/lib/concurrent/atomic/event.rb +33 -42
- data/lib/concurrent/atomic/read_write_lock.rb +252 -0
- data/lib/concurrent/atomic/semaphore.rb +64 -89
- data/lib/concurrent/atomic/thread_local_var.rb +130 -58
- data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +3 -0
- data/lib/concurrent/atomic_reference/jruby.rb +6 -3
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +10 -32
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
- data/lib/concurrent/atomic_reference/rbx.rb +4 -1
- data/lib/concurrent/atomic_reference/ruby.rb +6 -3
- data/lib/concurrent/atomics.rb +74 -4
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
- data/lib/concurrent/collection/priority_queue.rb +300 -245
- data/lib/concurrent/concern/deprecation.rb +27 -0
- data/lib/concurrent/concern/dereferenceable.rb +88 -0
- data/lib/concurrent/concern/logging.rb +25 -0
- data/lib/concurrent/concern/obligation.rb +228 -0
- data/lib/concurrent/concern/observable.rb +85 -0
- data/lib/concurrent/configuration.rb +226 -112
- data/lib/concurrent/dataflow.rb +2 -3
- data/lib/concurrent/delay.rb +141 -50
- data/lib/concurrent/edge.rb +30 -0
- data/lib/concurrent/errors.rb +10 -0
- data/lib/concurrent/exchanger.rb +25 -1
- data/lib/concurrent/executor/cached_thread_pool.rb +46 -33
- data/lib/concurrent/executor/executor.rb +46 -299
- data/lib/concurrent/executor/executor_service.rb +521 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +206 -26
- data/lib/concurrent/executor/immediate_executor.rb +9 -9
- data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
- data/lib/concurrent/executor/java_cached_thread_pool.rb +18 -16
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +11 -18
- data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
- data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +9 -18
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +10 -21
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
- data/lib/concurrent/executor/safe_task_executor.rb +5 -4
- data/lib/concurrent/executor/serialized_execution.rb +22 -18
- data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
- data/lib/concurrent/executor/single_thread_executor.rb +32 -21
- data/lib/concurrent/executor/thread_pool_executor.rb +72 -60
- data/lib/concurrent/executor/timer_set.rb +96 -84
- data/lib/concurrent/executors.rb +1 -1
- data/lib/concurrent/future.rb +70 -38
- data/lib/concurrent/immutable_struct.rb +89 -0
- data/lib/concurrent/ivar.rb +152 -60
- data/lib/concurrent/lazy_register.rb +40 -20
- data/lib/concurrent/maybe.rb +226 -0
- data/lib/concurrent/mutable_struct.rb +227 -0
- data/lib/concurrent/mvar.rb +44 -43
- data/lib/concurrent/promise.rb +208 -134
- data/lib/concurrent/scheduled_task.rb +339 -43
- data/lib/concurrent/settable_struct.rb +127 -0
- data/lib/concurrent/synchronization.rb +17 -0
- data/lib/concurrent/synchronization/abstract_object.rb +163 -0
- data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
- data/lib/concurrent/synchronization/condition.rb +53 -0
- data/lib/concurrent/synchronization/java_object.rb +35 -0
- data/lib/concurrent/synchronization/lock.rb +32 -0
- data/lib/concurrent/synchronization/monitor_object.rb +24 -0
- data/lib/concurrent/synchronization/mutex_object.rb +43 -0
- data/lib/concurrent/synchronization/object.rb +78 -0
- data/lib/concurrent/synchronization/rbx_object.rb +75 -0
- data/lib/concurrent/timer_task.rb +87 -100
- data/lib/concurrent/tvar.rb +42 -38
- data/lib/concurrent/utilities.rb +3 -1
- data/lib/concurrent/utility/at_exit.rb +97 -0
- data/lib/concurrent/utility/engine.rb +40 -0
- data/lib/concurrent/utility/monotonic_time.rb +59 -0
- data/lib/concurrent/utility/native_extension_loader.rb +56 -0
- data/lib/concurrent/utility/processor_counter.rb +156 -0
- data/lib/concurrent/utility/timeout.rb +18 -14
- data/lib/concurrent/utility/timer.rb +11 -6
- data/lib/concurrent/version.rb +2 -1
- data/lib/concurrent_ruby.rb +1 -0
- metadata +47 -83
- data/lib/concurrent/actor.rb +0 -103
- data/lib/concurrent/actor/behaviour.rb +0 -70
- data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
- data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
- data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
- data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
- data/lib/concurrent/actor/behaviour/linking.rb +0 -45
- data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
- data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
- data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
- data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
- data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
- data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
- data/lib/concurrent/actor/behaviour/termination.rb +0 -54
- data/lib/concurrent/actor/context.rb +0 -154
- data/lib/concurrent/actor/core.rb +0 -217
- data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
- data/lib/concurrent/actor/envelope.rb +0 -41
- data/lib/concurrent/actor/errors.rb +0 -27
- data/lib/concurrent/actor/internal_delegations.rb +0 -49
- data/lib/concurrent/actor/public_delegations.rb +0 -40
- data/lib/concurrent/actor/reference.rb +0 -81
- data/lib/concurrent/actor/root.rb +0 -37
- data/lib/concurrent/actor/type_check.rb +0 -48
- data/lib/concurrent/actor/utils.rb +0 -10
- data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
- data/lib/concurrent/actor/utils/balancer.rb +0 -42
- data/lib/concurrent/actor/utils/broadcast.rb +0 -52
- data/lib/concurrent/actor/utils/pool.rb +0 -59
- data/lib/concurrent/actress.rb +0 -3
- data/lib/concurrent/agent.rb +0 -209
- data/lib/concurrent/atomic.rb +0 -92
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
- data/lib/concurrent/atomic/synchronization.rb +0 -51
- data/lib/concurrent/channel/buffered_channel.rb +0 -85
- data/lib/concurrent/channel/channel.rb +0 -41
- data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
- data/lib/concurrent/channel/waitable_list.rb +0 -40
- data/lib/concurrent/channels.rb +0 -5
- data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
- data/lib/concurrent/collection/ring_buffer.rb +0 -59
- data/lib/concurrent/collections.rb +0 -3
- data/lib/concurrent/dereferenceable.rb +0 -108
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
- data/lib/concurrent/logging.rb +0 -20
- data/lib/concurrent/obligation.rb +0 -171
- data/lib/concurrent/observable.rb +0 -73
- data/lib/concurrent/options_parser.rb +0 -52
- data/lib/concurrent/utility/processor_count.rb +0 -152
- data/lib/extension_helper.rb +0 -37
data/lib/concurrent/mvar.rb
CHANGED
@@ -1,39 +1,42 @@
|
|
1
|
-
require 'concurrent/dereferenceable'
|
2
|
-
require 'concurrent/atomic/condition'
|
3
|
-
require 'concurrent/atomic/event'
|
1
|
+
require 'concurrent/concern/dereferenceable'
|
4
2
|
|
5
3
|
module Concurrent
|
6
4
|
|
7
|
-
# An `MVar` is a synchronized single element container. They are empty or
|
8
|
-
# Taking a value from an empty `MVar` blocks, as does
|
9
|
-
# You can either think of them as blocking
|
10
|
-
# mutable variable.
|
11
|
-
#
|
12
|
-
# On top of the fundamental `#put` and `#take` operations, we also provide a
|
13
|
-
# that is atomic with respect to operations on the same instance.
|
14
|
-
# support timeouts.
|
15
|
-
#
|
16
|
-
# We also support non-blocking operations `#try_put!` and `#try_take!`, a
|
17
|
-
# ignores existing values, a `#value` that returns the value
|
18
|
-
# returns `MVar::EMPTY`, and a `#modify!` that yields
|
19
|
-
# empty and can be used to set `MVar::EMPTY`.
|
20
|
-
# first instance.
|
21
|
-
#
|
5
|
+
# An `MVar` is a synchronized single element container. They are empty or
|
6
|
+
# contain one item. Taking a value from an empty `MVar` blocks, as does
|
7
|
+
# putting a value into a full one. You can either think of them as blocking
|
8
|
+
# queue of length one, or a special kind of mutable variable.
|
9
|
+
#
|
10
|
+
# On top of the fundamental `#put` and `#take` operations, we also provide a
|
11
|
+
# `#mutate` that is atomic with respect to operations on the same instance.
|
12
|
+
# These operations all support timeouts.
|
13
|
+
#
|
14
|
+
# We also support non-blocking operations `#try_put!` and `#try_take!`, a
|
15
|
+
# `#set!` that ignores existing values, a `#value` that returns the value
|
16
|
+
# without removing it or returns `MVar::EMPTY`, and a `#modify!` that yields
|
17
|
+
# `MVar::EMPTY` if the `MVar` is empty and can be used to set `MVar::EMPTY`.
|
18
|
+
# You shouldn't use these operations in the first instance.
|
19
|
+
#
|
22
20
|
# `MVar` is a [Dereferenceable](Dereferenceable).
|
23
|
-
#
|
21
|
+
#
|
24
22
|
# `MVar` is related to M-structures in Id, `MVar` in Haskell and `SyncVar` in Scala.
|
25
23
|
#
|
26
24
|
# Note that unlike the original Haskell paper, our `#take` is blocking. This is how
|
27
25
|
# Haskell and Scala do it today.
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
26
|
+
#
|
27
|
+
# @!macro copy_options
|
28
|
+
#
|
29
|
+
# ## See Also
|
30
|
+
#
|
31
|
+
# 1. P. Barth, R. Nikhil, and Arvind. [M-Structures: Extending a parallel, non- strict, functional language with state](http://dl.acm.org/citation.cfm?id=652538). In Proceedings of the 5th
|
32
|
+
# ACM Conference on Functional Programming Languages and Computer Architecture (FPCA), 1991.
|
33
|
+
#
|
34
|
+
# 2. S. Peyton Jones, A. Gordon, and S. Finne. [Concurrent Haskell](http://dl.acm.org/citation.cfm?id=237794).
|
35
|
+
# In Proceedings of the 23rd Symposium on Principles of Programming Languages
|
36
|
+
# (PoPL), 1996.
|
34
37
|
class MVar
|
35
38
|
|
36
|
-
include Dereferenceable
|
39
|
+
include Concern::Dereferenceable
|
37
40
|
|
38
41
|
# Unique value that represents that an `MVar` was empty
|
39
42
|
EMPTY = Object.new
|
@@ -45,20 +48,13 @@ module Concurrent
|
|
45
48
|
# Create a new `MVar`, either empty or with an initial value.
|
46
49
|
#
|
47
50
|
# @param [Hash] opts the options controlling how the future will be processed
|
48
|
-
#
|
49
|
-
#
|
50
|
-
# global task pool (for short-running tasks)
|
51
|
-
# @option opts [object] :executor when provided will run all operations on
|
52
|
-
# this executor rather than the global thread pool (overrides :operation)
|
53
|
-
# @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
|
54
|
-
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
|
55
|
-
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
|
56
|
-
# returning the value returned from the proc
|
51
|
+
#
|
52
|
+
# @!macro deref_options
|
57
53
|
def initialize(value = EMPTY, opts = {})
|
58
54
|
@value = value
|
59
55
|
@mutex = Mutex.new
|
60
|
-
@empty_condition =
|
61
|
-
@full_condition =
|
56
|
+
@empty_condition = ConditionVariable.new
|
57
|
+
@full_condition = ConditionVariable.new
|
62
58
|
set_deref_options(opts)
|
63
59
|
end
|
64
60
|
|
@@ -184,7 +180,7 @@ module Concurrent
|
|
184
180
|
|
185
181
|
# Returns if the `MVar` currently contains a value.
|
186
182
|
def full?
|
187
|
-
|
183
|
+
!empty?
|
188
184
|
end
|
189
185
|
|
190
186
|
private
|
@@ -206,12 +202,17 @@ module Concurrent
|
|
206
202
|
end
|
207
203
|
|
208
204
|
def wait_while(condition, timeout)
|
209
|
-
|
210
|
-
|
211
|
-
|
205
|
+
if timeout.nil?
|
206
|
+
while yield
|
207
|
+
condition.wait(@mutex)
|
208
|
+
end
|
209
|
+
else
|
210
|
+
stop = Concurrent.monotonic_time + timeout
|
211
|
+
while yield && timeout > 0.0
|
212
|
+
condition.wait(@mutex, timeout)
|
213
|
+
timeout = stop - Concurrent.monotonic_time
|
214
|
+
end
|
212
215
|
end
|
213
216
|
end
|
214
|
-
|
215
217
|
end
|
216
|
-
|
217
218
|
end
|
data/lib/concurrent/promise.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/
|
4
|
-
require 'concurrent/
|
2
|
+
require 'concurrent/errors'
|
3
|
+
require 'concurrent/ivar'
|
4
|
+
require 'concurrent/executor/executor'
|
5
5
|
|
6
6
|
module Concurrent
|
7
7
|
|
@@ -9,221 +9,236 @@ module Concurrent
|
|
9
9
|
|
10
10
|
# Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)
|
11
11
|
# and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications.
|
12
|
-
#
|
13
|
-
# > A promise represents the eventual value returned from the single
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
12
|
+
#
|
13
|
+
# > A promise represents the eventual value returned from the single
|
14
|
+
# > completion of an operation.
|
15
|
+
#
|
16
|
+
# Promises are similar to futures and share many of the same behaviours.
|
17
|
+
# Promises are far more robust, however. Promises can be chained in a tree
|
18
|
+
# structure where each promise may have zero or more children. Promises are
|
19
|
+
# chained using the `then` method. The result of a call to `then` is always
|
20
|
+
# another promise. Promises are resolved asynchronously (with respect to the
|
21
|
+
# main thread) but in a strict order: parents are guaranteed to be resolved
|
22
|
+
# before their children, children before their younger siblings. The `then`
|
23
|
+
# method takes two parameters: an optional block to be executed upon parent
|
24
|
+
# resolution and an optional callable to be executed upon parent failure. The
|
25
|
+
# result of each promise is passed to each of its children upon resolution.
|
26
|
+
# When a promise is rejected all its children will be summarily rejected and
|
27
|
+
# will receive the reason.
|
28
|
+
#
|
29
|
+
# Promises have four possible states: *unscheduled*, *pending*, *rejected*,
|
30
|
+
# and *fulfilled*. A Promise created using `.new` will be *unscheduled*. It is
|
31
|
+
# scheduled by calling the `execute` method. Upon execution the Promise and
|
32
|
+
# all its children will be set to *pending*. When a promise is *pending* it
|
33
|
+
# will remain in that state until processing is complete. A completed Promise
|
34
|
+
# is either *rejected*, indicating that an exception was thrown during
|
35
|
+
# processing, or *fulfilled*, indicating it succeeded. If a Promise is
|
36
|
+
# *fulfilled* its `value` will be updated to reflect the result of the
|
37
|
+
# operation. If *rejected* the `reason` will be updated with a reference to
|
38
|
+
# the thrown exception. The predicate methods `unscheduled?`, `pending?`,
|
39
|
+
# `rejected?`, and `fulfilled?` can be called at any time to obtain the state
|
40
|
+
# of the Promise, as can the `state` method, which returns a symbol. A Promise
|
41
|
+
# created using `.execute` will be *pending*, a Promise created using
|
42
|
+
# `.fulfill(value)` will be *fulfilled* with the given value and a Promise
|
43
|
+
# created using `.reject(reason)` will be *rejected* with the given reason.
|
44
|
+
#
|
45
|
+
# Retrieving the value of a promise is done through the `value` (alias:
|
46
|
+
# `deref`) method. Obtaining the value of a promise is a potentially blocking
|
47
|
+
# operation. When a promise is *rejected* a call to `value` will return `nil`
|
48
|
+
# immediately. When a promise is *fulfilled* a call to `value` will
|
49
|
+
# immediately return the current value. When a promise is *pending* a call to
|
50
|
+
# `value` will block until the promise is either *rejected* or *fulfilled*. A
|
51
|
+
# *timeout* value can be passed to `value` to limit how long the call will
|
52
|
+
# block. If `nil` the call will block indefinitely. If `0` the call will not
|
53
|
+
# block. Any other integer or float value will indicate the maximum number of
|
54
|
+
# seconds to block.
|
55
|
+
#
|
46
56
|
# Promises run on the global thread pool.
|
47
|
-
#
|
57
|
+
#
|
58
|
+
# @!macro copy_options
|
59
|
+
#
|
48
60
|
# ### Examples
|
49
|
-
#
|
61
|
+
#
|
50
62
|
# Start by requiring promises
|
51
|
-
#
|
63
|
+
#
|
52
64
|
# ```ruby
|
53
65
|
# require 'concurrent'
|
54
66
|
# ```
|
55
|
-
#
|
67
|
+
#
|
56
68
|
# Then create one
|
57
|
-
#
|
69
|
+
#
|
58
70
|
# ```ruby
|
59
71
|
# p = Concurrent::Promise.execute do
|
60
72
|
# # do something
|
61
73
|
# 42
|
62
74
|
# end
|
63
75
|
# ```
|
64
|
-
#
|
65
|
-
# Promises can be chained using the `then` method. The `then` method accepts a
|
66
|
-
# on fulfillment, and a callable argument to be executed
|
67
|
-
# is passed as the block argument
|
68
|
-
#
|
76
|
+
#
|
77
|
+
# Promises can be chained using the `then` method. The `then` method accepts a
|
78
|
+
# block, to be executed on fulfillment, and a callable argument to be executed
|
79
|
+
# on rejection. The result of the each promise is passed as the block argument
|
80
|
+
# to chained promises.
|
81
|
+
#
|
69
82
|
# ```ruby
|
70
83
|
# p = Concurrent::Promise.new{10}.then{|x| x * 2}.then{|result| result - 10 }.execute
|
71
84
|
# ```
|
72
|
-
#
|
85
|
+
#
|
73
86
|
# And so on, and so on, and so on...
|
74
|
-
#
|
87
|
+
#
|
75
88
|
# ```ruby
|
76
89
|
# p = Concurrent::Promise.fulfill(20).
|
77
90
|
# then{|result| result - 10 }.
|
78
91
|
# then{|result| result * 3 }.
|
79
92
|
# then{|result| result % 5 }.execute
|
80
93
|
# ```
|
81
|
-
#
|
94
|
+
#
|
82
95
|
# The initial state of a newly created Promise depends on the state of its parent:
|
83
96
|
# - if parent is *unscheduled* the child will be *unscheduled*
|
84
97
|
# - if parent is *pending* the child will be *pending*
|
85
98
|
# - if parent is *fulfilled* the child will be *pending*
|
86
99
|
# - if parent is *rejected* the child will be *pending* (but will ultimately be *rejected*)
|
87
|
-
#
|
88
|
-
# Promises are executed asynchronously from the main thread. By the time a
|
89
|
-
# nitialization it may be in a different state that its
|
90
|
-
#
|
91
|
-
# execution
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
100
|
+
#
|
101
|
+
# Promises are executed asynchronously from the main thread. By the time a
|
102
|
+
# child Promise finishes nitialization it may be in a different state that its
|
103
|
+
# parent (by the time a child is created its parent may have completed
|
104
|
+
# execution and changed state). Despite being asynchronous, however, the order
|
105
|
+
# of execution of Promise objects in a chain (or tree) is strictly defined.
|
106
|
+
#
|
107
|
+
# There are multiple ways to create and execute a new `Promise`. Both ways
|
108
|
+
# provide identical behavior:
|
109
|
+
#
|
95
110
|
# ```ruby
|
96
111
|
# # create, operate, then execute
|
97
112
|
# p1 = Concurrent::Promise.new{ "Hello World!" }
|
98
113
|
# p1.state #=> :unscheduled
|
99
114
|
# p1.execute
|
100
|
-
#
|
115
|
+
#
|
101
116
|
# # create and immediately execute
|
102
117
|
# p2 = Concurrent::Promise.new{ "Hello World!" }.execute
|
103
|
-
#
|
118
|
+
#
|
104
119
|
# # execute during creation
|
105
120
|
# p3 = Concurrent::Promise.execute{ "Hello World!" }
|
106
121
|
# ```
|
107
|
-
#
|
122
|
+
#
|
108
123
|
# Once the `execute` method is called a `Promise` becomes `pending`:
|
109
|
-
#
|
124
|
+
#
|
110
125
|
# ```ruby
|
111
126
|
# p = Concurrent::Promise.execute{ "Hello, world!" }
|
112
127
|
# p.state #=> :pending
|
113
128
|
# p.pending? #=> true
|
114
129
|
# ```
|
115
|
-
#
|
130
|
+
#
|
116
131
|
# Wait a little bit, and the promise will resolve and provide a value:
|
117
|
-
#
|
132
|
+
#
|
118
133
|
# ```ruby
|
119
134
|
# p = Concurrent::Promise.execute{ "Hello, world!" }
|
120
135
|
# sleep(0.1)
|
121
|
-
#
|
136
|
+
#
|
122
137
|
# p.state #=> :fulfilled
|
123
138
|
# p.fulfilled? #=> true
|
124
139
|
# p.value #=> "Hello, world!"
|
125
140
|
# ```
|
126
|
-
#
|
141
|
+
#
|
127
142
|
# If an exception occurs, the promise will be rejected and will provide
|
128
143
|
# a reason for the rejection:
|
129
|
-
#
|
144
|
+
#
|
130
145
|
# ```ruby
|
131
146
|
# p = Concurrent::Promise.execute{ raise StandardError.new("Here comes the Boom!") }
|
132
147
|
# sleep(0.1)
|
133
|
-
#
|
148
|
+
#
|
134
149
|
# p.state #=> :rejected
|
135
150
|
# p.rejected? #=> true
|
136
151
|
# p.reason #=> "#<StandardError: Here comes the Boom!>"
|
137
152
|
# ```
|
138
|
-
#
|
153
|
+
#
|
139
154
|
# #### Rejection
|
140
|
-
#
|
141
|
-
# When a promise is rejected all its children will be rejected and will
|
142
|
-
# as the rejection callable parameter:
|
143
|
-
#
|
155
|
+
#
|
156
|
+
# When a promise is rejected all its children will be rejected and will
|
157
|
+
# receive the rejection `reason` as the rejection callable parameter:
|
158
|
+
#
|
144
159
|
# ```ruby
|
145
160
|
# p = [ Concurrent::Promise.execute{ Thread.pass; raise StandardError } ]
|
146
|
-
#
|
161
|
+
#
|
147
162
|
# c1 = p.then(Proc.new{ |reason| 42 })
|
148
163
|
# c2 = p.then(Proc.new{ |reason| raise 'Boom!' })
|
149
|
-
#
|
164
|
+
#
|
150
165
|
# sleep(0.1)
|
151
|
-
#
|
166
|
+
#
|
152
167
|
# c1.state #=> :rejected
|
153
168
|
# c2.state #=> :rejected
|
154
169
|
# ```
|
155
|
-
#
|
156
|
-
# Once a promise is rejected it will continue to accept children that will
|
157
|
-
# (they will be executed asynchronously).
|
158
|
-
#
|
170
|
+
#
|
171
|
+
# Once a promise is rejected it will continue to accept children that will
|
172
|
+
# receive immediately rejection (they will be executed asynchronously).
|
173
|
+
#
|
159
174
|
# #### Aliases
|
160
|
-
#
|
161
|
-
# The `then` method is the most generic alias: it accepts a block to be
|
162
|
-
# and a callable to be executed upon parent
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
175
|
+
#
|
176
|
+
# The `then` method is the most generic alias: it accepts a block to be
|
177
|
+
# executed upon parent fulfillment and a callable to be executed upon parent
|
178
|
+
# rejection. At least one of them should be passed. The default block is `{
|
179
|
+
# |result| result }` that fulfills the child with the parent value. The
|
180
|
+
# default callable is `{ |reason| raise reason }` that rejects the child with
|
181
|
+
# the parent reason.
|
182
|
+
#
|
166
183
|
# - `on_success { |result| ... }` is the same as `then {|result| ... }`
|
167
184
|
# - `rescue { |reason| ... }` is the same as `then(Proc.new { |reason| ... } )`
|
168
185
|
# - `rescue` is aliased by `catch` and `on_error`
|
169
|
-
class Promise
|
170
|
-
# TODO unify promise and future to single class, with dataflow
|
171
|
-
include Obligation
|
186
|
+
class Promise < IVar
|
172
187
|
|
173
188
|
# Initialize a new Promise with the provided options.
|
174
189
|
#
|
175
|
-
# @!macro
|
190
|
+
# @!macro executor_and_deref_options
|
176
191
|
#
|
177
|
-
#
|
192
|
+
# @!macro [attach] promise_init_options
|
178
193
|
#
|
179
194
|
# @option opts [Promise] :parent the parent `Promise` when building a chain/tree
|
180
195
|
# @option opts [Proc] :on_fulfill fulfillment handler
|
181
196
|
# @option opts [Proc] :on_reject rejection handler
|
197
|
+
# @option opts [object, Array] :args zero or more arguments to be passed
|
198
|
+
# the task block on execution
|
182
199
|
#
|
183
|
-
#
|
184
|
-
# operation pool (for long-running operations), when `false` will execute the future on the
|
185
|
-
# global task pool (for short-running tasks)
|
186
|
-
# @option opts [object] :executor when provided will run all operations on
|
187
|
-
# this executor rather than the global thread pool (overrides :operation)
|
188
|
-
# @option opts [object, Array] :args zero or more arguments to be passed the task block on execution
|
200
|
+
# @yield The block operation to be performed asynchronously.
|
189
201
|
#
|
190
|
-
#
|
191
|
-
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
|
192
|
-
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
|
193
|
-
# returning the value returned from the proc
|
202
|
+
# @raise [ArgumentError] if no block is given
|
194
203
|
#
|
195
204
|
# @see http://wiki.commonjs.org/wiki/Promises/A
|
196
205
|
# @see http://promises-aplus.github.io/promises-spec/
|
197
206
|
def initialize(opts = {}, &block)
|
198
207
|
opts.delete_if { |k, v| v.nil? }
|
199
|
-
|
200
|
-
@executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
|
201
|
-
@args = OptionsParser::get_arguments_from(opts)
|
202
|
-
|
203
|
-
@parent = opts.fetch(:parent) { nil }
|
204
|
-
@on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
|
205
|
-
@on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
|
206
|
-
|
207
|
-
@promise_body = block || Proc.new { |result| result }
|
208
|
-
@state = :unscheduled
|
209
|
-
@children = []
|
210
|
-
|
211
|
-
init_obligation
|
212
|
-
set_deref_options(opts)
|
208
|
+
super(IVar::NO_VALUE, opts.merge(__promise_body_from_block__: block), &nil)
|
213
209
|
end
|
214
210
|
|
215
|
-
#
|
211
|
+
# Create a new `Promise` and fulfill it immediately.
|
212
|
+
#
|
213
|
+
# @!macro executor_and_deref_options
|
214
|
+
#
|
215
|
+
# @!macro promise_init_options
|
216
|
+
#
|
217
|
+
# @raise [ArgumentError] if no block is given
|
218
|
+
#
|
219
|
+
# @return [Promise] the newly created `Promise`
|
216
220
|
def self.fulfill(value, opts = {})
|
217
221
|
Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
|
218
222
|
end
|
219
223
|
|
220
|
-
|
221
|
-
#
|
224
|
+
# Create a new `Promise` and reject it immediately.
|
225
|
+
#
|
226
|
+
# @!macro executor_and_deref_options
|
227
|
+
#
|
228
|
+
# @!macro promise_init_options
|
229
|
+
#
|
230
|
+
# @raise [ArgumentError] if no block is given
|
231
|
+
#
|
232
|
+
# @return [Promise] the newly created `Promise`
|
222
233
|
def self.reject(reason, opts = {})
|
223
234
|
Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
|
224
235
|
end
|
225
236
|
|
226
|
-
#
|
237
|
+
# Execute an `:unscheduled` `Promise`. Immediately sets the state to `:pending` and
|
238
|
+
# passes the block to a new thread/thread pool for eventual execution.
|
239
|
+
# Does nothing if the `Promise` is in any state other than `:unscheduled`.
|
240
|
+
#
|
241
|
+
# @return [Promise] a reference to `self`
|
227
242
|
def execute
|
228
243
|
if root?
|
229
244
|
if compare_and_set_state(:pending, :unscheduled)
|
@@ -236,9 +251,34 @@ module Concurrent
|
|
236
251
|
self
|
237
252
|
end
|
238
253
|
|
254
|
+
# @!macro ivar_set_method
|
255
|
+
#
|
256
|
+
# @raise [Concurrent::PromiseExecutionError] if not the root promise
|
257
|
+
def set(value = IVar::NO_VALUE, &block)
|
258
|
+
raise PromiseExecutionError.new('supported only on root promise') unless root?
|
259
|
+
check_for_block_or_value!(block_given?, value)
|
260
|
+
synchronize do
|
261
|
+
if @state != :unscheduled
|
262
|
+
raise MultipleAssignmentError
|
263
|
+
else
|
264
|
+
@promise_body = block || Proc.new { |result| value }
|
265
|
+
end
|
266
|
+
end
|
267
|
+
execute
|
268
|
+
end
|
269
|
+
|
270
|
+
# @!macro ivar_fail_method
|
271
|
+
#
|
272
|
+
# @raise [Concurrent::PromiseExecutionError] if not the root promise
|
273
|
+
def fail(reason = StandardError.new)
|
274
|
+
set { raise reason }
|
275
|
+
end
|
276
|
+
|
239
277
|
# Create a new `Promise` object with the given block, execute it, and return the
|
240
278
|
# `:pending` object.
|
241
279
|
#
|
280
|
+
# @!macro executor_and_deref_options
|
281
|
+
#
|
242
282
|
# @!macro promise_init_options
|
243
283
|
#
|
244
284
|
# @return [Promise] the newly created `Promise` in the `:pending` state
|
@@ -252,6 +292,13 @@ module Concurrent
|
|
252
292
|
new(opts, &block).execute
|
253
293
|
end
|
254
294
|
|
295
|
+
# Chain a new promise off the current promise.
|
296
|
+
#
|
297
|
+
# @param [Proc] rescuer An optional rescue block to be executed if the
|
298
|
+
# promise is rejected.
|
299
|
+
#
|
300
|
+
# @yield The block operation to be performed asynchronously.
|
301
|
+
#
|
255
302
|
# @return [Promise] the new promise
|
256
303
|
def then(rescuer = nil, &block)
|
257
304
|
raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
|
@@ -263,7 +310,7 @@ module Concurrent
|
|
263
310
|
on_reject: rescuer
|
264
311
|
)
|
265
312
|
|
266
|
-
|
313
|
+
synchronize do
|
267
314
|
child.state = :pending if @state == :pending
|
268
315
|
child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
|
269
316
|
child.on_reject(@reason) if @state == :rejected
|
@@ -273,13 +320,23 @@ module Concurrent
|
|
273
320
|
child
|
274
321
|
end
|
275
322
|
|
276
|
-
#
|
323
|
+
# Chain onto this promise an action to be undertaken on success
|
324
|
+
# (fulfillment).
|
325
|
+
#
|
326
|
+
# @yield The block to execute
|
327
|
+
#
|
328
|
+
# @return [Promise] self
|
277
329
|
def on_success(&block)
|
278
330
|
raise ArgumentError.new('no block given') unless block_given?
|
279
331
|
self.then(&block)
|
280
332
|
end
|
281
333
|
|
282
|
-
#
|
334
|
+
# Chain onto this promise an action to be undertaken on failure
|
335
|
+
# (rejection).
|
336
|
+
#
|
337
|
+
# @yield The block to execute
|
338
|
+
#
|
339
|
+
# @return [Promise] self
|
283
340
|
def rescue(&block)
|
284
341
|
self.then(block)
|
285
342
|
end
|
@@ -381,6 +438,21 @@ module Concurrent
|
|
381
438
|
|
382
439
|
protected
|
383
440
|
|
441
|
+
def ns_initialize(value, opts)
|
442
|
+
super
|
443
|
+
|
444
|
+
@executor = Executor.executor_from_options(opts) || Concurrent.global_io_executor
|
445
|
+
@args = get_arguments_from(opts)
|
446
|
+
|
447
|
+
@parent = opts.fetch(:parent) { nil }
|
448
|
+
@on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
|
449
|
+
@on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
|
450
|
+
|
451
|
+
@promise_body = opts[:__promise_body_from_block__] || Proc.new { |result| result }
|
452
|
+
@state = :unscheduled
|
453
|
+
@children = []
|
454
|
+
end
|
455
|
+
|
384
456
|
# Aggregate a collection of zero or more promises under a composite promise,
|
385
457
|
# execute the aggregated promises and collect them into a standard Ruby array,
|
386
458
|
# call the given Ruby `Ennnumerable` predicate (such as `any?`, `all?`, `none?`,
|
@@ -406,7 +478,7 @@ module Concurrent
|
|
406
478
|
|
407
479
|
# @!visibility private
|
408
480
|
def set_pending
|
409
|
-
|
481
|
+
synchronize do
|
410
482
|
@state = :pending
|
411
483
|
@children.each { |c| c.set_pending }
|
412
484
|
end
|
@@ -435,17 +507,22 @@ module Concurrent
|
|
435
507
|
if_state(:rejected) { child.on_reject(@reason) }
|
436
508
|
end
|
437
509
|
|
510
|
+
# @!visibility private
|
511
|
+
def complete(success, value, reason)
|
512
|
+
children_to_notify = synchronize do
|
513
|
+
set_state!(success, value, reason)
|
514
|
+
@children.dup
|
515
|
+
end
|
516
|
+
|
517
|
+
children_to_notify.each { |child| notify_child(child) }
|
518
|
+
observers.notify_and_delete_observers{ [Time.now, self.value, reason] }
|
519
|
+
end
|
520
|
+
|
438
521
|
# @!visibility private
|
439
522
|
def realize(task)
|
440
523
|
@executor.post do
|
441
524
|
success, value, reason = SafeTaskExecutor.new(task).execute(*@args)
|
442
|
-
|
443
|
-
children_to_notify = mutex.synchronize do
|
444
|
-
set_state!(success, value, reason)
|
445
|
-
@children.dup
|
446
|
-
end
|
447
|
-
|
448
|
-
children_to_notify.each { |child| notify_child(child) }
|
525
|
+
complete(success, value, reason)
|
449
526
|
end
|
450
527
|
end
|
451
528
|
|
@@ -457,10 +534,7 @@ module Concurrent
|
|
457
534
|
|
458
535
|
# @!visibility private
|
459
536
|
def synchronized_set_state!(success, value, reason)
|
460
|
-
|
461
|
-
set_state!(success, value, reason)
|
462
|
-
ensure
|
463
|
-
mutex.unlock
|
537
|
+
synchronize { set_state!(success, value, reason) }
|
464
538
|
end
|
465
539
|
end
|
466
540
|
end
|