concurrent-ruby 0.7.0-x86-mingw32 → 0.7.1-x86-mingw32

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 (41) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +138 -0
  3. data/README.md +73 -105
  4. data/lib/1.9/concurrent_ruby_ext.so +0 -0
  5. data/lib/2.0/concurrent_ruby_ext.so +0 -0
  6. data/lib/concurrent/actor.rb +11 -12
  7. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +1 -1
  8. data/lib/concurrent/actor/behaviour/linking.rb +4 -1
  9. data/lib/concurrent/actor/behaviour/pausing.rb +2 -2
  10. data/lib/concurrent/actor/behaviour/supervised.rb +2 -1
  11. data/lib/concurrent/actor/behaviour/termination.rb +1 -1
  12. data/lib/concurrent/actor/context.rb +2 -1
  13. data/lib/concurrent/actor/core.rb +7 -3
  14. data/lib/concurrent/actor/utils/balancer.rb +4 -2
  15. data/lib/concurrent/actor/utils/pool.rb +1 -1
  16. data/lib/concurrent/agent.rb +1 -22
  17. data/lib/concurrent/async.rb +1 -79
  18. data/lib/concurrent/atomic.rb +1 -1
  19. data/lib/concurrent/atomic/thread_local_var.rb +71 -24
  20. data/lib/concurrent/atomics.rb +0 -1
  21. data/lib/concurrent/configuration.rb +11 -5
  22. data/lib/concurrent/dataflow.rb +1 -30
  23. data/lib/concurrent/dereferenceable.rb +9 -2
  24. data/lib/concurrent/executor/indirect_immediate_executor.rb +46 -0
  25. data/lib/concurrent/executor/java_thread_pool_executor.rb +2 -4
  26. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +24 -22
  27. data/lib/concurrent/executor/thread_pool_executor.rb +2 -0
  28. data/lib/concurrent/executor/timer_set.rb +7 -8
  29. data/lib/concurrent/executors.rb +1 -0
  30. data/lib/concurrent/future.rb +7 -29
  31. data/lib/concurrent/ivar.rb +9 -0
  32. data/lib/concurrent/logging.rb +3 -0
  33. data/lib/concurrent/mvar.rb +26 -9
  34. data/lib/concurrent/observable.rb +33 -0
  35. data/lib/concurrent/promise.rb +59 -1
  36. data/lib/concurrent/scheduled_task.rb +1 -0
  37. data/lib/concurrent/timer_task.rb +18 -18
  38. data/lib/concurrent/tvar.rb +2 -0
  39. data/lib/concurrent/version.rb +1 -1
  40. data/lib/concurrent_ruby_ext.so +0 -0
  41. metadata +21 -4
@@ -3,8 +3,15 @@ module Concurrent
3
3
  # Object references in Ruby are mutable. This can lead to serious problems when
4
4
  # the `#value` of a concurrent object is a mutable reference. Which is always the
5
5
  # case unless the value is a `Fixnum`, `Symbol`, or similar "primitive" data type.
6
- # Most classes in this library that expose a `#value` getter method do so using
7
- # this mixin module.
6
+ # Most classes in this library that expose a `#value` getter method do so using the
7
+ # `Dereferenceable` mixin module.
8
+ #
9
+ # Objects with this mixin can be configured with a few options that can help protect
10
+ # the program from potentially dangerous operations.
11
+ #
12
+ # * `:dup_on_deref` when true will call the `#dup` method on the `value` object every time the `#value` method is called (default: false)
13
+ # * `:freeze_on_deref` when true will call the `#freeze` method on the `value` object every time the `#value` method is called (default: false)
14
+ # * `:copy_on_deref` when given a `Proc` object the `Proc` will be run every time the `#value` method is called. The `Proc` will be given the current `value` as its only parameter and the result returned by the block will be the return value of the `#value` call. When `nil` this option will be ignored (default: nil)
8
15
  module Dereferenceable
9
16
 
10
17
  # Return the value this object represents after applying the options specified
