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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +114 -3
  3. data/README.md +111 -55
  4. data/lib/concurrent.rb +90 -14
  5. data/lib/concurrent/async.rb +143 -51
  6. data/lib/concurrent/atom.rb +131 -0
  7. data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
  8. data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
  9. data/lib/concurrent/atomic/atomic_reference.rb +49 -0
  10. data/lib/concurrent/atomic/condition.rb +23 -12
  11. data/lib/concurrent/atomic/count_down_latch.rb +23 -21
  12. data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
  13. data/lib/concurrent/atomic/event.rb +33 -42
  14. data/lib/concurrent/atomic/read_write_lock.rb +252 -0
  15. data/lib/concurrent/atomic/semaphore.rb +64 -89
  16. data/lib/concurrent/atomic/thread_local_var.rb +130 -58
  17. data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
  18. data/lib/concurrent/atomic_reference/direct_update.rb +34 -3
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
  21. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
  22. data/lib/concurrent/atomic_reference/rbx.rb +4 -1
  23. data/lib/concurrent/atomic_reference/ruby.rb +6 -3
  24. data/lib/concurrent/atomics.rb +74 -4
  25. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
  26. data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
  27. data/lib/concurrent/collection/priority_queue.rb +300 -245
  28. data/lib/concurrent/concern/deprecation.rb +34 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +27 -0
  31. data/lib/concurrent/concern/obligation.rb +228 -0
  32. data/lib/concurrent/concern/observable.rb +85 -0
  33. data/lib/concurrent/configuration.rb +234 -109
  34. data/lib/concurrent/dataflow.rb +2 -3
  35. data/lib/concurrent/delay.rb +141 -50
  36. data/lib/concurrent/edge.rb +30 -0
  37. data/lib/concurrent/errors.rb +19 -7
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +51 -33
  40. data/lib/concurrent/executor/executor.rb +46 -299
  41. data/lib/concurrent/executor/executor_service.rb +521 -0
  42. data/lib/concurrent/executor/fixed_thread_pool.rb +196 -23
  43. data/lib/concurrent/executor/immediate_executor.rb +9 -9
  44. data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
  45. data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
  46. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  47. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  48. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  49. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  50. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  51. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  52. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  53. data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
  54. data/lib/concurrent/executor/timer_set.rb +96 -84
  55. data/lib/concurrent/executors.rb +1 -1
  56. data/lib/concurrent/future.rb +71 -38
  57. data/lib/concurrent/immutable_struct.rb +89 -0
  58. data/lib/concurrent/ivar.rb +152 -60
  59. data/lib/concurrent/lazy_register.rb +40 -20
  60. data/lib/concurrent/maybe.rb +226 -0
  61. data/lib/concurrent/mutable_struct.rb +227 -0
  62. data/lib/concurrent/mvar.rb +44 -43
  63. data/lib/concurrent/promise.rb +229 -136
  64. data/lib/concurrent/scheduled_task.rb +341 -43
  65. data/lib/concurrent/settable_struct.rb +127 -0
  66. data/lib/concurrent/synchronization.rb +17 -0
  67. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  68. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  69. data/lib/concurrent/synchronization/condition.rb +53 -0
  70. data/lib/concurrent/synchronization/java_object.rb +34 -0
  71. data/lib/concurrent/synchronization/lock.rb +32 -0
  72. data/lib/concurrent/synchronization/monitor_object.rb +26 -0
  73. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  74. data/lib/concurrent/synchronization/object.rb +78 -0
  75. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  76. data/lib/concurrent/timer_task.rb +92 -103
  77. data/lib/concurrent/tvar.rb +42 -38
  78. data/lib/concurrent/utilities.rb +3 -1
  79. data/lib/concurrent/utility/at_exit.rb +97 -0
  80. data/lib/concurrent/utility/engine.rb +44 -0
  81. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  82. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  83. data/lib/concurrent/utility/processor_counter.rb +156 -0
  84. data/lib/concurrent/utility/timeout.rb +18 -14
  85. data/lib/concurrent/utility/timer.rb +11 -6
  86. data/lib/concurrent/version.rb +2 -1
  87. data/lib/concurrent_ruby.rb +1 -0
  88. data/lib/concurrent_ruby_ext.jar +0 -0
  89. metadata +46 -66
  90. data/lib/concurrent/actor.rb +0 -103
  91. data/lib/concurrent/actor/behaviour.rb +0 -70
  92. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  93. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  94. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  95. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  96. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  97. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  98. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  99. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  100. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  101. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  102. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  103. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  104. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  105. data/lib/concurrent/actor/context.rb +0 -154
  106. data/lib/concurrent/actor/core.rb +0 -217
  107. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  108. data/lib/concurrent/actor/envelope.rb +0 -41
  109. data/lib/concurrent/actor/errors.rb +0 -27
  110. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  111. data/lib/concurrent/actor/public_delegations.rb +0 -40
  112. data/lib/concurrent/actor/reference.rb +0 -81
  113. data/lib/concurrent/actor/root.rb +0 -37
  114. data/lib/concurrent/actor/type_check.rb +0 -48
  115. data/lib/concurrent/actor/utils.rb +0 -10
  116. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  117. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  118. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  119. data/lib/concurrent/actor/utils/pool.rb +0 -59
  120. data/lib/concurrent/actress.rb +0 -3
  121. data/lib/concurrent/agent.rb +0 -209
  122. data/lib/concurrent/atomic.rb +0 -92
  123. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  124. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  125. data/lib/concurrent/atomic/synchronization.rb +0 -51
  126. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  127. data/lib/concurrent/channel/channel.rb +0 -41
  128. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  129. data/lib/concurrent/channel/waitable_list.rb +0 -40
  130. data/lib/concurrent/channels.rb +0 -5
  131. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  132. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  133. data/lib/concurrent/collections.rb +0 -3
  134. data/lib/concurrent/dereferenceable.rb +0 -108
  135. data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
  136. data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
  137. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
  138. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
  139. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  140. data/lib/concurrent/logging.rb +0 -20
  141. data/lib/concurrent/obligation.rb +0 -171
  142. data/lib/concurrent/observable.rb +0 -73
  143. data/lib/concurrent/options_parser.rb +0 -48
  144. data/lib/concurrent/utility/processor_count.rb +0 -152
  145. data/lib/extension_helper.rb +0 -37
