concurrent-ruby 0.8.0 → 0.9.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|