concurrent-ruby 0.4.1 → 0.5.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -33
- data/lib/concurrent.rb +11 -3
- data/lib/concurrent/actor.rb +29 -29
- data/lib/concurrent/agent.rb +98 -16
- data/lib/concurrent/atomic.rb +125 -0
- data/lib/concurrent/channel.rb +36 -1
- data/lib/concurrent/condition.rb +67 -0
- data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
- data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
- data/lib/concurrent/count_down_latch.rb +60 -0
- data/lib/concurrent/dataflow.rb +85 -0
- data/lib/concurrent/dereferenceable.rb +69 -31
- data/lib/concurrent/event.rb +27 -21
- data/lib/concurrent/future.rb +103 -43
- data/lib/concurrent/ivar.rb +78 -0
- data/lib/concurrent/mvar.rb +154 -0
- data/lib/concurrent/obligation.rb +94 -9
- data/lib/concurrent/postable.rb +11 -9
- data/lib/concurrent/promise.rb +101 -127
- data/lib/concurrent/safe_task_executor.rb +28 -0
- data/lib/concurrent/scheduled_task.rb +60 -54
- data/lib/concurrent/stoppable.rb +2 -2
- data/lib/concurrent/supervisor.rb +36 -29
- data/lib/concurrent/thread_local_var.rb +117 -0
- data/lib/concurrent/timer_task.rb +28 -30
- data/lib/concurrent/utilities.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/agent_spec.rb +121 -230
- data/spec/concurrent/atomic_spec.rb +201 -0
- data/spec/concurrent/condition_spec.rb +171 -0
- data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
- data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
- data/spec/concurrent/count_down_latch_spec.rb +125 -0
- data/spec/concurrent/dataflow_spec.rb +160 -0
- data/spec/concurrent/dereferenceable_shared.rb +145 -0
- data/spec/concurrent/event_spec.rb +44 -9
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
- data/spec/concurrent/future_spec.rb +184 -69
- data/spec/concurrent/ivar_spec.rb +192 -0
- data/spec/concurrent/mvar_spec.rb +380 -0
- data/spec/concurrent/obligation_spec.rb +193 -0
- data/spec/concurrent/observer_set_shared.rb +233 -0
- data/spec/concurrent/postable_shared.rb +3 -7
- data/spec/concurrent/promise_spec.rb +270 -192
- data/spec/concurrent/safe_task_executor_spec.rb +58 -0
- data/spec/concurrent/scheduled_task_spec.rb +142 -38
- data/spec/concurrent/thread_local_var_spec.rb +113 -0
- data/spec/concurrent/thread_pool_shared.rb +2 -3
- data/spec/concurrent/timer_task_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/functions.rb +4 -0
- data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
- metadata +50 -30
- data/lib/concurrent/contract.rb +0 -21
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
- data/md/actor.md +0 -404
- data/md/agent.md +0 -142
- data/md/channel.md +0 -40
- data/md/dereferenceable.md +0 -49
- data/md/future.md +0 -125
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -217
- data/md/scheduled_task.md +0 -156
- data/md/supervisor.md +0 -246
- data/md/thread_pool.md +0 -225
- data/md/timer_task.md +0 -191
- data/spec/concurrent/contract_spec.rb +0 -34
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -62,13 +62,12 @@ share_examples_for :thread_pool do
|
|
62
62
|
end
|
63
63
|
|
64
64
|
it 'allows threads to exit normally' do
|
65
|
-
before_thread_count = Thread.list.size
|
66
65
|
10.times{ subject << proc{ nil } }
|
66
|
+
subject.length.should > 0
|
67
67
|
sleep(0.1)
|
68
|
-
Thread.list.size.should > before_thread_count
|
69
68
|
subject.shutdown
|
70
69
|
sleep(1)
|
71
|
-
|
70
|
+
subject.length.should == 0
|
72
71
|
end
|
73
72
|
end
|
74
73
|
|
@@ -87,7 +87,37 @@ module Concurrent
|
|
87
87
|
end
|
88
88
|
|
89
89
|
context '#kill' do
|
90
|
-
|
90
|
+
|
91
|
+
it 'kills its threads while sleeping' do
|
92
|
+
Thread.should_receive(:kill).at_least(:once).times.with(any_args)
|
93
|
+
task = TimerTask.new(run_now: false){ nil }
|
94
|
+
task.run!
|
95
|
+
sleep(0.1)
|
96
|
+
task.kill
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'kills its threads once executing' do
|
100
|
+
Thread.should_receive(:kill).at_least(2).times.with(any_args)
|
101
|
+
task = TimerTask.new(run_now: true){ nil }
|
102
|
+
task.run!
|
103
|
+
sleep(0.1)
|
104
|
+
task.kill
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns true on success' do
|
108
|
+
task = TimerTask.new(run_now: false){ nil }
|
109
|
+
task.run!
|
110
|
+
sleep(0.1)
|
111
|
+
task.kill.should be_true
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns false on exception' do
|
115
|
+
Thread.stub(:kill).with(any_args).and_raise(StandardError)
|
116
|
+
task = TimerTask.new(run_now: false){ nil }
|
117
|
+
task.run!
|
118
|
+
sleep(0.1)
|
119
|
+
task.kill.should be_false
|
120
|
+
end
|
91
121
|
end
|
92
122
|
end
|
93
123
|
|
data/spec/spec_helper.rb
CHANGED
@@ -8,14 +8,13 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
|
8
8
|
|
9
9
|
SimpleCov.start do
|
10
10
|
project_name 'concurrent-ruby'
|
11
|
-
add_filter '/
|
11
|
+
add_filter '/coverage/'
|
12
|
+
add_filter '/doc/'
|
12
13
|
add_filter '/pkg/'
|
13
14
|
add_filter '/spec/'
|
14
15
|
add_filter '/tasks/'
|
15
16
|
end
|
16
17
|
|
17
|
-
require 'eventmachine'
|
18
|
-
|
19
18
|
require 'concurrent'
|
20
19
|
|
21
20
|
# import all the support files
|
data/spec/support/functions.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concurrent-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jerry D'Antonio
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more.
|
@@ -25,70 +25,79 @@ files:
|
|
25
25
|
- lib/concurrent.rb
|
26
26
|
- lib/concurrent/actor.rb
|
27
27
|
- lib/concurrent/agent.rb
|
28
|
+
- lib/concurrent/atomic.rb
|
28
29
|
- lib/concurrent/cached_thread_pool.rb
|
29
30
|
- lib/concurrent/cached_thread_pool/worker.rb
|
30
31
|
- lib/concurrent/channel.rb
|
31
|
-
- lib/concurrent/
|
32
|
+
- lib/concurrent/condition.rb
|
33
|
+
- lib/concurrent/copy_on_notify_observer_set.rb
|
34
|
+
- lib/concurrent/copy_on_write_observer_set.rb
|
35
|
+
- lib/concurrent/count_down_latch.rb
|
36
|
+
- lib/concurrent/dataflow.rb
|
32
37
|
- lib/concurrent/dereferenceable.rb
|
33
38
|
- lib/concurrent/event.rb
|
34
|
-
- lib/concurrent/event_machine_defer_proxy.rb
|
35
39
|
- lib/concurrent/fixed_thread_pool.rb
|
36
40
|
- lib/concurrent/fixed_thread_pool/worker.rb
|
37
41
|
- lib/concurrent/future.rb
|
38
42
|
- lib/concurrent/global_thread_pool.rb
|
39
43
|
- lib/concurrent/immediate_executor.rb
|
44
|
+
- lib/concurrent/ivar.rb
|
45
|
+
- lib/concurrent/mvar.rb
|
40
46
|
- lib/concurrent/obligation.rb
|
41
47
|
- lib/concurrent/postable.rb
|
42
48
|
- lib/concurrent/promise.rb
|
43
49
|
- lib/concurrent/runnable.rb
|
50
|
+
- lib/concurrent/safe_task_executor.rb
|
44
51
|
- lib/concurrent/scheduled_task.rb
|
45
52
|
- lib/concurrent/stoppable.rb
|
46
53
|
- lib/concurrent/supervisor.rb
|
54
|
+
- lib/concurrent/thread_local_var.rb
|
47
55
|
- lib/concurrent/timer_task.rb
|
48
56
|
- lib/concurrent/utilities.rb
|
49
57
|
- lib/concurrent/version.rb
|
50
58
|
- lib/concurrent_ruby.rb
|
51
|
-
- md/actor.md
|
52
|
-
- md/agent.md
|
53
|
-
- md/channel.md
|
54
|
-
- md/dereferenceable.md
|
55
|
-
- md/future.md
|
56
|
-
- md/obligation.md
|
57
|
-
- md/promise.md
|
58
|
-
- md/scheduled_task.md
|
59
|
-
- md/supervisor.md
|
60
|
-
- md/thread_pool.md
|
61
|
-
- md/timer_task.md
|
62
59
|
- spec/concurrent/actor_spec.rb
|
63
60
|
- spec/concurrent/agent_spec.rb
|
61
|
+
- spec/concurrent/atomic_spec.rb
|
64
62
|
- spec/concurrent/cached_thread_pool_spec.rb
|
65
63
|
- spec/concurrent/channel_spec.rb
|
66
|
-
- spec/concurrent/
|
67
|
-
- spec/concurrent/
|
64
|
+
- spec/concurrent/condition_spec.rb
|
65
|
+
- spec/concurrent/copy_on_notify_observer_set_spec.rb
|
66
|
+
- spec/concurrent/copy_on_write_observer_set_spec.rb
|
67
|
+
- spec/concurrent/count_down_latch_spec.rb
|
68
|
+
- spec/concurrent/dataflow_spec.rb
|
69
|
+
- spec/concurrent/dereferenceable_shared.rb
|
68
70
|
- spec/concurrent/event_spec.rb
|
69
71
|
- spec/concurrent/fixed_thread_pool_spec.rb
|
70
72
|
- spec/concurrent/future_spec.rb
|
71
73
|
- spec/concurrent/global_thread_pool_spec.rb
|
72
74
|
- spec/concurrent/immediate_executor_spec.rb
|
75
|
+
- spec/concurrent/ivar_spec.rb
|
76
|
+
- spec/concurrent/mvar_spec.rb
|
73
77
|
- spec/concurrent/obligation_shared.rb
|
78
|
+
- spec/concurrent/obligation_spec.rb
|
79
|
+
- spec/concurrent/observer_set_shared.rb
|
74
80
|
- spec/concurrent/postable_shared.rb
|
75
81
|
- spec/concurrent/promise_spec.rb
|
76
82
|
- spec/concurrent/runnable_shared.rb
|
77
83
|
- spec/concurrent/runnable_spec.rb
|
84
|
+
- spec/concurrent/safe_task_executor_spec.rb
|
78
85
|
- spec/concurrent/scheduled_task_spec.rb
|
79
86
|
- spec/concurrent/stoppable_shared.rb
|
80
87
|
- spec/concurrent/supervisor_spec.rb
|
88
|
+
- spec/concurrent/thread_local_var_spec.rb
|
81
89
|
- spec/concurrent/thread_pool_shared.rb
|
82
90
|
- spec/concurrent/timer_task_spec.rb
|
83
91
|
- spec/concurrent/uses_global_thread_pool_shared.rb
|
84
92
|
- spec/concurrent/utilities_spec.rb
|
85
93
|
- spec/spec_helper.rb
|
86
94
|
- spec/support/functions.rb
|
95
|
+
- spec/support/less_than_or_equal_to_matcher.rb
|
87
96
|
homepage: http://www.concurrent-ruby.com
|
88
97
|
licenses:
|
89
98
|
- MIT
|
90
99
|
metadata: {}
|
91
|
-
post_install_message:
|
100
|
+
post_install_message:
|
92
101
|
rdoc_options: []
|
93
102
|
require_paths:
|
94
103
|
- lib
|
@@ -99,40 +108,51 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
108
|
version: 1.9.2
|
100
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
110
|
requirements:
|
102
|
-
- - '
|
111
|
+
- - '>'
|
103
112
|
- !ruby/object:Gem::Version
|
104
|
-
version:
|
113
|
+
version: 1.3.1
|
105
114
|
requirements: []
|
106
|
-
rubyforge_project:
|
107
|
-
rubygems_version: 2.2.
|
108
|
-
signing_key:
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.2.2
|
117
|
+
signing_key:
|
109
118
|
specification_version: 4
|
110
|
-
summary: Modern concurrency tools including agents, futures, promises, thread pools,
|
111
|
-
actors, and more.
|
119
|
+
summary: Modern concurrency tools including agents, futures, promises, thread pools, actors, and more.
|
112
120
|
test_files:
|
121
|
+
- spec/spec_helper.rb
|
113
122
|
- spec/concurrent/actor_spec.rb
|
114
123
|
- spec/concurrent/agent_spec.rb
|
124
|
+
- spec/concurrent/atomic_spec.rb
|
115
125
|
- spec/concurrent/cached_thread_pool_spec.rb
|
116
126
|
- spec/concurrent/channel_spec.rb
|
117
|
-
- spec/concurrent/
|
118
|
-
- spec/concurrent/
|
127
|
+
- spec/concurrent/condition_spec.rb
|
128
|
+
- spec/concurrent/copy_on_notify_observer_set_spec.rb
|
129
|
+
- spec/concurrent/copy_on_write_observer_set_spec.rb
|
130
|
+
- spec/concurrent/count_down_latch_spec.rb
|
131
|
+
- spec/concurrent/dataflow_spec.rb
|
132
|
+
- spec/concurrent/dereferenceable_shared.rb
|
119
133
|
- spec/concurrent/event_spec.rb
|
120
134
|
- spec/concurrent/fixed_thread_pool_spec.rb
|
121
135
|
- spec/concurrent/future_spec.rb
|
122
136
|
- spec/concurrent/global_thread_pool_spec.rb
|
123
137
|
- spec/concurrent/immediate_executor_spec.rb
|
138
|
+
- spec/concurrent/ivar_spec.rb
|
139
|
+
- spec/concurrent/mvar_spec.rb
|
124
140
|
- spec/concurrent/obligation_shared.rb
|
141
|
+
- spec/concurrent/obligation_spec.rb
|
142
|
+
- spec/concurrent/observer_set_shared.rb
|
125
143
|
- spec/concurrent/postable_shared.rb
|
126
144
|
- spec/concurrent/promise_spec.rb
|
127
145
|
- spec/concurrent/runnable_shared.rb
|
128
146
|
- spec/concurrent/runnable_spec.rb
|
147
|
+
- spec/concurrent/safe_task_executor_spec.rb
|
129
148
|
- spec/concurrent/scheduled_task_spec.rb
|
130
149
|
- spec/concurrent/stoppable_shared.rb
|
131
150
|
- spec/concurrent/supervisor_spec.rb
|
151
|
+
- spec/concurrent/thread_local_var_spec.rb
|
132
152
|
- spec/concurrent/thread_pool_shared.rb
|
133
153
|
- spec/concurrent/timer_task_spec.rb
|
134
154
|
- spec/concurrent/uses_global_thread_pool_shared.rb
|
135
155
|
- spec/concurrent/utilities_spec.rb
|
136
|
-
- spec/spec_helper.rb
|
137
156
|
- spec/support/functions.rb
|
138
|
-
|
157
|
+
- spec/support/less_than_or_equal_to_matcher.rb
|
158
|
+
has_rdoc:
|
data/lib/concurrent/contract.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'concurrent/obligation'
|
2
|
-
|
3
|
-
module Concurrent
|
4
|
-
|
5
|
-
class Contract
|
6
|
-
include Obligation
|
7
|
-
|
8
|
-
def initialize(opts = {})
|
9
|
-
@state = :pending
|
10
|
-
init_mutex
|
11
|
-
set_deref_options(opts)
|
12
|
-
end
|
13
|
-
|
14
|
-
def complete(value, reason)
|
15
|
-
@value = value
|
16
|
-
@reason = reason
|
17
|
-
@state = ( reason ? :rejected : :fulfilled )
|
18
|
-
event.set
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'concurrent/global_thread_pool'
|
2
|
-
|
3
|
-
module Concurrent
|
4
|
-
|
5
|
-
class EventMachineDeferProxy
|
6
|
-
|
7
|
-
def post(*args, &block)
|
8
|
-
if args.empty?
|
9
|
-
EventMachine.defer(block)
|
10
|
-
else
|
11
|
-
new_block = proc{ block.call(*args) }
|
12
|
-
EventMachine.defer(new_block)
|
13
|
-
end
|
14
|
-
return true
|
15
|
-
end
|
16
|
-
|
17
|
-
def <<(block)
|
18
|
-
EventMachine.defer(block)
|
19
|
-
return self
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/md/actor.md
DELETED
@@ -1,404 +0,0 @@
|
|
1
|
-
# All the world's a stage
|
2
|
-
|
3
|
-
Actor-based concurrency is all the rage in some circles. Originally described in
|
4
|
-
1973, the actor model is a paradigm for creating asynchronous, concurrent objects
|
5
|
-
that is becoming increasingly popular. Much has changed since actors were first
|
6
|
-
written about four decades ago, which has led to a serious fragmentation within
|
7
|
-
the actor community. There is *no* universally accepted, strict definition of
|
8
|
-
"actor" and actor implementations differ widely between languages and libraries.
|
9
|
-
|
10
|
-
A good definition of "actor" is:
|
11
|
-
|
12
|
-
> An independent, concurrent, single-purpose, computational entity that
|
13
|
-
> communicates exclusively via message passing.
|
14
|
-
|
15
|
-
The `Concurrent::Actor` class in this library is based solely on the
|
16
|
-
[Actor](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor) task
|
17
|
-
defined in the Scala standard library. It does not implement all the features of
|
18
|
-
Scala's `Actor` but its behavior for what *has* been implemented is nearly identical.
|
19
|
-
The excluded features mostly deal with Scala's message semantics, strong typing,
|
20
|
-
and other characteristics of Scala that don't really apply to Ruby.
|
21
|
-
|
22
|
-
Unlike most of the abstractions in this library, `Actor` takes an *object-oriented*
|
23
|
-
approach to asynchronous concurrency, rather than a *functional programming*
|
24
|
-
approach.
|
25
|
-
|
26
|
-
## Definition
|
27
|
-
|
28
|
-
Actors are defined by subclassing the `Concurrent::Actor` class and overriding the
|
29
|
-
`#act` method. The `#act` method can have any signature/arity but
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
def act(*args)
|
33
|
-
```
|
34
|
-
|
35
|
-
is the most flexible and least error-prone signature. The `#act` method is called in
|
36
|
-
response to a message being post to the `Actor` instance (see *Behavior* below).
|
37
|
-
|
38
|
-
## Behavior
|
39
|
-
|
40
|
-
The `Concurrent::Actor` class includes the `Concurrent::Runnable` module. This provides
|
41
|
-
an `Actor` instance with the necessary methods for running and graceful stopping.
|
42
|
-
This also means that an `Actor` can be managed by a `Concurrent::Supervisor` for
|
43
|
-
fault tolerance.
|
44
|
-
|
45
|
-
### Message Passing
|
46
|
-
|
47
|
-
Messages from any thread can be sent (aka "post") to an `Actor` using several methods.
|
48
|
-
When a message is post all arguments are gathered together and queued for processing.
|
49
|
-
Messages are processed in the order they are received, one at a time, on a dedicated
|
50
|
-
thread. When a message is processed the subclass `#act` method is called and the return
|
51
|
-
value (or raised exception) is handled by the superclass based on the rules of the method
|
52
|
-
used to post the message.
|
53
|
-
|
54
|
-
All message posting methods are compatible with observation (see below).
|
55
|
-
|
56
|
-
Message processing within the `#act` method is not limited in any way, but care should
|
57
|
-
be taken to behave in a thread-safe, concurrency-friendly manner. A common practice is for
|
58
|
-
one `Actor` to send messages to another `Actor` though this is hardly the only approach.
|
59
|
-
|
60
|
-
Messages post to an `Actor` that is not running will be rejected.
|
61
|
-
|
62
|
-
#### Fire and Forget
|
63
|
-
|
64
|
-
The primary method of posting a message to an `Actor` is the simple `#post` method.
|
65
|
-
When this method is called the message is queued for processing. The method returns
|
66
|
-
false if it cannot be queued (the `Actor` is not running) otherwise it returns the
|
67
|
-
size of the queue (after queueing the new message). The caller thread has no way
|
68
|
-
to know the result of the message processing. When the `#post` method is used the
|
69
|
-
only way to act upon the result of the message processing is via observation
|
70
|
-
(see below).
|
71
|
-
|
72
|
-
#### Post with an Obligation
|
73
|
-
|
74
|
-
A common theme in modern asynchronous concurrency is for operations to return a
|
75
|
-
"future" (or "promise"). In this context a "future" is not an instance of the
|
76
|
-
`Concurrent::Future` class, but it is an object with similar behavior. Within
|
77
|
-
this library "future" behavior is genericized by the `Concurrent::Obligation`
|
78
|
-
mixin module (shared by `Future`, `Promise`, and others).
|
79
|
-
|
80
|
-
To post a message that returns a `Obligation` use the `#post?` method. If the message
|
81
|
-
cannot be queued the method will return `nil`. Otherwise an object implementing
|
82
|
-
`Obligation` will returned. The `Obligation` has the exteced states (`:pending`,
|
83
|
-
`:fulfilled`, and `:rejected`), the expected state-named predicate methods,
|
84
|
-
plus `#value` and `#reason`. These methods all behave identically to `Concurrent::Future`.
|
85
|
-
|
86
|
-
#### Post with Timeout
|
87
|
-
|
88
|
-
Threads posting messages to an `Actor` should generally not block. Blocking to wait
|
89
|
-
for an `Actor` to process a specific message defeats the purpose of asynchronous
|
90
|
-
concurrency. The `#post!` method is provided when the caller absolutely must block.
|
91
|
-
The first argument to `#post!` is a number of seconds to block while waiting for the
|
92
|
-
operation to complete. All subsequent arguments constitute the message and are
|
93
|
-
queued for delivery to the `#act` method. If the queued operation completes within
|
94
|
-
the timeout period the `#post!` method returns the result of the operation.
|
95
|
-
|
96
|
-
Unlike most methods in this library, the `#post!` method does not suppress exceptions.
|
97
|
-
Because the `#post!` method return value represents the result of message processing
|
98
|
-
the return value cannot effectively communicate failure. Instead, exceptions are used.
|
99
|
-
Calls to the `#post!` method should generally be wrapped in `rescue` guards. The
|
100
|
-
following exceptions may be raised by the `#post!` method:
|
101
|
-
|
102
|
-
* `Concurrent::Runnable::LifecycleError` will be raised if the message cannot be
|
103
|
-
queued, such as when the `Actor` is not running.
|
104
|
-
* `Concurrent::TimeoutError` will be raised if the message is not processed within
|
105
|
-
the designated timeout period
|
106
|
-
* Any exception raised during message processing will be re-raised after all
|
107
|
-
post-processing operations (such as observer callbacks) have completed
|
108
|
-
|
109
|
-
When the `#post!` method results in a timeout the `Actor` will attempt to cancel
|
110
|
-
message processing, but cancellation is not guaranteed. If message processing has
|
111
|
-
not begun the cancellation will normally occur. If message processing is in-progress
|
112
|
-
when `#post!` reaches timeout then processing will be allowed to complete. Code that
|
113
|
-
uses the `#post!` method must therefore not assume that a timeout means that message
|
114
|
-
processing did not occur.
|
115
|
-
|
116
|
-
#### Implicit Forward/Reply
|
117
|
-
|
118
|
-
A common idiom is for an `Actor` to send messages to another `Actor`. This creates
|
119
|
-
a "data flow" style of design not dissimilar to Unix-style pipe commands. Less common,
|
120
|
-
but still frequent, is for an `Actor` to send the result of message processing back
|
121
|
-
to the `Actor` that sent the message. In Scala this is easy to do. The underlying
|
122
|
-
message passing system implicitly communicates to the receiver the address of the
|
123
|
-
sender. Therefore, Scala actors can easily reply to the sender. Ruby has no similar
|
124
|
-
message passing subsystem to implicit knowledge of the sender is not possible. This
|
125
|
-
`Actor` implementation provides a `#forward` method that encapsulates both
|
126
|
-
aforementioned idioms. The first argument to the `#forward` method is a reference
|
127
|
-
to another `Actor` to which the receiving `Actor` should forward the result of
|
128
|
-
the processed messages. All subsequent arguments constitute the message and are
|
129
|
-
queued for delivery to the `#act` method.
|
130
|
-
|
131
|
-
Upon successful message processing the `Actor` superclass will automatically
|
132
|
-
forward the result to the receiver provided when `#forward` was called. If an
|
133
|
-
exception is raised no forwarding occurs.
|
134
|
-
|
135
|
-
### Error Handling
|
136
|
-
|
137
|
-
Because `Actor` mixes in the `Concurrent::Runnable` module subclasses have access to
|
138
|
-
the `#on_error` method and can override it to implement custom error handling. The
|
139
|
-
`Actor` base class does not use `#on_error` so as to avoid conflit with subclasses
|
140
|
-
which override it. Generally speaking, `#on_error` should not be used. The `Actor`
|
141
|
-
base class provides concictent, reliable, and robust error handling already, and
|
142
|
-
error handling specifics are tied to the message posting method. Incorrect behavior
|
143
|
-
in an `#on_error` override can lead to inconsistent `Actor` behavior that may lead
|
144
|
-
to confusion and difficult debugging.
|
145
|
-
|
146
|
-
### Observation
|
147
|
-
|
148
|
-
The `Actor` superclass mixes in the Ruby standard library
|
149
|
-
[Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html)
|
150
|
-
module to provide consistent callbacks upon message processing completion. The normal
|
151
|
-
`Observable` methods, including `#add_observer` behave normally. Once an observer
|
152
|
-
is added to an `Actor` it will be notified of all messages processed *after*
|
153
|
-
addition. Notification will *not* occur for any messages that have already been
|
154
|
-
processed.
|
155
|
-
|
156
|
-
Observers will be notified regardless of whether the message processing is successful
|
157
|
-
or not. The `#update` method of the observer will receive four arguments. The
|
158
|
-
appropriate method signature is:
|
159
|
-
|
160
|
-
```ruby
|
161
|
-
def update(time, message, result, reason)
|
162
|
-
```
|
163
|
-
|
164
|
-
These four arguments represent:
|
165
|
-
|
166
|
-
* The time that message processing was completed
|
167
|
-
* An array containing all elements of the original message, in order
|
168
|
-
* The result of the call to `#act` (will be `nil` if an exception was raised)
|
169
|
-
* Any exception raised by `#act` (or `nil` if message processing was successful)
|
170
|
-
|
171
|
-
### Actor Pools
|
172
|
-
|
173
|
-
Every `Actor` instance operates on its own thread. When one thread isn't enough capacity
|
174
|
-
to manage all the messages being sent to an `Actor` a *pool* can be used instead. A pool
|
175
|
-
is a collection of `Actor` instances, all of the same type, that shate a message queue.
|
176
|
-
Messages from other threads are all sent to a single queue against which all `Actor`s
|
177
|
-
load balance.
|
178
|
-
|
179
|
-
## Additional Reading
|
180
|
-
|
181
|
-
* [API documentation](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor)
|
182
|
-
for the original (now deprecated) Scala Actor
|
183
|
-
* [Scala Actors: A Short Tutorial](http://www.scala-lang.org/old/node/242)
|
184
|
-
* [Scala Actors 101](http://java.dzone.com/articles/scala-threadless-concurrent)
|
185
|
-
|
186
|
-
## Examples
|
187
|
-
|
188
|
-
Two `Actor`s playing a back and forth game of Ping Pong, adapted from the Scala example
|
189
|
-
[here](http://www.scala-lang.org/old/node/242):
|
190
|
-
|
191
|
-
```ruby
|
192
|
-
class Ping < Concurrent::Actor
|
193
|
-
|
194
|
-
def initialize(count, pong)
|
195
|
-
super()
|
196
|
-
@pong = pong
|
197
|
-
@remaining = count
|
198
|
-
end
|
199
|
-
|
200
|
-
def act(msg)
|
201
|
-
|
202
|
-
if msg == :pong
|
203
|
-
print "Ping: pong\n" if @remaining % 1000 == 0
|
204
|
-
@pong.post(:ping)
|
205
|
-
|
206
|
-
if @remaining > 0
|
207
|
-
@pong << :ping
|
208
|
-
@remaining -= 1
|
209
|
-
else
|
210
|
-
print "Ping :stop\n"
|
211
|
-
@pong << :stop
|
212
|
-
self.stop
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
class Pong < Concurrent::Actor
|
219
|
-
|
220
|
-
attr_writer :ping
|
221
|
-
|
222
|
-
def initialize
|
223
|
-
super()
|
224
|
-
@count = 0
|
225
|
-
end
|
226
|
-
|
227
|
-
def act(msg)
|
228
|
-
|
229
|
-
if msg == :ping
|
230
|
-
print "Pong: ping\n" if @count % 1000 == 0
|
231
|
-
@ping << :pong
|
232
|
-
@count += 1
|
233
|
-
|
234
|
-
elsif msg == :stop
|
235
|
-
print "Pong :stop\n"
|
236
|
-
self.stop
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
pong = Pong.new
|
242
|
-
ping = Ping.new(10000, pong)
|
243
|
-
pong.ping = ping
|
244
|
-
|
245
|
-
t1 = ping.run!
|
246
|
-
t2 = pong.run!
|
247
|
-
sleep(0.1)
|
248
|
-
|
249
|
-
ping << :pong
|
250
|
-
```
|
251
|
-
|
252
|
-
A pool of `Actor`s and a `Supervisor`
|
253
|
-
|
254
|
-
```ruby
|
255
|
-
QUERIES = %w[YAHOO Microsoft google]
|
256
|
-
|
257
|
-
class FinanceActor < Concurrent::Actor
|
258
|
-
def act(query)
|
259
|
-
finance = Finance.new(query)
|
260
|
-
print "[#{Time.now}] RECEIVED '#{query}' to #{self} returned #{finance.update.suggested_symbols}\n\n"
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
financial, pool = FinanceActor.pool(5)
|
265
|
-
|
266
|
-
overlord = Concurrent::Supervisor.new
|
267
|
-
pool.each{|actor| overlord.add_worker(actor)}
|
268
|
-
|
269
|
-
overlord.run!
|
270
|
-
|
271
|
-
financial.post('YAHOO')
|
272
|
-
|
273
|
-
#>> [2013-10-18 09:35:28 -0400] SENT 'YAHOO' from main to worker pool
|
274
|
-
#>> [2013-10-18 09:35:28 -0400] RECEIVED 'YAHOO' to #<FinanceActor:0x0000010331af70>...
|
275
|
-
```
|
276
|
-
|
277
|
-
The `#post` method simply sends a message to an actor and returns. It's a
|
278
|
-
fire-and-forget interaction.
|
279
|
-
|
280
|
-
```ruby
|
281
|
-
class EchoActor < Concurrent::Actor
|
282
|
-
def act(*message)
|
283
|
-
p message
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
echo = EchoActor.new
|
288
|
-
echo.run!
|
289
|
-
|
290
|
-
echo.post("Don't panic") #=> true
|
291
|
-
#=> ["Don't panic"]
|
292
|
-
|
293
|
-
echo.post(1, 2, 3, 4, 5) #=> true
|
294
|
-
#=> [1, 2, 3, 4, 5]
|
295
|
-
|
296
|
-
echo << "There's a frood who really knows where his towel is." #=> #<EchoActor:0x007fc8012b8448...
|
297
|
-
#=> ["There's a frood who really knows where his towel is."]
|
298
|
-
```
|
299
|
-
|
300
|
-
The `#post?` method returns an `Obligation` (same API as `Future`) which can be queried
|
301
|
-
for value/reason on fulfillment/rejection.
|
302
|
-
|
303
|
-
```ruby
|
304
|
-
class EverythingActor < Concurrent::Actor
|
305
|
-
def act(message)
|
306
|
-
sleep(5)
|
307
|
-
return 42
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
life = EverythingActor.new
|
312
|
-
life.run!
|
313
|
-
sleep(0.1)
|
314
|
-
|
315
|
-
universe = life.post?('What do you get when you multiply six by nine?')
|
316
|
-
universe.pending? #=> true
|
317
|
-
|
318
|
-
# wait for it...
|
319
|
-
|
320
|
-
universe.fulfilled? #=> true
|
321
|
-
universe.value #=> 42
|
322
|
-
```
|
323
|
-
|
324
|
-
The `#post!` method is a blocking call. It takes a number of seconds to wait as the
|
325
|
-
first parameter and any number of additional parameters as the message. If the message
|
326
|
-
is processed within the given number of seconds the call returns the result of the
|
327
|
-
operation. If message processing raises an exception the exception is raised again
|
328
|
-
by the `#post!` method. If the call to `#post!` times out a `Concurrent::Timeout`
|
329
|
-
exception is raised.
|
330
|
-
|
331
|
-
```ruby
|
332
|
-
life = EverythingActor.new
|
333
|
-
life.run!
|
334
|
-
sleep(0.1)
|
335
|
-
|
336
|
-
life.post!(1, 'Mostly harmless.')
|
337
|
-
|
338
|
-
# wait for it...
|
339
|
-
#=> Concurrent::TimeoutError: Concurrent::TimeoutError
|
340
|
-
```
|
341
|
-
|
342
|
-
And, of course, the `Actor` class mixes in Ruby's `Observable`.
|
343
|
-
|
344
|
-
```ruby
|
345
|
-
class ActorObserver
|
346
|
-
def update(time, message, result, ex)
|
347
|
-
if result
|
348
|
-
print "(#{time}) Message #{message} returned #{result}\n"
|
349
|
-
elsif ex.is_a?(Concurrent::TimeoutError)
|
350
|
-
print "(#{time}) Message #{message} timed out\n"
|
351
|
-
else
|
352
|
-
print "(#{time}) Message #{message} failed with error #{ex}\n"
|
353
|
-
end
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
class SimpleActor < Concurrent::Actor
|
358
|
-
def act(*message)
|
359
|
-
message
|
360
|
-
end
|
361
|
-
end
|
362
|
-
|
363
|
-
actor = SimpleActor.new
|
364
|
-
actor.add_observer(ActorObserver.new)
|
365
|
-
actor.run!
|
366
|
-
|
367
|
-
actor.post(1)
|
368
|
-
#=> (2013-11-07 18:35:33 -0500) Message [1] returned [1]
|
369
|
-
|
370
|
-
actor.post(1,2,3)
|
371
|
-
#=> (2013-11-07 18:35:54 -0500) Message [1, 2, 3] returned [1, 2, 3]
|
372
|
-
|
373
|
-
actor.post('The Nightman Cometh')
|
374
|
-
#=> (2013-11-07 18:36:11 -0500) Message ["The Nightman Cometh"] returned ["The Nightman Cometh"]
|
375
|
-
```
|
376
|
-
|
377
|
-
## Copyright
|
378
|
-
|
379
|
-
*Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
380
|
-
It is free software and may be redistributed under the terms specified in the LICENSE file.
|
381
|
-
|
382
|
-
## License
|
383
|
-
|
384
|
-
Released under the MIT license.
|
385
|
-
|
386
|
-
http://www.opensource.org/licenses/mit-license.php
|
387
|
-
|
388
|
-
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
389
|
-
> of this software and associated documentation files (the "Software"), to deal
|
390
|
-
> in the Software without restriction, including without limitation the rights
|
391
|
-
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
392
|
-
> copies of the Software, and to permit persons to whom the Software is
|
393
|
-
> furnished to do so, subject to the following conditions:
|
394
|
-
>
|
395
|
-
> The above copyright notice and this permission notice shall be included in
|
396
|
-
> all copies or substantial portions of the Software.
|
397
|
-
>
|
398
|
-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
399
|
-
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
400
|
-
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
401
|
-
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
402
|
-
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
403
|
-
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
404
|
-
> THE SOFTWARE.
|