concurrent-ruby 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/jdantonio/concurrent-ruby.png)](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [![Coverage Status](https://coveralls.io/repos/jdantonio/concurrent-ruby/badge.png)](https://coveralls.io/r/jdantonio/concurrent-ruby) [![Dependency Status](https://gemnasium.com/jdantonio/concurrent-ruby.png)](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
|
|