concurrent-ruby 0.8.0.pre2-java → 0.9.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 +114 -3
- data/README.md +111 -55
- data/lib/concurrent.rb +90 -14
- data/lib/concurrent/async.rb +143 -51
- data/lib/concurrent/atom.rb +131 -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 +34 -3
- data/lib/concurrent/atomic_reference/jruby.rb +6 -3
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
- 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 +34 -0
- data/lib/concurrent/concern/dereferenceable.rb +88 -0
- data/lib/concurrent/concern/logging.rb +27 -0
- data/lib/concurrent/concern/obligation.rb +228 -0
- data/lib/concurrent/concern/observable.rb +85 -0
- data/lib/concurrent/configuration.rb +234 -109
- 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 +19 -7
- data/lib/concurrent/exchanger.rb +25 -1
- data/lib/concurrent/executor/cached_thread_pool.rb +51 -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 +196 -23
- data/lib/concurrent/executor/immediate_executor.rb +9 -9
- data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
- 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_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 +73 -60
- data/lib/concurrent/executor/timer_set.rb +96 -84
- data/lib/concurrent/executors.rb +1 -1
- data/lib/concurrent/future.rb +71 -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 +229 -136
- data/lib/concurrent/scheduled_task.rb +341 -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 +34 -0
- data/lib/concurrent/synchronization/lock.rb +32 -0
- data/lib/concurrent/synchronization/monitor_object.rb +26 -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 +92 -103
- 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 +44 -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
- data/lib/concurrent_ruby_ext.jar +0 -0
- metadata +46 -66
- 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/java_cached_thread_pool.rb +0 -32
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
- 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 -48
- 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,217 +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
|
-
#
|
190
|
+
# @!macro executor_and_deref_options
|
191
|
+
#
|
192
|
+
# @!macro [attach] promise_init_options
|
176
193
|
#
|
177
|
-
#
|
178
|
-
#
|
179
|
-
#
|
194
|
+
# @option opts [Promise] :parent the parent `Promise` when building a chain/tree
|
195
|
+
# @option opts [Proc] :on_fulfill fulfillment handler
|
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
|
180
199
|
#
|
181
|
-
# @
|
182
|
-
# operation pool (for long-running operations), when `false` will execute the future on the
|
183
|
-
# global task pool (for short-running tasks)
|
184
|
-
# @option opts [object] :executor when provided will run all operations on
|
185
|
-
# this executor rather than the global thread pool (overrides :operation)
|
200
|
+
# @yield The block operation to be performed asynchronously.
|
186
201
|
#
|
187
|
-
# @
|
188
|
-
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
|
189
|
-
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
|
190
|
-
# returning the value returned from the proc
|
202
|
+
# @raise [ArgumentError] if no block is given
|
191
203
|
#
|
192
204
|
# @see http://wiki.commonjs.org/wiki/Promises/A
|
193
205
|
# @see http://promises-aplus.github.io/promises-spec/
|
194
206
|
def initialize(opts = {}, &block)
|
195
207
|
opts.delete_if { |k, v| v.nil? }
|
196
|
-
|
197
|
-
@executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
|
198
|
-
@parent = opts.fetch(:parent) { nil }
|
199
|
-
@on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
|
200
|
-
@on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
|
201
|
-
|
202
|
-
@promise_body = block || Proc.new { |result| result }
|
203
|
-
@state = :unscheduled
|
204
|
-
@children = []
|
205
|
-
|
206
|
-
init_obligation
|
207
|
-
set_deref_options(opts)
|
208
|
+
super(IVar::NO_VALUE, opts.merge(__promise_body_from_block__: block), &nil)
|
208
209
|
end
|
209
210
|
|
210
|
-
#
|
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`
|
211
220
|
def self.fulfill(value, opts = {})
|
212
221
|
Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
|
213
222
|
end
|
214
223
|
|
215
|
-
|
216
|
-
#
|
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`
|
217
233
|
def self.reject(reason, opts = {})
|
218
234
|
Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
|
219
235
|
end
|
220
236
|
|
221
|
-
#
|
222
|
-
#
|
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`
|
223
242
|
def execute
|
224
243
|
if root?
|
225
244
|
if compare_and_set_state(:pending, :unscheduled)
|
@@ -232,11 +251,54 @@ module Concurrent
|
|
232
251
|
self
|
233
252
|
end
|
234
253
|
|
235
|
-
#
|
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
|
+
|
277
|
+
# Create a new `Promise` object with the given block, execute it, and return the
|
278
|
+
# `:pending` object.
|
279
|
+
#
|
280
|
+
# @!macro executor_and_deref_options
|
281
|
+
#
|
282
|
+
# @!macro promise_init_options
|
283
|
+
#
|
284
|
+
# @return [Promise] the newly created `Promise` in the `:pending` state
|
285
|
+
#
|
286
|
+
# @raise [ArgumentError] if no block is given
|
287
|
+
#
|
288
|
+
# @example
|
289
|
+
# promise = Concurrent::Promise.execute{ sleep(1); 42 }
|
290
|
+
# promise.state #=> :pending
|
236
291
|
def self.execute(opts = {}, &block)
|
237
292
|
new(opts, &block).execute
|
238
293
|
end
|
239
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
|
+
#
|
240
302
|
# @return [Promise] the new promise
|
241
303
|
def then(rescuer = nil, &block)
|
242
304
|
raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
|
@@ -248,7 +310,7 @@ module Concurrent
|
|
248
310
|
on_reject: rescuer
|
249
311
|
)
|
250
312
|
|
251
|
-
|
313
|
+
synchronize do
|
252
314
|
child.state = :pending if @state == :pending
|
253
315
|
child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
|
254
316
|
child.on_reject(@reason) if @state == :rejected
|
@@ -258,13 +320,23 @@ module Concurrent
|
|
258
320
|
child
|
259
321
|
end
|
260
322
|
|
261
|
-
#
|
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
|
262
329
|
def on_success(&block)
|
263
330
|
raise ArgumentError.new('no block given') unless block_given?
|
264
331
|
self.then(&block)
|
265
332
|
end
|
266
333
|
|
267
|
-
#
|
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
|
268
340
|
def rescue(&block)
|
269
341
|
self.then(block)
|
270
342
|
end
|
@@ -366,6 +438,21 @@ module Concurrent
|
|
366
438
|
|
367
439
|
protected
|
368
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
|
+
|
369
456
|
# Aggregate a collection of zero or more promises under a composite promise,
|
370
457
|
# execute the aggregated promises and collect them into a standard Ruby array,
|
371
458
|
# call the given Ruby `Ennnumerable` predicate (such as `any?`, `all?`, `none?`,
|
@@ -389,8 +476,9 @@ module Concurrent
|
|
389
476
|
composite
|
390
477
|
end
|
391
478
|
|
479
|
+
# @!visibility private
|
392
480
|
def set_pending
|
393
|
-
|
481
|
+
synchronize do
|
394
482
|
@state = :pending
|
395
483
|
@children.each { |c| c.set_pending }
|
396
484
|
end
|
@@ -413,35 +501,40 @@ module Concurrent
|
|
413
501
|
nil
|
414
502
|
end
|
415
503
|
|
504
|
+
# @!visibility private
|
416
505
|
def notify_child(child)
|
417
506
|
if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) }
|
418
507
|
if_state(:rejected) { child.on_reject(@reason) }
|
419
508
|
end
|
420
509
|
|
421
510
|
# @!visibility private
|
422
|
-
def
|
423
|
-
|
424
|
-
success, value, reason
|
511
|
+
def complete(success, value, reason)
|
512
|
+
children_to_notify = synchronize do
|
513
|
+
set_state!(success, value, reason)
|
514
|
+
@children.dup
|
515
|
+
end
|
425
516
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
end
|
517
|
+
children_to_notify.each { |child| notify_child(child) }
|
518
|
+
observers.notify_and_delete_observers{ [Time.now, self.value, reason] }
|
519
|
+
end
|
430
520
|
|
431
|
-
|
521
|
+
# @!visibility private
|
522
|
+
def realize(task)
|
523
|
+
@executor.post do
|
524
|
+
success, value, reason = SafeTaskExecutor.new(task).execute(*@args)
|
525
|
+
complete(success, value, reason)
|
432
526
|
end
|
433
527
|
end
|
434
528
|
|
529
|
+
# @!visibility private
|
435
530
|
def set_state!(success, value, reason)
|
436
531
|
set_state(success, value, reason)
|
437
532
|
event.set
|
438
533
|
end
|
439
534
|
|
535
|
+
# @!visibility private
|
440
536
|
def synchronized_set_state!(success, value, reason)
|
441
|
-
|
442
|
-
set_state!(success, value, reason)
|
443
|
-
ensure
|
444
|
-
mutex.unlock
|
537
|
+
synchronize { set_state!(success, value, reason) }
|
445
538
|
end
|
446
539
|
end
|
447
540
|
end
|