concurrent-ruby 0.3.2 → 0.4.0
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 +4 -4
- data/README.md +76 -225
- data/lib/concurrent.rb +18 -2
- data/lib/concurrent/actor.rb +180 -72
- data/lib/concurrent/channel.rb +28 -0
- data/lib/concurrent/dereferenceable.rb +21 -1
- data/lib/concurrent/event.rb +33 -1
- data/lib/concurrent/postable.rb +98 -0
- data/lib/concurrent/stoppable.rb +20 -0
- data/lib/concurrent/timer_task.rb +213 -8
- data/lib/concurrent/utilities.rb +12 -1
- data/lib/concurrent/version.rb +1 -1
- data/md/actor.md +2 -2
- data/md/agent.md +1 -1
- data/md/channel.md +40 -0
- data/md/dereferenceable.md +7 -9
- data/md/future.md +2 -2
- data/md/promise.md +1 -1
- data/md/scheduled_task.md +1 -1
- data/md/timer_task.md +1 -1
- data/spec/concurrent/actor_spec.rb +48 -255
- data/spec/concurrent/channel_spec.rb +86 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -18
- data/spec/concurrent/event_spec.rb +38 -3
- data/spec/concurrent/global_thread_pool_spec.rb +0 -19
- data/spec/concurrent/postable_shared.rb +222 -0
- data/spec/concurrent/stoppable_shared.rb +37 -0
- data/spec/concurrent/timer_task_spec.rb +37 -1
- metadata +22 -15
- data/lib/concurrent/goroutine.rb +0 -25
- data/md/goroutine.md +0 -54
- data/spec/concurrent/goroutine_spec.rb +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 755d47349aa9471448745d4d102eef30c9ff39f9
|
4
|
+
data.tar.gz: 3ed64a6aa698387b8043cceb72bb64c3b45d4f2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba402eee86f4836735c7f659ac45c0c8df22863d718435179348a4fa5707a1671629d113c615ea4f87a0ac60019999556c1ae84b468c8abca3907e7e7efb6ea1
|
7
|
+
data.tar.gz: a689c273e9173afffa0945b4621b88bd9276cf478c4aaf9692bddb78ed610521c9a22fb449ca229feb869654018f37df670f19eb172e027ba4a631f5482692ec
|
data/README.md
CHANGED
@@ -1,86 +1,42 @@
|
|
1
1
|
# Concurrent Ruby [](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [](https://coveralls.io/r/jdantonio/concurrent-ruby) [](https://gemnasium.com/jdantonio/concurrent-ruby)
|
2
2
|
|
3
|
-
Modern concurrency tools
|
4
|
-
Inspired by Erlang, Clojure, Scala, Go, Java, JavaScript, and classic concurrency patterns.
|
5
|
-
|
6
|
-
## Introduction
|
7
|
-
|
8
|
-
The old-school "lock and synchronize" approach to concurrency is dead. The
|
9
|
-
future of concurrency is asynchronous. Send out a bunch of independent
|
10
|
-
[actors](http://en.wikipedia.org/wiki/Actor_model) to do your bidding and
|
11
|
-
process the results when you are ready. Many modern programming languages (like
|
3
|
+
Modern concurrency tools for Ruby. Inspired by
|
12
4
|
[Erlang](http://www.erlang.org/doc/reference_manual/processes.html),
|
13
5
|
[Clojure](http://clojure.org/concurrent_programming),
|
14
6
|
[Scala](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor),
|
15
7
|
[Haskell](http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism#Concurrent_Haskell),
|
16
8
|
[F#](http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx),
|
17
9
|
[C#](http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx),
|
18
|
-
[Java](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html)
|
19
|
-
|
20
|
-
runtime environment, or the language iteself. This library implements a few of
|
21
|
-
the most interesting and useful of those variations.
|
22
|
-
|
23
|
-
Remember, *there is no silver bullet in concurrent programming.* Concurrency is hard.
|
24
|
-
These tools will help ease the burden, but at the end of the day it is essential that you
|
25
|
-
*know what you are doing.*
|
26
|
-
|
27
|
-
* Decouple business logic from concurrency logic
|
28
|
-
* Test business logic separate from concurrency logic
|
29
|
-
* Keep the intersection of business logic and concurrency and small as possible
|
30
|
-
* Don't share mutable data unless absolutely necessary
|
31
|
-
* Protect shared data as much as possible (prefer [immutability](https://github.com/harukizaemon/hamster))
|
32
|
-
* Don't mix Ruby's [concurrency](http://ruby-doc.org/core-2.0.0/Thread.html)
|
33
|
-
[primitives](http://www.ruby-doc.org/core-2.0.0/Mutex.html) with asynchronous concurrency libraries
|
34
|
-
|
35
|
-
The project is hosted on the following sites:
|
10
|
+
[Java](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html),
|
11
|
+
and classic concurrency patterns.
|
36
12
|
|
37
|
-
|
38
|
-
* [Source code on GitHub](https://github.com/jdantonio/concurrent-ruby)
|
39
|
-
* [YARD documentation on RubyDoc.info](http://rubydoc.info/github/jdantonio/concurrent-ruby/frames)
|
40
|
-
* [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/concurrent-ruby)
|
41
|
-
* [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/concurrent-ruby)
|
42
|
-
* [Follow me on Twitter](https://twitter.com/jerrydantonio)
|
43
|
-
|
44
|
-
### Conference Presentations
|
45
|
-
|
46
|
-
I've given several conference presentations on concurrent programming with this gem.
|
47
|
-
Check them out:
|
48
|
-
|
49
|
-
* ["Advanced Concurrent Programming in Ruby"](http://rubyconf.org/program#jerry-dantonio)
|
50
|
-
at [RubyConf 2013](http://rubyconf.org/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation) version of the presentation
|
51
|
-
* ["Advanced Multithreading in Ruby"](http://cascadiaruby.com/#advanced-multithreading-in-ruby)
|
52
|
-
at [Cascadia Ruby 2013](http://cascadiaruby.com/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation/tree/cascadia-ruby-2013) version of the presentation
|
53
|
-
* I'll be giving ["Advanced Concurrent Programming in Ruby"](http://codemash.org/sessions)
|
54
|
-
at [CodeMash 2014](http://codemash.org/)
|
55
|
-
|
56
|
-
## Goals
|
13
|
+
The design goals of this gem are:
|
57
14
|
|
58
15
|
* Stay true to the spirit of the languages providing inspiration
|
59
16
|
* But implement in a way that makes sense for Ruby
|
60
17
|
* Keep the semantics as idiomatic Ruby as possible
|
61
18
|
* Support features that make sense in Ruby
|
62
19
|
* Exclude features that don't make sense in Ruby
|
63
|
-
*
|
64
|
-
* Be as fast as reasonably possible
|
20
|
+
* Be small, lean, and loosely coupled
|
65
21
|
|
66
|
-
## Features
|
22
|
+
## Features & Documentation
|
67
23
|
|
68
|
-
|
69
|
-
|
70
|
-
*
|
71
|
-
*
|
72
|
-
*
|
73
|
-
* Go inspired [Goroutine](https://github.com/jdantonio/concurrent-ruby/blob/master/md/goroutine.md)
|
74
|
-
* JavaScript inspired [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
|
75
|
-
* Java inspired [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
|
24
|
+
* Clojure-inspired [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
|
25
|
+
* Clojure-inspired [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
|
26
|
+
* Scala-inspired [Actor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/actor.md)
|
27
|
+
* JavaScript-inspired [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
|
28
|
+
* Java-inspired [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
|
76
29
|
* Old school [events](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx) from back in my Visual C++ days
|
77
|
-
* Repeated task execution with Java
|
78
|
-
* Scheduled task execution with Java
|
79
|
-
* Erlang
|
30
|
+
* Repeated task execution with Java-inspired [TimerTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/timer_task.md) service
|
31
|
+
* Scheduled task execution with Java-inspired [ScheduledTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/scheduled_task.md) service
|
32
|
+
* Erlang-inspired [Supervisor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md) for managing long-running threads
|
33
|
+
* Actor variant [Channel](https://github.com/jdantonio/concurrent-ruby/blob/master/md/channel.md)
|
34
|
+
loosely based on the [MailboxProcessor](http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx)
|
35
|
+
agent in [F#](http://msdn.microsoft.com/en-us/library/ee370357.aspx)
|
80
36
|
|
81
|
-
###
|
37
|
+
### Semantic Versioning
|
82
38
|
|
83
|
-
|
39
|
+
*Beginning with v0.4.0 this gem will strictly adhere to the rules of semantic versioning.*
|
84
40
|
|
85
41
|
### Supported Ruby versions
|
86
42
|
|
@@ -89,188 +45,83 @@ It should be fully compatible with any Ruby interpreter that is 1.9.x compliant.
|
|
89
45
|
about Rubinius or the others to fully support them. I can promise good karma and attribution on this page
|
90
46
|
to anyone wishing to take responsibility for verifying compaitibility with any Ruby other than MRI.
|
91
47
|
|
92
|
-
###
|
93
|
-
|
94
|
-
```shell
|
95
|
-
gem install concurrent-ruby
|
96
|
-
```
|
97
|
-
|
98
|
-
or add the following line to Gemfile:
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
gem 'concurrent-ruby'
|
102
|
-
```
|
103
|
-
|
104
|
-
and run `bundle install` from your shell.
|
105
|
-
|
106
|
-
Once you've installed the gem you must `require` it in your project:
|
107
|
-
|
108
|
-
```ruby
|
109
|
-
require 'concurrent'
|
110
|
-
```
|
111
|
-
|
112
|
-
### Examples
|
113
|
-
|
114
|
-
For complete examples, see the specific documentation for each abstraction.
|
115
|
-
The examples below are just basic usage.
|
116
|
-
|
117
|
-
#### Goroutine (Go)
|
118
|
-
|
119
|
-
Full documentation: [Goroutine](https://github.com/jdantonio/concurrent-ruby/blob/master/md/goroutine.md)
|
120
|
-
|
121
|
-
```ruby
|
122
|
-
require 'concurrent'
|
123
|
-
|
124
|
-
go('foo'){|echo| sleep(0.1); print "#{echo}\n"; sleep(0.1); print "Boom!\n" }
|
125
|
-
sleep(0.5)
|
126
|
-
|
127
|
-
#=> foo
|
128
|
-
#=> Boom!
|
129
|
-
```
|
130
|
-
|
131
|
-
#### Agent (Clojure)
|
132
|
-
|
133
|
-
Full documentation: [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
|
134
|
-
|
135
|
-
```ruby
|
136
|
-
require 'concurrent'
|
137
|
-
|
138
|
-
score = Concurrent::Agent.new(10)
|
139
|
-
score.value #=> 10
|
140
|
-
|
141
|
-
score << proc{|current| current + 100 }
|
142
|
-
sleep(0.1)
|
143
|
-
score.value #=> 110
|
144
|
-
```
|
145
|
-
|
146
|
-
#### Future (Clojure)
|
48
|
+
### Example
|
147
49
|
|
148
|
-
|
50
|
+
Many more code examples can be found in the documentation for each class (linked above).
|
51
|
+
This one simple example shows some of the power of this gem.
|
149
52
|
|
150
53
|
```ruby
|
151
54
|
require 'concurrent'
|
55
|
+
require 'faker'
|
152
56
|
|
153
|
-
|
154
|
-
|
155
|
-
#
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
#### Promise (JavaScript)
|
160
|
-
|
161
|
-
Full documentation: [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
|
162
|
-
|
163
|
-
```ruby
|
164
|
-
require 'concurrent'
|
165
|
-
|
166
|
-
p = Concurrent::Promise.new("Jerry", "D'Antonio"){|a, b| "#{a} #{b}" }.
|
167
|
-
then{|result| "Hello #{result}." }.
|
168
|
-
rescue(StandardError){|ex| puts "Boom!" }.
|
169
|
-
then{|result| "#{result} Would you like to play a game?"}
|
170
|
-
sleep(1)
|
171
|
-
p.value #=> "Hello Jerry D'Antonio. Would you like to play a game?"
|
172
|
-
```
|
173
|
-
|
174
|
-
#### Thread Pools (Java)
|
175
|
-
|
176
|
-
Full documentation: [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
|
177
|
-
|
178
|
-
```ruby
|
179
|
-
require 'concurrent'
|
180
|
-
|
181
|
-
pool = Concurrent::FixedThreadPool.new(2)
|
182
|
-
pool.size #=> 2
|
183
|
-
|
184
|
-
pool.post{ sleep(0.5); print "Boom!\n" }
|
185
|
-
pool.size #=> 2
|
186
|
-
|
187
|
-
sleep(1)
|
188
|
-
#=> Boom!
|
189
|
-
|
190
|
-
pool = Concurrent::CachedThreadPool.new
|
191
|
-
pool.size #=> 0
|
192
|
-
|
193
|
-
pool << proc{ sleep(0.5); print "Boom!\n" }
|
194
|
-
pool.size #=> 1
|
195
|
-
|
196
|
-
sleep(1)
|
197
|
-
#=> Boom!
|
198
|
-
```
|
199
|
-
|
200
|
-
#### TimerTask (Java)
|
201
|
-
|
202
|
-
Full documentation: [TimerTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/timer_task.md)
|
203
|
-
|
204
|
-
```ruby
|
205
|
-
require 'concurrent'
|
206
|
-
|
207
|
-
ec = Concurrent::TimerTask.run{ puts 'Boom!' }
|
208
|
-
|
209
|
-
ec.execution_interval #=> 60 == Concurrent::TimerTask::EXECUTION_INTERVAL
|
210
|
-
ec.timeout_interval #=> 30 == Concurrent::TimerTask::TIMEOUT_INTERVAL
|
211
|
-
ec.status #=> "sleep"
|
212
|
-
|
213
|
-
# wait 60 seconds...
|
214
|
-
#=> 'Boom!'
|
215
|
-
|
216
|
-
ec.kill #=> true
|
217
|
-
```
|
57
|
+
class EchoActor < Concurrent::Actor
|
58
|
+
def act(*message)
|
59
|
+
puts "#{message} handled by #{self}"
|
60
|
+
end
|
61
|
+
end
|
218
62
|
|
219
|
-
|
63
|
+
mailbox, pool = EchoActor.pool(5)
|
220
64
|
|
221
|
-
|
65
|
+
timer_proc = proc do
|
66
|
+
mailbox.post(Faker::Company.bs)
|
67
|
+
end
|
222
68
|
|
223
|
-
|
69
|
+
t1 = Concurrent::TimerTask.new(execution_interval: rand(5)+1, &timer_proc)
|
70
|
+
t2 = Concurrent::TimerTask.new(execution_interval: rand(5)+1, &timer_proc)
|
224
71
|
|
225
|
-
|
72
|
+
overlord = Concurrent::Supervisor.new
|
226
73
|
|
227
|
-
|
74
|
+
overlord.add_worker(t1)
|
75
|
+
overlord.add_worker(t2)
|
76
|
+
pool.each{|actor| overlord.add_worker(actor)}
|
228
77
|
|
229
|
-
|
230
|
-
class FinanceActor < Concurrent::Actor
|
231
|
-
def act(query)
|
232
|
-
finance = Finance.new(query)
|
233
|
-
print "[#{Time.now}] RECEIVED '#{query}' to #{self} returned #{finance.update.suggested_symbols}\n\n"
|
234
|
-
end
|
235
|
-
end
|
78
|
+
overlord.run!
|
236
79
|
|
237
|
-
|
80
|
+
#=> ["mesh proactive platforms"] handled by #<EchoActor:0x007fa5ac18bdf8>
|
81
|
+
#=> ["maximize sticky portals"] handled by #<EchoActor:0x007fa5ac18bdd0>
|
82
|
+
#=> ["morph bleeding-edge markets"] handled by #<EchoActor:0x007fa5ac18bd80>
|
83
|
+
#=> ["engage clicks-and-mortar interfaces"] handled by #<EchoActor:0x007fa5ac18bd58>
|
84
|
+
#=> ["monetize transparent infrastructures"] handled by #<EchoActor:0x007fa5ac18bd30>
|
85
|
+
#=> ["morph sexy e-tailers"] handled by #<EchoActor:0x007fa5ac18bdf8>
|
86
|
+
#=> ["exploit dot-com models"] handled by #<EchoActor:0x007fa5ac18bdd0>
|
87
|
+
#=> ["incentivize virtual deliverables"] handled by #<EchoActor:0x007fa5ac18bd80>
|
88
|
+
#=> ["enhance B2B models"] handled by #<EchoActor:0x007fa5ac18bd58>
|
89
|
+
#=> ["envisioneer real-time architectures"] handled by #<EchoActor:0x007fa5ac18bd30>
|
238
90
|
|
239
|
-
|
240
|
-
pool << 'Micosoft'
|
241
|
-
pool << 'google'
|
91
|
+
overlord.stop
|
242
92
|
```
|
243
93
|
|
244
|
-
|
245
|
-
|
246
|
-
Full documentation: [Supervisor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md)
|
94
|
+
### Disclaimer
|
247
95
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
pong.ping = ping
|
252
|
-
|
253
|
-
task = Concurrent::TimerTask.new{ print "Boom!\n" }
|
254
|
-
|
255
|
-
boss = Concurrent::Supervisor.new
|
256
|
-
boss.add_worker(ping)
|
257
|
-
boss.add_worker(pong)
|
258
|
-
boss.add_worker(task)
|
96
|
+
Remember, *there is no silver bullet in concurrent programming.* Concurrency is hard.
|
97
|
+
These tools will help ease the burden, but at the end of the day it is essential that you
|
98
|
+
*know what you are doing.*
|
259
99
|
|
260
|
-
|
100
|
+
* Decouple business logic from concurrency logic
|
101
|
+
* Test business logic separate from concurrency logic
|
102
|
+
* Keep the intersection of business logic and concurrency and small as possible
|
103
|
+
* Don't share mutable data unless absolutely necessary
|
104
|
+
* Protect shared data as much as possible (prefer [immutability](https://github.com/harukizaemon/hamster))
|
105
|
+
* Don't mix Ruby's [concurrency](http://ruby-doc.org/core-2.0.0/Thread.html)
|
106
|
+
[primitives](http://www.ruby-doc.org/core-2.0.0/Mutex.html) with asynchronous concurrency libraries
|
261
107
|
|
262
|
-
|
263
|
-
```
|
108
|
+
### Conference Presentations
|
264
109
|
|
265
|
-
|
110
|
+
I've given several conference presentations on concurrent programming with this gem.
|
111
|
+
Check them out:
|
266
112
|
|
267
|
-
* [
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
113
|
+
* ["Advanced Concurrent Programming in Ruby"](http://rubyconf.org/program#jerry-dantonio)
|
114
|
+
at [RubyConf 2013](http://rubyconf.org/)
|
115
|
+
used [this](https://github.com/jdantonio/concurrent-ruby-presentation) version of the presentation
|
116
|
+
and is available for viewing on [Confreaks](http://www.confreaks.com/videos/2872-rubyconf2013-advanced-concurrent-programming-in-ruby)
|
117
|
+
* ["Advanced Multithreading in Ruby"](http://cascadiaruby.com/#advanced-multithreading-in-ruby)
|
118
|
+
at [Cascadia Ruby 2013](http://cascadiaruby.com/)
|
119
|
+
used [this](https://github.com/jdantonio/concurrent-ruby-presentation/tree/cascadia-ruby-2013) version of the presentation
|
120
|
+
and is available for viewing on [Confreaks](http://www.confreaks.com/videos/2790-cascadiaruby2013-advanced-multithreading-in-ruby)
|
121
|
+
* [Cleveland Ruby Brigade](http://www.meetup.com/ClevelandRuby/events/149981942/) meetup in December of 2013
|
122
|
+
used [this](https://github.com/jdantonio/concurrent-ruby-presentation/releases/tag/clerb-dec-2013) version of the presentation
|
123
|
+
* I'll be giving ["Advanced Concurrent Programming in Ruby"](http://codemash.org/sessions)
|
124
|
+
at [CodeMash 2014](http://codemash.org/)
|
274
125
|
|
275
126
|
## Contributing
|
276
127
|
|
data/lib/concurrent.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
require 'concurrent/version'
|
2
2
|
|
3
|
-
|
4
3
|
require 'concurrent/actor'
|
5
4
|
require 'concurrent/agent'
|
6
5
|
require 'concurrent/contract'
|
6
|
+
require 'concurrent/channel'
|
7
7
|
require 'concurrent/dereferenceable'
|
8
8
|
require 'concurrent/event'
|
9
9
|
require 'concurrent/future'
|
10
|
-
require 'concurrent/goroutine'
|
11
10
|
require 'concurrent/obligation'
|
11
|
+
require 'concurrent/postable'
|
12
12
|
require 'concurrent/promise'
|
13
13
|
require 'concurrent/runnable'
|
14
14
|
require 'concurrent/scheduled_task'
|
15
|
+
require 'concurrent/stoppable'
|
15
16
|
require 'concurrent/supervisor'
|
16
17
|
require 'concurrent/timer_task'
|
17
18
|
require 'concurrent/utilities'
|
@@ -22,3 +23,18 @@ require 'concurrent/cached_thread_pool'
|
|
22
23
|
require 'concurrent/fixed_thread_pool'
|
23
24
|
|
24
25
|
require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
|
26
|
+
|
27
|
+
# Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,
|
28
|
+
# F#, C#, Java, and classic concurrency patterns.
|
29
|
+
#
|
30
|
+
# The design goals of this gem are:
|
31
|
+
#
|
32
|
+
# * Stay true to the spirit of the languages providing inspiration
|
33
|
+
# * But implement in a way that makes sense for Ruby
|
34
|
+
# * Keep the semantics as idiomatic Ruby as possible
|
35
|
+
# * Support features that make sense in Ruby
|
36
|
+
# * Exclude features that don't make sense in Ruby
|
37
|
+
# * Be small, lean, and loosely coupled
|
38
|
+
module Concurrent
|
39
|
+
|
40
|
+
end
|
data/lib/concurrent/actor.rb
CHANGED
@@ -3,78 +3,132 @@ require 'observer'
|
|
3
3
|
|
4
4
|
require 'concurrent/event'
|
5
5
|
require 'concurrent/obligation'
|
6
|
+
require 'concurrent/postable'
|
6
7
|
require 'concurrent/runnable'
|
7
8
|
|
8
9
|
module Concurrent
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
11
|
+
# Actor-based concurrency is all the rage in some circles. Originally described in
|
12
|
+
# 1973, the actor model is a paradigm for creating asynchronous, concurrent objects
|
13
|
+
# that is becoming increasingly popular. Much has changed since actors were first
|
14
|
+
# written about four decades ago, which has led to a serious fragmentation within
|
15
|
+
# the actor community. There is *no* universally accepted, strict definition of
|
16
|
+
# "actor" and actor implementations differ widely between languages and libraries.
|
17
|
+
#
|
18
|
+
# A good definition of "actor" is:
|
19
|
+
#
|
20
|
+
# An independent, concurrent, single-purpose, computational entity that communicates exclusively via message passing.
|
21
|
+
#
|
22
|
+
# The `Concurrent::Actor` class in this library is based solely on the
|
23
|
+
# {http://www.scala-lang.org/api/current/index.html#scala.actors.Actor Actor} trait
|
24
|
+
# defined in the Scala standard library. It does not implement all the features of
|
25
|
+
# Scala's `Actor` but its behavior for what *has* been implemented is nearly identical.
|
26
|
+
# The excluded features mostly deal with Scala's message semantics, strong typing,
|
27
|
+
# and other characteristics of Scala that don't really apply to Ruby.
|
28
|
+
#
|
29
|
+
# Unlike most of the abstractions in this library, `Actor` takes an *object-oriented*
|
30
|
+
# approach to asynchronous concurrency, rather than a *functional programming*
|
31
|
+
# approach.
|
32
|
+
#
|
33
|
+
# Because `Actor` mixes in the `Concurrent::Runnable` module subclasses have access to
|
34
|
+
# the `#on_error` method and can override it to implement custom error handling. The
|
35
|
+
# `Actor` base class does not use `#on_error` so as to avoid conflit with subclasses
|
36
|
+
# which override it. Generally speaking, `#on_error` should not be used. The `Actor`
|
37
|
+
# base class provides concictent, reliable, and robust error handling already, and
|
38
|
+
# error handling specifics are tied to the message posting method. Incorrect behavior
|
39
|
+
# in an `#on_error` override can lead to inconsistent `Actor` behavior that may lead
|
40
|
+
# to confusion and difficult debugging.
|
41
|
+
#
|
42
|
+
# The `Actor` superclass mixes in the Ruby standard library
|
43
|
+
# {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html Observable}
|
44
|
+
# module to provide consistent callbacks upon message processing completion. The normal
|
45
|
+
# `Observable` methods, including `#add_observer` behave normally. Once an observer
|
46
|
+
# is added to an `Actor` it will be notified of all messages processed *after*
|
47
|
+
# addition. Notification will *not* occur for any messages that have already been
|
48
|
+
# processed.
|
49
|
+
#
|
50
|
+
# Observers will be notified regardless of whether the message processing is successful
|
51
|
+
# or not. The `#update` method of the observer will receive four arguments. The
|
52
|
+
# appropriate method signature is:
|
53
|
+
#
|
54
|
+
# def update(time, message, result, reason)
|
55
|
+
#
|
56
|
+
# These four arguments represent:
|
57
|
+
#
|
58
|
+
# * The time that message processing was completed
|
59
|
+
# * An array containing all elements of the original message, in order
|
60
|
+
# * The result of the call to `#act` (will be `nil` if an exception was raised)
|
61
|
+
# * Any exception raised by `#act` (or `nil` if message processing was successful)
|
62
|
+
#
|
63
|
+
# @example Actor Ping Pong
|
64
|
+
# class Ping < Concurrent::Actor
|
65
|
+
#
|
66
|
+
# def initialize(count, pong)
|
67
|
+
# super()
|
68
|
+
# @pong = pong
|
69
|
+
# @remaining = count
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# def act(msg)
|
73
|
+
#
|
74
|
+
# if msg == :pong
|
75
|
+
# print "Ping: pong\n" if @remaining % 1000 == 0
|
76
|
+
# @pong.post(:ping)
|
77
|
+
#
|
78
|
+
# if @remaining > 0
|
79
|
+
# @pong << :ping
|
80
|
+
# @remaining -= 1
|
81
|
+
# else
|
82
|
+
# print "Ping :stop\n"
|
83
|
+
# @pong << :stop
|
84
|
+
# self.stop
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# class Pong < Concurrent::Actor
|
91
|
+
#
|
92
|
+
# attr_writer :ping
|
93
|
+
#
|
94
|
+
# def initialize
|
95
|
+
# super()
|
96
|
+
# @count = 0
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# def act(msg)
|
100
|
+
#
|
101
|
+
# if msg == :ping
|
102
|
+
# print "Pong: ping\n" if @count % 1000 == 0
|
103
|
+
# @ping << :pong
|
104
|
+
# @count += 1
|
105
|
+
#
|
106
|
+
# elsif msg == :stop
|
107
|
+
# print "Pong :stop\n"
|
108
|
+
# self.stop
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# pong = Pong.new
|
114
|
+
# ping = Ping.new(10000, pong)
|
115
|
+
# pong.ping = ping
|
116
|
+
#
|
117
|
+
# t1 = ping.run!
|
118
|
+
# t2 = pong.run!
|
119
|
+
#
|
120
|
+
# ping << :pong
|
121
|
+
#
|
122
|
+
# @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
|
72
123
|
class Actor
|
73
124
|
include Observable
|
74
|
-
include Runnable
|
75
125
|
include Postable
|
126
|
+
include Runnable
|
127
|
+
|
128
|
+
private
|
76
129
|
|
77
|
-
|
130
|
+
# @!visibility private
|
131
|
+
class Poolbox # :nodoc:
|
78
132
|
include Postable
|
79
133
|
|
80
134
|
def initialize(queue)
|
@@ -82,12 +136,53 @@ module Concurrent
|
|
82
136
|
end
|
83
137
|
end
|
84
138
|
|
85
|
-
|
86
|
-
|
139
|
+
public
|
140
|
+
|
141
|
+
# Create a pool of actors that share a common mailbox.
|
142
|
+
#
|
143
|
+
# Every `Actor` instance operates on its own thread. When one thread isn't enough capacity
|
144
|
+
# to manage all the messages being sent to an `Actor` a *pool* can be used instead. A pool
|
145
|
+
# is a collection of `Actor` instances, all of the same type, that shate a message queue.
|
146
|
+
# Messages from other threads are all sent to a single queue against which all `Actor`s
|
147
|
+
# load balance.
|
148
|
+
#
|
149
|
+
# @param [Integer] count the number of actors in the pool
|
150
|
+
# @param [Array] args zero or more arguments to pass to each actor in the pool
|
151
|
+
#
|
152
|
+
# @return [Array] two-element array with the shared mailbox as the first element
|
153
|
+
# and an array of actors as the second element
|
154
|
+
#
|
155
|
+
# @raise ArgumentError if `count` is zero or less
|
156
|
+
#
|
157
|
+
# @example
|
158
|
+
# class EchoActor < Concurrent::Actor
|
159
|
+
# def act(*message)
|
160
|
+
# puts "#{message} handled by #{self}"
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# mailbox, pool = EchoActor.pool(5)
|
165
|
+
# pool.each{|echo| echo.run! }
|
166
|
+
#
|
167
|
+
# 10.times{|i| mailbox.post(i) }
|
168
|
+
# #=> [0] handled by #<EchoActor:0x007fc8014fb8b8>
|
169
|
+
# #=> [1] handled by #<EchoActor:0x007fc8014fb890>
|
170
|
+
# #=> [2] handled by #<EchoActor:0x007fc8014fb868>
|
171
|
+
# #=> [3] handled by #<EchoActor:0x007fc8014fb890>
|
172
|
+
# #=> [4] handled by #<EchoActor:0x007fc8014fb840>
|
173
|
+
# #=> [5] handled by #<EchoActor:0x007fc8014fb8b8>
|
174
|
+
# #=> [6] handled by #<EchoActor:0x007fc8014fb8b8>
|
175
|
+
# #=> [7] handled by #<EchoActor:0x007fc8014fb818>
|
176
|
+
# #=> [8] handled by #<EchoActor:0x007fc8014fb890>
|
177
|
+
def self.pool(count, *args, &block)
|
87
178
|
raise ArgumentError.new('count must be greater than zero') unless count > 0
|
88
179
|
mailbox = Queue.new
|
89
180
|
actors = count.times.collect do
|
90
|
-
|
181
|
+
if block_given?
|
182
|
+
actor = self.new(*args, &block.dup)
|
183
|
+
else
|
184
|
+
actor = self.new(*args)
|
185
|
+
end
|
91
186
|
actor.instance_variable_set(:@queue, mailbox)
|
92
187
|
actor
|
93
188
|
end
|
@@ -96,7 +191,20 @@ module Concurrent
|
|
96
191
|
|
97
192
|
protected
|
98
193
|
|
99
|
-
|
194
|
+
# Actors are defined by subclassing the `Concurrent::Actor` class and overriding the
|
195
|
+
# #act method. The #act method can have any signature/arity but `def act(*args)`
|
196
|
+
# is the most flexible and least error-prone signature. The #act method is called in
|
197
|
+
# response to a message being post to the `Actor` instance (see *Behavior* below).
|
198
|
+
#
|
199
|
+
# @param [Array] message one or more arguments representing the message sent to the
|
200
|
+
# actor via one of the Concurrent::Postable methods
|
201
|
+
#
|
202
|
+
# @return [Object] the result obtained when the message is successfully processed
|
203
|
+
#
|
204
|
+
# @raise NotImplementedError unless overridden in the `Actor` subclass
|
205
|
+
#
|
206
|
+
# @!visibility public
|
207
|
+
def act(*message)
|
100
208
|
raise NotImplementedError.new("#{self.class} does not implement #act")
|
101
209
|
end
|
102
210
|
|
@@ -124,12 +232,12 @@ module Concurrent
|
|
124
232
|
rescue => ex
|
125
233
|
on_error(Time.now, package.message, ex)
|
126
234
|
ensure
|
127
|
-
if
|
128
|
-
package.handler.complete(result, ex)
|
129
|
-
elsif notifier.is_a?(Event) && ! notifier.set?
|
235
|
+
if notifier.is_a?(Event) && ! notifier.set?
|
130
236
|
package.handler.push(result || ex)
|
131
237
|
package.notifier.set
|
132
|
-
elsif package.handler.is_a?(
|
238
|
+
elsif package.handler.is_a?(Contract)
|
239
|
+
package.handler.complete(result, ex)
|
240
|
+
elsif package.handler.respond_to?(:post) && ex.nil?
|
133
241
|
package.handler.post(result)
|
134
242
|
end
|
135
243
|
|