concurrent-ruby 0.7.0-x86-linux → 0.7.1-x86-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/CHANGELOG.md +138 -0
- data/README.md +73 -105
- data/lib/1.9/concurrent_ruby_ext.so +0 -0
- data/lib/2.0/concurrent_ruby_ext.so +0 -0
- data/lib/concurrent/actor.rb +11 -12
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +1 -1
- data/lib/concurrent/actor/behaviour/linking.rb +4 -1
- data/lib/concurrent/actor/behaviour/pausing.rb +2 -2
- data/lib/concurrent/actor/behaviour/supervised.rb +2 -1
- data/lib/concurrent/actor/behaviour/termination.rb +1 -1
- data/lib/concurrent/actor/context.rb +2 -1
- data/lib/concurrent/actor/core.rb +7 -3
- data/lib/concurrent/actor/utils/balancer.rb +4 -2
- data/lib/concurrent/actor/utils/pool.rb +1 -1
- data/lib/concurrent/agent.rb +1 -22
- data/lib/concurrent/async.rb +1 -79
- data/lib/concurrent/atomic.rb +1 -1
- data/lib/concurrent/atomic/thread_local_var.rb +71 -24
- data/lib/concurrent/atomics.rb +0 -1
- data/lib/concurrent/configuration.rb +11 -5
- data/lib/concurrent/dataflow.rb +1 -30
- data/lib/concurrent/dereferenceable.rb +9 -2
- data/lib/concurrent/executor/indirect_immediate_executor.rb +46 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +2 -4
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +24 -22
- data/lib/concurrent/executor/thread_pool_executor.rb +2 -0
- data/lib/concurrent/executor/timer_set.rb +7 -8
- data/lib/concurrent/executors.rb +1 -0
- data/lib/concurrent/future.rb +7 -29
- data/lib/concurrent/ivar.rb +9 -0
- data/lib/concurrent/logging.rb +3 -0
- data/lib/concurrent/mvar.rb +26 -9
- data/lib/concurrent/observable.rb +33 -0
- data/lib/concurrent/promise.rb +59 -1
- data/lib/concurrent/scheduled_task.rb +1 -0
- data/lib/concurrent/timer_task.rb +18 -18
- data/lib/concurrent/tvar.rb +2 -0
- data/lib/concurrent/version.rb +1 -1
- data/lib/concurrent_ruby_ext.so +0 -0
- 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
|
-
#
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
81
|
-
@max_length
|
82
|
-
@idletime
|
83
|
-
@max_queue
|
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
|
93
|
+
@pool = []
|
94
|
+
@queue = Queue.new
|
95
95
|
@scheduled_task_count = 0
|
96
96
|
@completed_task_count = 0
|
97
|
-
@largest_length
|
97
|
+
@largest_length = 0
|
98
98
|
|
99
|
-
@gc_interval
|
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? && !
|
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
|
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
|
258
|
-
|
259
|
-
|
260
|
-
|
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
|
26
|
-
@task_executor
|
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
|
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
|
-
|
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)
|
data/lib/concurrent/executors.rb
CHANGED
@@ -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'
|
data/lib/concurrent/future.rb
CHANGED
@@ -6,35 +6,7 @@ require 'concurrent/executor/safe_task_executor'
|
|
6
6
|
|
7
7
|
module Concurrent
|
8
8
|
|
9
|
-
#
|
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
|
data/lib/concurrent/ivar.rb
CHANGED
@@ -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
|
data/lib/concurrent/logging.rb
CHANGED
@@ -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
|
data/lib/concurrent/mvar.rb
CHANGED
@@ -4,16 +4,33 @@ require 'concurrent/atomic/event'
|
|
4
4
|
|
5
5
|
module Concurrent
|
6
6
|
|
7
|
-
# An `MVar` is a single
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
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
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
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
|
data/lib/concurrent/promise.rb
CHANGED
@@ -5,8 +5,9 @@ require 'concurrent/options_parser'
|
|
5
5
|
|
6
6
|
module Concurrent
|
7
7
|
|
8
|
-
#
|
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
|