@@ -0,0 +1,46 @@
1
+ require 'concurrent/executor/executor'
2
+
3
+ module Concurrent
4
+ # An executor service which runs all operations on a new thread, blocking
5
+ # until it completes. Operations are performed in the order they are received
6
+ # and no two operations can be performed simultaneously.
7
+ #
8
+ # This executor service exists mainly for testing an debugging. When used it
9
+ # immediately runs every `#post` operation on a new thread, blocking the
10
+ # current thread until the operation is complete. This is similar to how the
11
+ # ImmediateExecutor works, but the operation has the full stack of the new
12
+ # thread at its disposal. This can be helpful when the operations will spawn
13
+ # more operations on the same executor and so on - such a situation might
14
+ # overflow the single stack in case of an ImmediateExecutor, which is
15
+ # inconsistent with how it would behave for a threaded executor.
16
+ #
17
+ # @note Intended for use primarily in testing and debugging.
18
+ class IndirectImmediateExecutor < ImmediateExecutor
19
+ # Creates a new executor
20
+ def initialize
21
+ super
22
+ @internal_executor = PerThreadExecutor.new
23
+ end
24
+
25
+ # @!macro executor_method_post
26
+ def post(*args, &task)
27
+ raise ArgumentError.new("no block given") unless block_given?
28
+ return false unless running?
29
+
30
+ event = Concurrent::Event.new
31
+ internal_executor.post do
32
+ begin
33
+ task.call(*args)
34
+ ensure
35
+ event.set
36
+ end
37
+ end
38
+ event.wait
39
+
40
+ true
41
+ end
42
+
43
+ private
44
+ attr_reader :internal_executor
45
+ end
46
+ end
@@ -74,9 +74,7 @@ if RUBY_PLATFORM == 'java'
74
74
  raise ArgumentError.new('min_threads cannot be more than max_threads') if min_length > max_length
75
75
  raise ArgumentError.new("#{@overflow_policy} is not a valid overflow policy") unless OVERFLOW_POLICIES.keys.include?(@overflow_policy)
76
76
 
77
- if min_length == 0 && @max_queue == 0
78
- queue = java.util.concurrent.SynchronousQueue.new
79
- elsif @max_queue == 0
77
+ if @max_queue == 0
80
78
  queue = java.util.concurrent.LinkedBlockingQueue.new
81
79
  else
82
80
  queue = java.util.concurrent.LinkedBlockingQueue.new(@max_queue)
@@ -90,7 +88,7 @@ if RUBY_PLATFORM == 'java'
90
88
  set_shutdown_hook
91
89
  end
92
90
 
93
- # @!macro executor_module_method_can_overflow_question
91
+ # @!macro executor_module_method_can_overflow_question
94
92
  def can_overflow?
95
93
  @max_queue != 0
96
94
  end
@@ -11,20 +11,20 @@ module Concurrent
11
11
  include RubyExecutor
12
12
 
13
13
  # Default maximum number of threads that will be created in the pool.
14
- DEFAULT_MAX_POOL_SIZE = 2**15 # 32768
14
+ DEFAULT_MAX_POOL_SIZE = 2**15 # 32768
15
15
 
16
16
  # Default minimum number of threads that will be retained in the pool.
17
- DEFAULT_MIN_POOL_SIZE = 0
17
+ DEFAULT_MIN_POOL_SIZE = 0
18
18
 
19
19
  # Default maximum number of tasks that may be added to the task queue.
20
- DEFAULT_MAX_QUEUE_SIZE = 0
20
+ DEFAULT_MAX_QUEUE_SIZE = 0
21
21
 
22
22
  # Default maximum number of seconds a thread in the pool may remain idle
23
23
  # before being reclaimed.
24
24
  DEFAULT_THREAD_IDLETIMEOUT = 60
25
25
 
26
26
  # The set of possible overflow policies that may be set at thread pool creation.
27
- OVERFLOW_POLICIES = [:abort, :discard, :caller_runs]
27
+ OVERFLOW_POLICIES = [:abort, :discard, :caller_runs]
28
28
 
29
29
  # The maximum number of threads that may be created in the pool.
30
30
  attr_reader :max_length
@@ -77,10 +77,10 @@ module Concurrent
77
77
  #
78
78
  # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
79
79
  def initialize(opts = {})
80
- @min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
81
- @max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
82
- @idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
83
- @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
80
+ @min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
81
+ @max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
82
+ @idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
83
+ @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
84
84
  @overflow_policy = opts.fetch(:overflow_policy, :abort)