@@ -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 contain one item.
8
- # Taking a value from an empty `MVar` blocks, as does putting a value into a full one.
9
- # You can either think of them as blocking queue of length one, or a special kind of
10
- # mutable variable.
11
- #
12
- # On top of the fundamental `#put` and `#take` operations, we also provide a `#mutate`
13
- # that is atomic with respect to operations on the same instance. These operations all
14
- # support timeouts.
15
- #
16
- # We also support non-blocking operations `#try_put!` and `#try_take!`, a `#set!` that
17
- # ignores existing values, a `#value` that returns the value without removing it or
18
- # returns `MVar::EMPTY`, and a `#modify!` that yields `MVar::EMPTY` if the `MVar` is
19
- # empty and can be used to set `MVar::EMPTY`. You shouldn't use these operations in the
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
- # **See Also:**
30
- #
31
- # 1. P. Barth, R. Nikhil, and Arvind. [M-Structures: Extending a parallel, non-
32
- # strict, functional language with state](http://dl.acm.org/citation.cfm?id=652538). In Proceedings of the 5th ACM Conference on Functional Programming Languages and Computer Architecture (FPCA), 1991.
33
- # 2. S. Peyton Jones, A. Gordon, and S. Finne. [Concurrent Haskell](http://dl.acm.org/citation.cfm?id=237794). In Proceedings of the 23rd Symposium on Principles of Programming Languages (PoPL), 1996.
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
- # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
49
- # operation pool (for long-running operations), when `false` will execute the future on the
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 = Condition.new
61
- @full_condition = Condition.new
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
- not empty?
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
- remaining = Condition::Result.new(timeout)
210
- while yield && remaining.can_wait?
211
- remaining = condition.wait(@mutex, remaining.remaining_time)
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
@@ -1,7 +1,7 @@
1
1
  require 'thread'
