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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de0e0da91bc658bb4ed962ec88d82b3997b95a09
4
- data.tar.gz: 35d3a5d3111ff16f3a7782f38736e84b25007fc1
3
+ metadata.gz: 755d47349aa9471448745d4d102eef30c9ff39f9
4
+ data.tar.gz: 3ed64a6aa698387b8043cceb72bb64c3b45d4f2f
5
5
  SHA512:
6
- metadata.gz: 804490a05bcf41198363ec31ec285535039dd9beb9a4a46cf2e266c7b26ca333f9dada1ecfafacb8a3f9146c1b63cf5b699385d011306cceea8d6ddbc78f9ff8
7
- data.tar.gz: b2a9eaec2ce45a071ee8ef9dc3695f3842c8b23dea252d52e8c3ac5a0fd533aa290ff6e278055173388459a92f0d33c8e41d896d872b04870a9575d5e1a2e4c3
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 including agents, futures, promises, thread pools, supervisors, and more.
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
- provide asynchronous concurrency mechanisms within their standard libraries, the
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
- * [RubyGems project page](https://rubygems.org/gems/concurrent-ruby)
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
- * Keep everything small
64
- * Be as fast as reasonably possible
20
+ * Be small, lean, and loosely coupled
65
21
 
66
- ## Features (and Documentation)
22
+ ## Features & Documentation
67
23
 
68
- Several features from Erlang, Go, Clojure, Java, and JavaScript have been implemented thus far:
69
-
70
- * Clojure inspired [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
71
- * Clojure inspired [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
72
- * Scala inspired [Actor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/actor.md)
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 inspired [TimerTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/timer_task.md) service
78
- * Scheduled task execution with Java inspired [ScheduledTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/scheduled_task.md) service
79
- * Erlang inspired [Supervisor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md) for managing long-running threads
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
- ### Is it any good?
37
+ ### Semantic Versioning
82
38
 
83
- [Yes](http://news.ycombinator.com/item?id=3067434)
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
- ### Install
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
- Full documentation: [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
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
- count = Concurrent::Future.new{ sleep(1); 10 }
154
- count.state #=> :pending
155
- # do stuff...
156
- count.value #=> 10 (after blocking)
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
- #### ScheduledTask (Java)
63
+ mailbox, pool = EchoActor.pool(5)
220
64
 
221
- Full documentation: [ScheduledTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/scheduled_task.md)
65
+ timer_proc = proc do
66
+ mailbox.post(Faker::Company.bs)
67
+ end
222
68
 
223
- *TBD*
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
- #### Actor (Scala)
72
+ overlord = Concurrent::Supervisor.new
226
73
 
227
- Full documentation: [Actor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/actor.md)
74
+ overlord.add_worker(t1)
75
+ overlord.add_worker(t2)
76
+ pool.each{|actor| overlord.add_worker(actor)}
228
77
 
229
- ```ruby
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
- financial, pool = FinanceActor.pool(5)
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
- pool << 'YAHOO'
240
- pool << 'Micosoft'
241
- pool << 'google'
91
+ overlord.stop
242
92
  ```
243
93
 
244
- #### Supervisor (Erlang)
245
-
246
- Full documentation: [Supervisor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md)
94
+ ### Disclaimer
247
95
 
248
- ```ruby
249
- pong = Pong.new
250
- ping = Ping.new(10000, pong)
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
- boss.run!
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
- ping << :pong
263
- ```
108
+ ### Conference Presentations
264
109
 
265
- ## Todo
110
+ I've given several conference presentations on concurrent programming with this gem.
111
+ Check them out:
266
112
 
267
- * [Task Parallel Library (TPL)](http://msdn.microsoft.com/en-us/library/dd460717.aspx)
268
- * [Data Parallelism](http://msdn.microsoft.com/en-us/library/dd537608.aspx)
269
- * [Task Parallelism](http://msdn.microsoft.com/en-us/library/dd537609.aspx)
270
- * More Erlang goodness
271
- * [gen_server](http://www.erlang.org/doc/man/gen_server.html)
272
- * [gen_event](http://www.erlang.org/doc/man/gen_event.html)
273
- * [gen_fsm](http://www.erlang.org/doc/man/gen_fsm.html)
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
 
@@ -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
@@ -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
- module Postable
11
-
12
- Package = Struct.new(:message, :handler, :notifier)
13
-
14
- def post(*message)
15
- return false unless ready?
16
- queue.push(Package.new(message))
17
- return queue.length
18
- end
19
-
20
- def <<(message)
21
- post(*message)
22
- return self
23
- end
24
-
25
- def post?(*message)
26
- return nil unless ready?
27
- contract = Contract.new
28
- queue.push(Package.new(message, contract))
29
- return contract
30
- end
31
-
32
- def post!(seconds, *message)
33
- raise Concurrent::Runnable::LifecycleError unless ready?
34
- raise Concurrent::TimeoutError if seconds.to_f <= 0.0
35
- event = Event.new
36
- cback = Queue.new
37
- queue.push(Package.new(message, cback, event))
38
- if event.wait(seconds)
39
- result = cback.pop
40
- if result.is_a?(Exception)
41
- raise result
42
- else
43
- return result
44
- end
45
- else
46
- event.set # attempt to cancel
47
- raise Concurrent::TimeoutError
48
- end
49
- end
50
-
51
- def forward(receiver, *message)
52
- return false unless ready?
53
- queue.push(Package.new(message, receiver))
54
- return queue.length
55
- end
56
-
57
- def ready?
58
- if self.respond_to?(:running?) && ! running?
59
- return false
60
- else
61
- return true
62
- end
63
- end
64
-
65
- private
66
-
67
- def queue
68
- @queue ||= Queue.new
69
- end
70
- end
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
- class Poolbox
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
- # FIXME: duplicate the block (thread safety)
86
- def self.pool(count, &block)
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
- actor = self.new(&block)
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
- def act(*args)
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 package.handler.is_a?(Contract)
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?(Actor) && ex.nil?
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