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 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