2
-
3
- require 'concurrent/obligation'
4
- require 'concurrent/options_parser'
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 completion of an operation.
14
- #
15
- # Promises are similar to futures and share many of the same behaviours. Promises are far more
16
- # robust, however. Promises can be chained in a tree structure where each promise may have zero
17
- # or more children. Promises are chained using the `then` method. The result of a call to `then`
18
- # is always another promise. Promises are resolved asynchronously (with respect to the main thread)
19
- # but in a strict order: parents are guaranteed to be resolved before their children, children
20
- # before their younger siblings. The `then` method takes two parameters: an optional block to
21
- # be executed upon parent resolution and an optional callable to be executed upon parent failure.
22
- # The result of each promise is passed to each of its children upon resolution. When a promise
23
- # is rejected all its children will be summarily rejected and will receive the reason.
24
- #
25
- # Promises have four possible states: *unscheduled*, *pending*, *rejected*, and *fulfilled*. A
26
- # Promise created using `.new` will be *unscheduled*. It is scheduled by calling the `execute`
27
- # method. Upon execution the Promise and all its children will be set to *pending*. When a promise
28
- # is *pending* it will remain in that state until processing is complete. A completed Promise is
29
- # either *rejected*, indicating that an exception was thrown during processing, or *fulfilled*,
30
- # indicating it succeeded. If a Promise is *fulfilled* its `value` will be updated to reflect
31
- # the result of the operation. If *rejected* the `reason` will be updated with a reference to
32
- # the thrown exception. The predicate methods `unscheduled?`, `pending?`, `rejected?`, and
33
- # `fulfilled?` can be called at any time to obtain the state of the Promise, as can the `state`
34
- # method, which returns a symbol. A Promise created using `.execute` will be *pending*, a Promise
35
- # created using `.fulfill(value)` will be *fulfilled* with the given value and a Promise created
36
- # using `.reject(reason)` will be *rejected* with the given reason.
37
- #
38
- # Retrieving the value of a promise is done through the `value` (alias: `deref`) method. Obtaining
39
- # the value of a promise is a potentially blocking operation. When a promise is *rejected* a call
40
- # to `value` will return `nil` immediately. When a promise is *fulfilled* a call to `value` will
41
- # immediately return the current value. When a promise is *pending* a call to `value` will block
42
- # until the promise is either *rejected* or *fulfilled*. A *timeout* value can be passed to `value`
43
- # to limit how long the call will block. If `nil` the call will block indefinitely. If `0` the call
44
- # will not block. Any other integer or float value will indicate the maximum number of seconds to block.
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 block, to be executed
66
- # on fulfillment, and a callable argument to be executed on rejection. The result of the each promise
67
- # is passed as the block argument to chained promises.
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 child Promise finishes
89
- # nitialization it may be in a different state that its parent (by the time a child is created its parent
90
- # may have completed execution and changed state). Despite being asynchronous, however, the order of
91
- # execution of Promise objects in a chain (or tree) is strictly defined.
92
- #
93
- # There are multiple ways to create and execute a new `Promise`. Both ways provide identical behavior:
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 receive the rejection `reason`
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 receive immediately rejection
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 executed upon parent fulfillment
162
- # and a callable to be executed upon parent rejection. At least one of them should be passed. The default
163
- # block is `{ |result| result }` that fulfills the child with the parent value. The default callable is
164
- # `{ |reason| raise reason }` that rejects the child with the parent reason.
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
- # @param [Hash] opts the options used to define the behavior at update and deref
190
+ # @!macro executor_and_deref_options
191
+ #
192
+ # @!macro [attach] promise_init_options
176
193
  #
177
- # @option opts [Promise] :parent the parent `Promise` when building a chain/tree
178
- # @option opts [Proc] :on_fulfill fulfillment handler
179
- # @option opts [Proc] :on_reject rejection handler
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
- # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
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
- # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
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
- # @return [Promise]
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
- # @return [Promise]
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
- # @return [Promise]
222
- # @since 0.5.0
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
- # @since 0.5.0
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
- mutex.synchronize do
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
- # @return [Promise]
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
- # @return [Promise]
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
- mutex.synchronize do
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 realize(task)
423
- @executor.post do
424
- success, value, reason = SafeTaskExecutor.new(task).execute
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
- children_to_notify = mutex.synchronize do
427
- set_state!(success, value, reason)
428
- @children.dup
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
- children_to_notify.each { |child| notify_child(child) }
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
- mutex.lock
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