o-concurrent-ruby 1.1.11

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +542 -0
  3. data/Gemfile +37 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +404 -0
  6. data/Rakefile +307 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +189 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby/concurrent/agent.rb +587 -0
  23. data/lib/concurrent-ruby/concurrent/array.rb +66 -0
  24. data/lib/concurrent-ruby/concurrent/async.rb +449 -0
  25. data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
  26. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  27. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
  28. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
  29. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
  30. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
  31. data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
  32. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
  33. data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
  34. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
  35. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
  36. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  37. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  38. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  39. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
  40. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
  41. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
  42. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
  43. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
  44. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
  45. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  46. data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  47. data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
  48. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  49. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  50. data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  51. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
  52. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  53. data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
  54. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  55. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  56. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  57. data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  58. data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
  59. data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
  60. data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
  61. data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
  62. data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
  63. data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
  64. data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
  65. data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
  66. data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
  67. data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
  68. data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
  69. data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
  70. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
  71. data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
  72. data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
  73. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
  74. data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
  75. data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
  76. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
  77. data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
  78. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
  79. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
  80. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
  81. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
  82. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
  83. data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
  84. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
  85. data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
  86. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
  87. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
  88. data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
  89. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
  90. data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
  91. data/lib/concurrent-ruby/concurrent/future.rb +141 -0
  92. data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
  93. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
  94. data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
  95. data/lib/concurrent-ruby/concurrent/map.rb +346 -0
  96. data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
  97. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
  98. data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
  99. data/lib/concurrent-ruby/concurrent/options.rb +42 -0
  100. data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
  101. data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
  102. data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
  103. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
  104. data/lib/concurrent-ruby/concurrent/set.rb +74 -0
  105. data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
  106. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  107. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
  108. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
  109. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
  110. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  111. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
  112. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
  113. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
  114. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
  115. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
  116. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
  117. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
  118. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
  119. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
  120. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
  121. data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
  122. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  123. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
  124. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  125. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
  126. data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  127. data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
  128. data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
  129. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  130. data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
  131. data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
  132. data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
  133. data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
  134. data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
  135. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
  136. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
  137. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
  138. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
  139. data/lib/concurrent-ruby/concurrent/version.rb +3 -0
  140. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
  141. data/lib/concurrent-ruby/concurrent.rb +134 -0
  142. metadata +192 -0