85
85
 
86
86
  raise ArgumentError.new('max_threads must be greater than zero') if @max_length <= 0
@@ -90,13 +90,13 @@ module Concurrent
90
90
 
91
91
  init_executor
92
92
 
93
- @pool = []
94
- @queue = Queue.new
93
+ @pool = []
94
+ @queue = Queue.new
95
95
  @scheduled_task_count = 0
96
96
  @completed_task_count = 0
97
- @largest_length = 0
97
+ @largest_length = 0
98
98
 
99
- @gc_interval = opts.fetch(:gc_interval, 1).to_i # undocumented
99
+ @gc_interval = opts.fetch(:gc_interval, 1).to_i # undocumented
100
100
  @last_gc_time = Time.now.to_f - [1.0, (@gc_interval * 2.0)].max
101
101
  end
102
102
 
@@ -109,15 +109,16 @@ module Concurrent
109
109
  #
110
110
  # @return [Integer] the length
111
111
  def length
112
- mutex.synchronize{ running? ? @pool.length : 0 }
112
+ mutex.synchronize { running? ? @pool.length : 0 }
113
113
  end
114
+
114
115
  alias_method :current_length, :length
115
116
 
116
117
  # The number of tasks in the queue awaiting execution.
117
118
  #
118
119
  # @return [Integer] the queue_length
119
120
  def queue_length
120
- mutex.synchronize{ running? ? @queue.length : 0 }
121
+ mutex.synchronize { running? ? @queue.length : 0 }
121
122
  end
122
123
 
123
124
  # Number of tasks that may be enqueued before reaching `max_queue` and rejecting
@@ -152,7 +153,7 @@ module Concurrent
152
153
  def on_worker_exit(worker)
153
154
  mutex.synchronize do
154
155
  @pool.delete(worker)
155
- if @pool.empty? && ! running?
156
+ if @pool.empty? && !running?
156
157
  stop_event.set
157
158
  stopped_event.set
158
159
  end
@@ -177,7 +178,7 @@ module Concurrent
177
178
  if @pool.empty?
178
179
  stopped_event.set
179
180
  else
180
- @pool.length.times{ @queue << :stop }
181
+ @pool.length.times { @queue << :stop }
181
182
  end
182
183
  end
183
184
 
@@ -196,7 +197,7 @@ module Concurrent
196
197
  # @!visibility private
197
198
  def ensure_capacity?
198
199
  additional = 0
199
- capacity = true
200
+ capacity = true
200
201
 
201
202
  if @pool.size < @min_length
202
203
  additional = @min_length - @pool.size
@@ -254,10 +255,11 @@ module Concurrent
254
255
  # @!visibility private
255
256
  def prune_pool
256
257
  if Time.now.to_f - @gc_interval >= @last_gc_time
257
- @pool.delete_if do |worker|
258
- worker.dead? ||
259
- (@idletime == 0 ? false : Time.now.to_f - @idletime > worker.last_activity)
260
- end
258
+ @pool.delete_if { |worker| worker.dead? }
259
+ # send :stop for each thread over idletime
260
+ @pool.
261
+ select { |worker| @idletime != 0 && Time.now.to_f - @idletime > worker.last_activity }.
262
+ each { @queue << :stop }
261
263
  @last_gc_time = Time.now.to_f
262
264
  end
263
265
  end
@@ -266,7 +268,7 @@ module Concurrent
266
268
  #
267
269
  # @!visibility private
268
270
  def drain_pool
269
- @pool.each {|worker| worker.kill }
271
+ @pool.each { |worker| worker.kill }
270
272
  @pool.clear
271
273
  end
272
274
 
@@ -49,6 +49,8 @@ module Concurrent
49
49
  # * `:discard`: Silently discard the task and return `nil` as the task result.
50
50
  # * `:caller_runs`: Execute the task on the calling thread.
51
51
  #
52
+ # {include:file:doc/thread_pools.md}
53
+ #
52
54
  # @note When running on the JVM (JRuby) this class will inherit from `JavaThreadPoolExecutor`.
53
55
  # On all other platforms it will inherit from `RubyThreadPoolExecutor`.
54
56
  #
@@ -22,10 +22,10 @@ module Concurrent
22
22
  # @option opts [object] :executor when provided will run all operations on
23
23
  # this executor rather than the global thread pool (overrides :operation)
24
24
  def initialize(opts = {})
25
- @queue = PriorityQueue.new(order: :min)
26
- @task_executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_task_pool
25
+ @queue = PriorityQueue.new(order: :min)
26
+ @task_executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_task_pool
27
27
  @timer_executor = SingleThreadExecutor.new
28
- @condition = Condition.new
28
+ @condition = Condition.new
29
29
  init_executor
30
30
  end
31
31
 
@@ -64,7 +64,7 @@ module Concurrent
64
64
  # For a timer, #kill is like an orderly shutdown, except we need to manually
65
65
  # (and destructively) clear the queue first
66
66
  def kill
67
- @queue.clear
67
+ mutex.synchronize { @queue.clear }
68
68
  shutdown
69
69
  end
70
70
 
@@ -124,14 +124,13 @@ module Concurrent
124
124
  # @!visibility private
125
125
  def process_tasks
126
126
  loop do
127
- break if @queue.empty?
128
-
129
- task = @queue.peek
127
+ task = mutex.synchronize { @queue.peek }
128
+ break unless task
130
129
  interval = task.time - Time.now.to_f
131
130
 
132
131
  if interval <= 0
133
132
  @task_executor.post(*task.args, &task.op)
134
- @queue.pop
133
+ mutex.synchronize { @queue.pop }
135
134
  else
136
135
  mutex.synchronize do
137
136
  @condition.wait(mutex, [interval, 60].min)
@@ -1,6 +1,7 @@
1
1
  require 'concurrent/executor/cached_thread_pool'
2
2
  require 'concurrent/executor/fixed_thread_pool'
3
3
  require 'concurrent/executor/immediate_executor'
4
+ require 'concurrent/executor/indirect_immediate_executor'
4
5
  require 'concurrent/executor/per_thread_executor'
5
6
  require 'concurrent/executor/safe_task_executor'
6
7
  require 'concurrent/executor/single_thread_executor'
@@ -6,35 +6,7 @@ require 'concurrent/executor/safe_task_executor'
6
6
 
7
7
  module Concurrent
8
8
 
9
- # A `Future` represents a promise to complete an action at some time in the future.
10
- # The action is atomic and permanent. The idea behind a future is to send an operation
11
- # for asynchronous completion, do other stuff, then return and retrieve the result
12
- # of the async operation at a later time.
13
- #
14
- # A `Future` has four possible states: *:unscheduled*, *:pending*, *:rejected*, or *:fulfilled*.
15
- # When a `Future` is created its state is set to *:unscheduled*. Once the `#execute` method is
16
- # called the state becomes *:pending* and will remain in that state until processing is
17
- # complete. A completed `Future` is either *:rejected*, indicating that an exception was
18
- # thrown during processing, or *:fulfilled*, indicating success. If a `Future` is *:fulfilled*
19
- # its `value` will be updated to reflect the result of the operation. If *:rejected* the
20
- # `reason` will be updated with a reference to the thrown exception. The predicate methods
21
- # `#unscheduled?`, `#pending?`, `#rejected?`, and `fulfilled?` can be called at any time to
22
- # obtain the state of the `Future`, as can the `#state` method, which returns a symbol.
23
- #
24
- # Retrieving the value of a `Future` is done through the `#value` (alias: `#deref`) method.
25
- # Obtaining the value of a `Future` is a potentially blocking operation. When a `Future` is
26
- # *:rejected* a call to `#value` will return `nil` immediately. When a `Future` is
27
- # *:fulfilled* a call to `#value` will immediately return the current value. When a
28
- # `Future` is *:pending* a call to `#value` will block until the `Future` is either
29
- # *:rejected* or *:fulfilled*. A *timeout* value can be passed to `#value` to limit how
30
- # long the call will block. If `nil` the call will block indefinitely. If `0` the call will
31
- # not block. Any other integer or float value will indicate the maximum number of seconds to block.
32
- #
33
- # The `Future` class also includes the behavior of the Ruby standard library `Observable` module,
34
- # but does so in a thread-safe way. On fulfillment or rejection all observers will be notified
35
- # according to the normal `Observable` behavior. The observer callback function will be called
36
- # with three parameters: the `Time` of fulfillment/rejection, the final `value`, and the final
37
- # `reason`. Observers added after fulfillment/rejection will still be notified as normal.
9
+ # {include:file:doc/future.md}
38
10
  #