@@ -0,0 +1,42 @@
1
+ require 'concurrent/configuration'
2
+
3
+ module Concurrent
4
+
5
+ # @!visibility private
6
+ module Options
7
+
8
+ # Get the requested `Executor` based on the values set in the options hash.
9
+ #
10
+ # @param [Hash] opts the options defining the requested executor
11
+ # @option opts [Executor] :executor when set use the given `Executor` instance.
12
+ # Three special values are also supported: `:fast` returns the global fast executor,
13
+ # `:io` returns the global io executor, and `:immediate` returns a new
14
+ # `ImmediateExecutor` object.
15
+ #
16
+ # @return [Executor, nil] the requested thread pool, or nil when no option specified
17
+ #
18
+ # @!visibility private
19
+ def self.executor_from_options(opts = {}) # :nodoc:
20
+ if identifier = opts.fetch(:executor, nil)
21
+ executor(identifier)
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ def self.executor(executor_identifier)
28
+ case executor_identifier
29
+ when :fast
30
+ Concurrent.global_fast_executor
31
+ when :io
32
+ Concurrent.global_io_executor
33
+ when :immediate
34
+ Concurrent.global_immediate_executor
35
+ when Concurrent::ExecutorService
36
+ executor_identifier
37
+ else
38
+ raise ArgumentError, "executor not recognized by '#{executor_identifier}'"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,580 @@
1
+ require 'thread'
2
+ require 'concurrent/constants'
3
+ require 'concurrent/errors'
4
+ require 'concurrent/ivar'
5
+ require 'concurrent/executor/safe_task_executor'
6
+
7
+ require 'concurrent/options'
8
+
9
+ module Concurrent
10
+
11
+ PromiseExecutionError = Class.new(StandardError)
12
+
13
+ # Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)
14
+ # and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications.
15
+ #
16
+ # > A promise represents the eventual value returned from the single
17
+ # > completion of an operation.
18
+ #
19
+ # Promises are similar to futures and share many of the same behaviours.
20
+ # Promises are far more robust, however. Promises can be chained in a tree
21
+ # structure where each promise may have zero or more children. Promises are
22
+ # chained using the `then` method. The result of a call to `then` is always
23
+ # another promise. Promises are resolved asynchronously (with respect to the
24
+ # main thread) but in a strict order: parents are guaranteed to be resolved
25
+ # before their children, children before their younger siblings. The `then`
26
+ # method takes two parameters: an optional block to be executed upon parent
27
+ # resolution and an optional callable to be executed upon parent failure. The
28
+ # result of each promise is passed to each of its children upon resolution.
29
+ # When a promise is rejected all its children will be summarily rejected and
30
+ # will receive the reason.
31
+ #
32
+ # Promises have several possible states: *:unscheduled*, *:pending*,
33
+ # *:processing*, *:rejected*, or *:fulfilled*. These are also aggregated as
34
+ # `#incomplete?` and `#complete?`. When a Promise is created it is set to
35
+ # *:unscheduled*. Once the `#execute` method is called the state becomes
36
+ # *:pending*. Once a job is pulled from the thread pool's queue and is given
37
+ # to a thread for processing (often immediately upon `#post`) the state
38
+ # becomes *:processing*. The future will remain in this state until processing
39
+ # is complete. A future that is in the *:unscheduled*, *:pending*, or
40
+ # *:processing* is considered `#incomplete?`. A `#complete?` Promise is either
41
+ # *:rejected*, indicating that an exception was thrown during processing, or
42
+ # *:fulfilled*, indicating success. If a Promise is *:fulfilled* its `#value`
43
+ # will be updated to reflect the result of the operation. If *:rejected* the
44
+ # `reason` will be updated with a reference to the thrown exception. The
45
+ # predicate methods `#unscheduled?`, `#pending?`, `#rejected?`, and
46
+ # `#fulfilled?` can be called at any time to obtain the state of the Promise,
47
+ # as can the `#state` method, which returns a symbol.
48
+ #
49
+ # Retrieving the value of a promise is done through the `value` (alias:
50
+ # `deref`) method. Obtaining the value of a promise is a potentially blocking
51
+ # operation. When a promise is *rejected* a call to `value` will return `nil`
52
+ # immediately. When a promise is *fulfilled* a call to `value` will
53
+ # immediately return the current value. When a promise is *pending* a call to
54
+ # `value` will block until the promise is either *rejected* or *fulfilled*. A
55
+ # *timeout* value can be passed to `value` to limit how long the call will
56
+ # block. If `nil` the call will block indefinitely. If `0` the call will not
57
+ # block. Any other integer or float value will indicate the maximum number of
58
+ # seconds to block.
59
+ #
60
+ # Promises run on the global thread pool.
61
+ #
62
+ # @!macro copy_options
63
+ #
64
+ # ### Examples
65
+ #
66
+ # Start by requiring promises
67
+ #
68
+ # ```ruby
69
+ # require 'concurrent'
70
+ # ```
71
+ #
72
+ # Then create one
73
+ #
74
+ # ```ruby
75
+ # p = Concurrent::Promise.execute do
76
+ # # do something
77
+ # 42
78
+ # end
79
+ # ```
80
+ #
81
+ # Promises can be chained using the `then` method. The `then` method accepts a
82
+ # block and an executor, to be executed on fulfillment, and a callable argument to be executed
83
+ # on rejection. The result of the each promise is passed as the block argument
84
+ # to chained promises.
85
+ #
86
+ # ```ruby
87
+ # p = Concurrent::Promise.new{10}.then{|x| x * 2}.then{|result| result - 10 }.execute
88
+ # ```
89
+ #
90
+ # And so on, and so on, and so on...
91
+ #
92
+ # ```ruby
93
+ # p = Concurrent::Promise.fulfill(20).
94
+ # then{|result| result - 10 }.
95
+ # then{|result| result * 3 }.
96
+ # then(executor: different_executor){|result| result % 5 }.execute
97
+ # ```
98
+ #
99
+ # The initial state of a newly created Promise depends on the state of its parent:
100
+ # - if parent is *unscheduled* the child will be *unscheduled*
101
+ # - if parent is *pending* the child will be *pending*
102
+ # - if parent is *fulfilled* the child will be *pending*
103
+ # - if parent is *rejected* the child will be *pending* (but will ultimately be *rejected*)
104
+ #
105
+ # Promises are executed asynchronously from the main thread. By the time a
106
+ # child Promise finishes intialization it may be in a different state than its
107
+ # parent (by the time a child is created its parent may have completed
108
+ # execution and changed state). Despite being asynchronous, however, the order
109
+ # of execution of Promise objects in a chain (or tree) is strictly defined.
110
+ #
111
+ # There are multiple ways to create and execute a new `Promise`. Both ways
112
+ # provide identical behavior:
113
+ #
114
+ # ```ruby
115
+ # # create, operate, then execute
116
+ # p1 = Concurrent::Promise.new{ "Hello World!" }
117
+ # p1.state #=> :unscheduled
118
+ # p1.execute
119
+ #
120
+ # # create and immediately execute
121
+ # p2 = Concurrent::Promise.new{ "Hello World!" }.execute
122
+ #
123
+ # # execute during creation
124
+ # p3 = Concurrent::Promise.execute{ "Hello World!" }
125
+ # ```
126
+ #
127
+ # Once the `execute` method is called a `Promise` becomes `pending`:
128
+ #
129
+ # ```ruby
130
+ # p = Concurrent::Promise.execute{ "Hello, world!" }
131
+ # p.state #=> :pending
132
+ # p.pending? #=> true
133
+ # ```
134
+ #
135
+ # Wait a little bit, and the promise will resolve and provide a value:
136
+ #
137
+ # ```ruby
138
+ # p = Concurrent::Promise.execute{ "Hello, world!" }
139
+ # sleep(0.1)
140
+ #
141
+ # p.state #=> :fulfilled
142
+ # p.fulfilled? #=> true
143
+ # p.value #=> "Hello, world!"
144
+ # ```
145
+ #
146
+ # If an exception occurs, the promise will be rejected and will provide
147
+ # a reason for the rejection:
148
+ #
149
+ # ```ruby
150
+ # p = Concurrent::Promise.execute{ raise StandardError.new("Here comes the Boom!") }
151
+ # sleep(0.1)
152
+ #
153
+ # p.state #=> :rejected
154
+ # p.rejected? #=> true
155
+ # p.reason #=> "#<StandardError: Here comes the Boom!>"
156
+ # ```
157
+ #
158
+ # #### Rejection
159
+ #
160
+ # When a promise is rejected all its children will be rejected and will
161
+ # receive the rejection `reason` as the rejection callable parameter:
162
+ #
163
+ # ```ruby
164
+ # p = Concurrent::Promise.execute { Thread.pass; raise StandardError }
165
+ #
166
+ # c1 = p.then(-> reason { 42 })
167
+ # c2 = p.then(-> reason { raise 'Boom!' })
168
+ #
169
+ # c1.wait.state #=> :fulfilled
170
+ # c1.value #=> 45
171
+ # c2.wait.state #=> :rejected
172
+ # c2.reason #=> #<RuntimeError: Boom!>
173
+ # ```
174
+ #
175
+ # Once a promise is rejected it will continue to accept children that will
176
+ # receive immediately rejection (they will be executed asynchronously).
177
+ #
178
+ # #### Aliases
179
+ #
180
+ # The `then` method is the most generic alias: it accepts a block to be
181
+ # executed upon parent fulfillment and a callable to be executed upon parent
182
+ # rejection. At least one of them should be passed. The default block is `{
183
+ # |result| result }` that fulfills the child with the parent value. The
184
+ # default callable is `{ |reason| raise reason }` that rejects the child with
185
+ # the parent reason.
186
+ #
187
+ # - `on_success { |result| ... }` is the same as `then {|result| ... }`
188
+ # - `rescue { |reason| ... }` is the same as `then(Proc.new { |reason| ... } )`
189
+ # - `rescue` is aliased by `catch` and `on_error`
190
+ class Promise < IVar
191
+
192
+ # Initialize a new Promise with the provided options.
193
+ #
194
+ # @!macro executor_and_deref_options
195
+ #
196
+ # @!macro promise_init_options
197
+ #
198
+ # @option opts [Promise] :parent the parent `Promise` when building a chain/tree
199
+ # @option opts [Proc] :on_fulfill fulfillment handler
200
+ # @option opts [Proc] :on_reject rejection handler
201
+ # @option opts [object, Array] :args zero or more arguments to be passed
202
+ # the task block on execution
203
+ #
204
+ # @yield The block operation to be performed asynchronously.
205
+ #
206
+ # @raise [ArgumentError] if no block is given
207
+ #
208
+ # @see http://wiki.commonjs.org/wiki/Promises/A
209
+ # @see http://promises-aplus.github.io/promises-spec/
210
+ def initialize(opts = {}, &block)
211
+ opts.delete_if { |k, v| v.nil? }
212
+ super(NULL, opts.merge(__promise_body_from_block__: block), &nil)
213
+ end
214
+
215
+ # Create a new `Promise` and fulfill it immediately.
216
+ #
217
+ # @!macro executor_and_deref_options
218
+ #
219
+ # @!macro promise_init_options
220
+ #
221
+ # @raise [ArgumentError] if no block is given
222
+ #
223
+ # @return [Promise] the newly created `Promise`
224
+ def self.fulfill(value, opts = {})
225
+ Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
226
+ end
227
+
228
+ # Create a new `Promise` and reject it immediately.
229
+ #
230
+ # @!macro executor_and_deref_options
231
+ #
232
+ # @!macro promise_init_options
233
+ #
234
+ # @raise [ArgumentError] if no block is given
235
+ #
236
+ # @return [Promise] the newly created `Promise`
237
+ def self.reject(reason, opts = {})
238
+ Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
239
+ end
240
+
241
+ # Execute an `:unscheduled` `Promise`. Immediately sets the state to `:pending` and
242
+ # passes the block to a new thread/thread pool for eventual execution.
243
+ # Does nothing if the `Promise` is in any state other than `:unscheduled`.
244
+ #
245
+ # @return [Promise] a reference to `self`
246
+ def execute
247
+ if root?
248
+ if compare_and_set_state(:pending, :unscheduled)
249
+ set_pending
250
+ realize(@promise_body)
251
+ end
252
+ else
253
+ compare_and_set_state(:pending, :unscheduled)
254
+ @parent.execute
255
+ end
256
+ self
257
+ end
258
+
259
+ # @!macro ivar_set_method
260
+ #
261
+ # @raise [Concurrent::PromiseExecutionError] if not the root promise
262
+ def set(value = NULL, &block)
263
+ raise PromiseExecutionError.new('supported only on root promise') unless root?
264
+ check_for_block_or_value!(block_given?, value)
265
+ synchronize do
266
+ if @state != :unscheduled
267
+ raise MultipleAssignmentError
268
+ else
269
+ @promise_body = block || Proc.new { |result| value }
270
+ end
271
+ end
272
+ execute
273
+ end
274
+
275
+ # @!macro ivar_fail_method
276
+ #
277
+ # @raise [Concurrent::PromiseExecutionError] if not the root promise
278
+ def fail(reason = StandardError.new)
279
+ set { raise reason }
280
+ end
281
+
282
+ # Create a new `Promise` object with the given block, execute it, and return the
283
+ # `:pending` object.
284
+ #
285
+ # @!macro executor_and_deref_options
286
+ #
287
+ # @!macro promise_init_options
288
+ #
289
+ # @return [Promise] the newly created `Promise` in the `:pending` state
290
+ #
291
+ # @raise [ArgumentError] if no block is given
292
+ #
293
+ # @example
294
+ # promise = Concurrent::Promise.execute{ sleep(1); 42 }
295
+ # promise.state #=> :pending
296
+ def self.execute(opts = {}, &block)
297
+ new(opts, &block).execute
298
+ end
299
+
300
+ # Chain a new promise off the current promise.
301
+ #
302
+ # @return [Promise] the new promise
303
+ # @yield The block operation to be performed asynchronously.
304
+ # @overload then(rescuer, executor, &block)
305
+ # @param [Proc] rescuer An optional rescue block to be executed if the
306
+ # promise is rejected.
307
+ # @param [ThreadPool] executor An optional thread pool executor to be used
308
+ # in the new Promise
309
+ # @overload then(rescuer, executor: executor, &block)
310
+ # @param [Proc] rescuer An optional rescue block to be executed if the
311
+ # promise is rejected.
312
+ # @param [ThreadPool] executor An optional thread pool executor to be used
313
+ # in the new Promise
314
+ def then(*args, &block)
315
+ if args.last.is_a?(::Hash)
316
+ executor = args.pop[:executor]
317
+ rescuer = args.first
318
+ else
319
+ rescuer, executor = args
320
+ end
321
+
322
+ executor ||= @executor
323
+
324
+ raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
325
+ block = Proc.new { |result| result } unless block_given?
326
+ child = Promise.new(
327
+ parent: self,
328
+ executor: executor,
329
+ on_fulfill: block,
330
+ on_reject: rescuer
331
+ )
332
+
333
+ synchronize do
334
+ child.state = :pending if @state == :pending
335
+ child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
336
+ child.on_reject(@reason) if @state == :rejected
337
+ @children << child
338
+ end
339
+
340
+ child
341
+ end
342
+
343
+ # Chain onto this promise an action to be undertaken on success
344
+ # (fulfillment).
345
+ #
346
+ # @yield The block to execute
347
+ #
348
+ # @return [Promise] self
349
+ def on_success(&block)
350
+ raise ArgumentError.new('no block given') unless block_given?
351
+ self.then(&block)
352
+ end
353
+
354
+ # Chain onto this promise an action to be undertaken on failure
355
+ # (rejection).
356
+ #
357
+ # @yield The block to execute
358
+ #
359
+ # @return [Promise] self
360
+ def rescue(&block)
361
+ self.then(block)
362
+ end
363
+
364
+ alias_method :catch, :rescue
365
+ alias_method :on_error, :rescue
366
+
367
+ # Yield the successful result to the block that returns a promise. If that
368
+ # promise is also successful the result is the result of the yielded promise.
369
+ # If either part fails the whole also fails.
370
+ #
371
+ # @example
372
+ # Promise.execute { 1 }.flat_map { |v| Promise.execute { v + 2 } }.value! #=> 3
373
+ #
374
+ # @return [Promise]
375
+ def flat_map(&block)
376
+ child = Promise.new(
377
+ parent: self,
378
+ executor: ImmediateExecutor.new,
379
+ )
380
+
381
+ on_error { |e| child.on_reject(e) }
382
+ on_success do |result1|
383
+ begin
384
+ inner = block.call(result1)
385
+ inner.execute
386
+ inner.on_success { |result2| child.on_fulfill(result2) }
387
+ inner.on_error { |e| child.on_reject(e) }
388
+ rescue => e
389
+ child.on_reject(e)
390
+ end
391
+ end
392
+
393
+ child
394
+ end
395
+
396
+ # Builds a promise that produces the result of promises in an Array
397
+ # and fails if any of them fails.
398
+ #
399
+ # @overload zip(*promises)
400
+ # @param [Array<Promise>] promises
401
+ #
402
+ # @overload zip(*promises, opts)
403
+ # @param [Array<Promise>] promises
404
+ # @param [Hash] opts the configuration options
405
+ # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance.
406
+ # @option opts [Boolean] :execute (true) execute promise before returning
407
+ #
408
+ # @return [Promise<Array>]
409
+ def self.zip(*promises)
410
+ opts = promises.last.is_a?(::Hash) ? promises.pop.dup : {}
411
+ opts[:executor] ||= ImmediateExecutor.new
412
+ zero = if !opts.key?(:execute) || opts.delete(:execute)
413
+ fulfill([], opts)
414
+ else
415
+ Promise.new(opts) { [] }
416
+ end
417
+
418
+ promises.reduce(zero) do |p1, p2|
419
+ p1.flat_map do |results|
420
+ p2.then do |next_result|
421
+ results << next_result
422
+ end
423
+ end
424
+ end
425
+ end
426
+
427
+ # Builds a promise that produces the result of self and others in an Array
428
+ # and fails if any of them fails.
429
+ #
430
+ # @overload zip(*promises)
431
+ # @param [Array<Promise>] others
432
+ #
433
+ # @overload zip(*promises, opts)
434
+ # @param [Array<Promise>] others
435
+ # @param [Hash] opts the configuration options
436
+ # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance.
437
+ # @option opts [Boolean] :execute (true) execute promise before returning
438
+ #
439
+ # @return [Promise<Array>]
440
+ def zip(*others)
441
+ self.class.zip(self, *others)
442
+ end
443
+
444
+ # Aggregates a collection of promises and executes the `then` condition
445
+ # if all aggregated promises succeed. Executes the `rescue` handler with
446
+ # a `Concurrent::PromiseExecutionError` if any of the aggregated promises
447
+ # fail. Upon execution will execute any of the aggregate promises that
448
+ # were not already executed.
449
+ #
450
+ # @!macro promise_self_aggregate
451
+ #
452
+ # The returned promise will not yet have been executed. Additional `#then`
453
+ # and `#rescue` handlers may still be provided. Once the returned promise
454
+ # is execute the aggregate promises will be also be executed (if they have
455
+ # not been executed already). The results of the aggregate promises will
456
+ # be checked upon completion. The necessary `#then` and `#rescue` blocks
457
+ # on the aggregating promise will then be executed as appropriate. If the
458
+ # `#rescue` handlers are executed the raises exception will be
459
+ # `Concurrent::PromiseExecutionError`.
460
+ #
461
+ # @param [Array] promises Zero or more promises to aggregate
462
+ # @return [Promise] an unscheduled (not executed) promise that aggregates
463
+ # the promises given as arguments
464
+ def self.all?(*promises)
465
+ aggregate(:all?, *promises)
466
+ end
467
+
468
+ # Aggregates a collection of promises and executes the `then` condition
469
+ # if any aggregated promises succeed. Executes the `rescue` handler with
470
+ # a `Concurrent::PromiseExecutionError` if any of the aggregated promises
471
+ # fail. Upon execution will execute any of the aggregate promises that
472
+ # were not already executed.
473
+ #
474
+ # @!macro promise_self_aggregate
475
+ def self.any?(*promises)
476
+ aggregate(:any?, *promises)
477
+ end
478
+
479
+ protected
480
+
481
+ def ns_initialize(value, opts)
482
+ super
483
+
484
+ @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor
485
+ @args = get_arguments_from(opts)
486
+
487
+ @parent = opts.fetch(:parent) { nil }
488
+ @on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
489
+ @on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
490
+
491
+ @promise_body = opts[:__promise_body_from_block__] || Proc.new { |result| result }
492
+ @state = :unscheduled
493
+ @children = []
494
+ end
495
+
496
+ # Aggregate a collection of zero or more promises under a composite promise,
497
+ # execute the aggregated promises and collect them into a standard Ruby array,
498
+ # call the given Ruby `Ennnumerable` predicate (such as `any?`, `all?`, `none?`,
499
+ # or `one?`) on the collection checking for the success or failure of each,
500
+ # then executing the composite's `#then` handlers if the predicate returns
501
+ # `true` or executing the composite's `#rescue` handlers if the predicate
502
+ # returns false.
503
+ #
504
+ # @!macro promise_self_aggregate
505
+ def self.aggregate(method, *promises)
506
+ composite = Promise.new do
507
+ completed = promises.collect do |promise|
508
+ promise.execute if promise.unscheduled?
509
+ promise.wait
510
+ promise
511
+ end
512
+ unless completed.empty? || completed.send(method){|promise| promise.fulfilled? }
513
+ raise PromiseExecutionError
514
+ end
515
+ end
516
+ composite
517
+ end
518
+
519
+ # @!visibility private
520
+ def set_pending
521
+ synchronize do
522
+ @state = :pending
523
+ @children.each { |c| c.set_pending }
524
+ end
525
+ end
526
+
527
+ # @!visibility private
528
+ def root? # :nodoc:
529
+ @parent.nil?
530
+ end
531
+
532
+ # @!visibility private
533
+ def on_fulfill(result)
534
+ realize Proc.new { @on_fulfill.call(result) }
535
+ nil
536
+ end
537
+
538
+ # @!visibility private
539
+ def on_reject(reason)
540
+ realize Proc.new { @on_reject.call(reason) }
541
+ nil
542
+ end
543
+
544
+ # @!visibility private
545
+ def notify_child(child)
546
+ if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) }
547
+ if_state(:rejected) { child.on_reject(@reason) }
548
+ end
549
+
550
+ # @!visibility private
551
+ def complete(success, value, reason)
552
+ children_to_notify = synchronize do
553
+ set_state!(success, value, reason)
554
+ @children.dup
555
+ end
556
+
557
+ children_to_notify.each { |child| notify_child(child) }
558
+ observers.notify_and_delete_observers{ [Time.now, self.value, reason] }
559
+ end
560
+
561
+ # @!visibility private
562
+ def realize(task)
563
+ @executor.post do
564
+ success, value, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args)
565
+ complete(success, value, reason)
566
+ end
567
+ end
568
+
569
+ # @!visibility private
570
+ def set_state!(success, value, reason)
571
+ set_state(success, value, reason)
572
+ event.set
573
+ end
574
+
575
+ # @!visibility private
576
+ def synchronized_set_state!(success, value, reason)
577
+ synchronize { set_state!(success, value, reason) }
578
+ end
579
+ end
580
+ end