39
11
  # @see http://ruby-doc.org/stdlib-2.1.1/libdoc/observer/rdoc/Observable.html Ruby Observable module
40
12
  # @see http://clojuredocs.org/clojure_core/clojure.core/future Clojure's future function
@@ -94,6 +66,12 @@ module Concurrent
94
66
  #
95
67
  # @yield the asynchronous operation to perform
96
68
  #
69
+ # @param [Hash] opts the options controlling how the future will be processed
70
+ # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
71
+ # operation pool (for long-running operations), when `false` will execute the future on the
72
+ # global task pool (for short-running tasks)
73
+ # @option opts [object] :executor when provided will run all operations on
74
+ # this executor rather than the global thread pool (overrides :operation)
97
75
  # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
98
76
  # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
99
77
  # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
@@ -6,6 +6,10 @@ require 'concurrent/observable'
6
6
 
7
7
  module Concurrent
8
8
 
9
+ # An `IVar` is like a future that you can assign. As a future is a value that is being computed that you can wait on, an `IVar` is a value that is waiting to be assigned, that you can wait on. `IVars` are single assignment and deterministic.
10
+ #
11
+ # Then, express futures as an asynchronous computation that assigns an `IVar`. The `IVar` becomes the primitive on which [futures](Future) and [dataflow](Dataflow) are built.
12
+ #
9
13
  # An `IVar` is a single-element container that is normally created empty, and
10
14
  # can only be set once. The I in `IVar` stands for immutable. Reading an `IVar`
11
15
  # normally blocks until it is set. It is safe to set and read an `IVar` from
@@ -15,6 +19,11 @@ module Concurrent
15
19
  # a `Future`. If you want to create a graph of parallel tasks all executed when
16
20
  # the values they depend on are ready you want `dataflow`. `IVar` is generally
17
21
  # a low-level primitive.
22
+ #
23
+ # **See Also:**
24
+ #
25
+ # * For the theory: Arvind, R. Nikhil, and K. Pingali. [I-Structures: Data structures for parallel computing](http://dl.acm.org/citation.cfm?id=69562). In Proceedings of Workshop on Graph Reduction, 1986.
26
+ # * For recent application: [DataDrivenFuture in Habanero Java from Rice](http://www.cs.rice.edu/~vs3/hjlib/doc/edu/rice/hj/api/HjDataDrivenFuture.html).
18
27
  #
19
28
  # @example Create, set and get an `IVar`
20
29
  # ivar = Concurrent::IVar.new
@@ -12,6 +12,9 @@ module Concurrent
12
12
  # @yieldreturn [String] a message
13
13
  def log(level, progname, message = nil, &block)
14
14
  (@logger || Concurrent.configuration.logger).call level, progname, message, &block
15
+ rescue => error
16
+ $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
17
+ "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
15
18
  end
16
19
  end
17
20
  end
@@ -4,16 +4,33 @@ require 'concurrent/atomic/event'
4
4
 
5
5
  module Concurrent
6
6
 
7
- # An `MVar` is a single-element container that blocks on `get` if it is empty,
8
- # and blocks on `put` if it is full. It is safe to use an `MVar` from
9
- # multiple threads. `MVar` can be seen as a single-element blocking queue, or
10
- # a rendezvous variable.
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
+ #
22
+ # `MVar` is a [Dereferenceable](Dereferenceable).
23
+ #
24
+ # `MVar` is related to M-structures in Id, `MVar` in Haskell and `SyncVar` in Scala.
11
25
  #
12
- # An `MVar` is typically used to transfer objects between threads, where the
13
- # sending thread will block if the previous message hasn't been taken yet by the
14
- # receiving thread. It can also be used to control access to some global shared
15
- # state, where threads `take` the value, perform some operation, and then
16
- # `put` it back.
26
+ # Note that unlike the original Haskell paper, our `#take` is blocking. This is how
27
+ # 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.
17
34
  class MVar
18
35
 
19
36
  include Dereferenceable
@@ -3,6 +3,39 @@ require 'concurrent/atomic/copy_on_write_observer_set'
3
3
 
4
4
  module Concurrent
5
5
 
6
+ # The [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) is one of the most useful design pattern.
7
+ #
8
+ # The workflow is very simple:
9
+ # - an `observer` can register itself to a `subject` via a callback
10
+ # - many `observers` can be registered to the same `subject`
11
+ # - the `subject` notifies all registered observers when its status changes
12
+ # - an `observer` can deregister itself when is no more interested to receive event notifications
13
+ #
14
+ # In a single threaded environment the whole pattern is very easy: the `subject` can use a simple data structure to manage all its subscribed `observer`s and every `observer` can react directly to every event without caring about synchronization.
15
+ #
16
+ # In a multi threaded environment things are more complex.
17
+ # The `subject` must synchronize the access to its data structure and to do so currently we're using two specialized ObserverSet: CopyOnWriteObserverSet and CopyOnNotifyObserverSet.
18
+ #
19
+ # When implementing and `observer` there's a very important rule to remember: **there are no guarantees about the thread that will execute the callback**
20
+ #
21
+ # Let's take this example
22
+ # ```
23
+ # class Observer
24
+ # def initialize
25
+ # @count = 0
26
+ # end
27
+ #
28
+ # def update
29
+ # @count += 1
30
+ # end
31
+ # end
32
+ #
33
+ # obs = Observer.new
34
+ # [obj1, obj2, obj3, obj4].each { |o| o.add_observer(obs) }
35
+ # # execute [obj1, obj2, obj3, obj4]
36
+ # ```
37
+ #
38
+ # `obs` is wrong because the variable `@count` can be accessed by different threads at the same time, so it should be synchronized (using either a Mutex or an AtomicFixum)
6
39
  module Observable
7
40
 
8
41
  # @return [Object] the added observer
@@ -5,8 +5,9 @@ require 'concurrent/options_parser'
5
5
 
6
6
  module Concurrent
7
7
 
8
- # TODO unify promise and future to single class, with dataflow
8
+ # {include:file:doc/promise.md}
9
9
  class Promise
10
+ # TODO unify promise and future to single class, with dataflow
10
11
  include Obligation
11
12
 
12
13
  # Initialize a new Promise with the provided options.
@@ -110,6 +111,63 @@ module Concurrent
110
111
  alias_method :catch, :rescue
111
112
  alias_method :on_error, :rescue
112
113
 
114
+ # Yield the successful result to the block that returns a promise. If that
115
+ # promise is also successful the result is the result of the yielded promise.
116
+ # If either part fails the whole also fails.
117
+ #
118
+ # @example
119
+ # Promise.execute { 1 }.flat_map { |v| Promise.execute { v + 2 } }.value! #=> 3
120
+ #
121
+ # @return [Promise]
122
+ def flat_map(&block)
123
+ child = Promise.new(
124
+ parent: self,
125
+ executor: ImmediateExecutor.new,
126
+ )
127
+
128
+ on_error { |e| child.on_reject(e) }
129
+ on_success do |result1|
130
+ begin
131
+ inner = block.call(result1)
132
+ inner.execute
133
+ inner.on_success { |result2| child.on_fulfill(result2) }
134
+ inner.on_error { |e| child.on_reject(e) }
135
+ rescue => e
136
+ child.on_reject(e)
137
+ end
138
+ end
139
+
140
+ child
141
+ end
142
+
143
+ # Builds a promise that produces the result of promises in an Array
144
+ # and fails if any of them fails.
145
+ #
146
+ # @param [Array<Promise>] promises
147
+ #
148
+ # @return [Promise<Array>]
149
+ def self.zip(*promises)
150
+ zero = fulfill([], executor: ImmediateExecutor.new)
151
+
152
+ promises.reduce(zero) do |p1, p2|
153
+ p1.flat_map do |results|
154
+ p2.then do |next_result|
155
+ results << next_result
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ # Builds a promise that produces the result of self and others in an Array
162
+ # and fails if any of them fails.
163
+ #
164
+ # @param [Array<Promise>] others
165
+ #
166
+ # @return [Promise<Array>]
167
+ def zip(*others)
168
+ self.class.zip(self, *others)
169
+ end
170
+
113
171
  protected
114
172
 
115
173
  def set_pending