concurrent-ruby 0.3.0 → 0.3.1.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,13 @@
1
1
  require 'thread'
2
2
  require 'timeout'
3
3
 
4
+ require 'concurrent/dereferenceable'
4
5
  require 'concurrent/event'
5
6
 
6
7
  module Concurrent
7
8
 
8
9
  module Obligation
10
+ include Dereferenceable
9
11
 
10
12
  attr_reader :state
11
13
  attr_reader :reason
@@ -15,7 +17,7 @@ module Concurrent
15
17
  def fulfilled?() return(@state == :fulfilled); end
16
18
  alias_method :realized?, :fulfilled?
17
19
 
18
- # Has the promise been rejected?
20
+ # Has the obligation been rejected?
19
21
  # @return [Boolean]
20
22
  def rejected?() return(@state == :rejected); end
21
23
 
@@ -25,9 +27,8 @@ module Concurrent
25
27
 
26
28
  def value(timeout = nil)
27
29
  event.wait(timeout) unless timeout == 0 || @state != :pending
28
- return @value
30
+ super()
29
31
  end
30
- alias_method :deref, :value
31
32
 
32
33
  protected
33
34
 
@@ -115,13 +115,13 @@ module Concurrent
115
115
  end
116
116
 
117
117
  # @private
118
- def on_fulfill(value) # :nodoc:
118
+ def on_fulfill(result) # :nodoc:
119
119
  @lock.synchronize do
120
- @value = @handler.call(value)
120
+ @value = @handler.call(result)
121
121
  @state = :fulfilled
122
122
  @reason = nil
123
123
  end
124
- return @value
124
+ return self.value
125
125
  end
126
126
 
127
127
  # @private
@@ -0,0 +1,94 @@
1
+ require 'observer'
2
+
3
+ require 'concurrent/obligation'
4
+ require 'concurrent/runnable'
5
+
6
+ module Concurrent
7
+
8
+ class ScheduledTask
9
+ include Obligation
10
+ include Observable
11
+ include Runnable
12
+
13
+ attr_reader :schedule_time
14
+
15
+ def initialize(schedule_time, opts = {}, &block)
16
+ now = Time.now
17
+
18
+ if ! block_given?
19
+ raise ArgumentError.new('no block given')
20
+ elsif schedule_time.is_a?(Time)
21
+ if schedule_time <= now
22
+ raise ArgumentError.new('schedule time must be in the future')
23
+ else
24
+ @schedule_time = schedule_time.dup
25
+ end
26
+ elsif schedule_time.to_f <= 0.0
27
+ raise ArgumentError.new('seconds must be greater than zero')
28
+ else
29
+ @schedule_time = now + schedule_time.to_f
30
+ end
31
+
32
+ @state = :pending
33
+ @task = block
34
+ @schedule_time.freeze
35
+ set_deref_options(opts)
36
+ end
37
+
38
+ def cancelled?
39
+ return @state == :cancelled
40
+ end
41
+
42
+ def in_progress?
43
+ return @state == :in_progress
44
+ end
45
+
46
+ def cancel
47
+ return false if mutex.locked?
48
+ return mutex.synchronize do
49
+ if @state == :pending
50
+ @state = :cancelled
51
+ event.set
52
+ true
53
+ else
54
+ false
55
+ end
56
+ end
57
+ end
58
+
59
+ def add_observer(observer, func = :update)
60
+ return false unless @state == :pending || @state == :in_progress
61
+ super
62
+ end
63
+
64
+ protected
65
+
66
+ def on_task
67
+ while (diff = @schedule_time.to_f - Time.now.to_f) > 0
68
+ sleep( diff > 60 ? 60 : diff )
69
+ end
70
+
71
+ if @state == :pending
72
+ mutex.synchronize do
73
+ @state = :in_progress
74
+ begin
75
+ @value = @task.call
76
+ @state = :fulfilled
77
+ rescue => ex
78
+ @reason = ex
79
+ @state = :rejected
80
+ ensure
81
+ changed
82
+ end
83
+ end
84
+ end
85
+
86
+ if self.changed?
87
+ notify_observers(Time.now, self.value, @reason)
88
+ delete_observers
89
+ end
90
+ event.set
91
+ self.stop
92
+ end
93
+ end
94
+ end
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.1.pre.1'
3
3
  end
@@ -0,0 +1,209 @@
1
+ # All the world's a stage
2
+
3
+ Actor-based concurrency is all the rage in some circles. Originally described in
4
+ 1973, the actor model is a paradigm for creating asynchronous, concurrent objects
5
+ that is becoming increasingly popular. Much has changed since actors were first
6
+ written about four decades ago, which has led to a serious fragmentation within
7
+ the actor community. There is *no* universally accepted, strict definition of
8
+ "actor" and actor implementations differ widely between languages and libraries.
9
+
10
+ A good definition of "actor" is:
11
+
12
+ > An independent, concurrent, single-purpose, computational entity that
13
+ > communicates exclusively via message passing.
14
+
15
+ The `Concurrent::Actor` class in this library is based solely on the
16
+ [Actor](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor) task
17
+ defined in the Scala standard library. It does not implement all the features of
18
+ Scala's `Actor` but its behavior for what *has* been implemented is nearly identical
19
+
20
+ ## Definition
21
+
22
+ Actors are defined by subclassing the `Concurrent::Actor` class and overriding the
23
+ `#act` method. The `#act` method can have any signature/arity but
24
+
25
+ > def act(*args, &block)
26
+
27
+ is the most flexible and least error-prone signature. The `#act` method is called in
28
+ response to a message being `#post` to the `Actor` instance (see *Behavior* below).
29
+
30
+ ## Behavior
31
+
32
+ The `Concurrent::Actor` class includes the `Concurrent::Runnable` module. This provides
33
+ an `Actor` instance with the necessary methods for running and graceful stopping.
34
+ This also means that an `Actor` can be managed by a `Concurrent::Supervisor` for
35
+ fault tolerance.
36
+
37
+ Messages from any thread can be sent to an `Actor` using either the `#post` method. Calling
38
+ this method causes all arguments and a block (if given) to be passed to the subclass `#act`
39
+ method. Messages are processed one at a time in the order received. Each `Actor` subclass
40
+ must detemine how it will interact with the rest of the system. A common practice is for
41
+ one `Actor` to send messages to another `Actor` though this is hardly the only approach.
42
+
43
+ ## Pools
44
+
45
+ Every `Actor` instance operates on its own thread. When one thread isn't enough capacity
46
+ to manage all the messages being sent to an `Actor` a *pool* can be used instead. A pool
47
+ is a collection of `Actor` instances, all of the same type, that shate a message queue.
48
+ Messages from other threads are all sent to a single queue against which all `Actor`s
49
+ load balance.
50
+
51
+ ## Additional Reading
52
+
53
+ * [API documentation](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor)
54
+ for the original (now deprecated) Scala Actor
55
+ * [Scala Actors: A Short Tutorial](http://www.scala-lang.org/old/node/242)
56
+ * [Scala Actors 101](http://java.dzone.com/articles/scala-threadless-concurrent)
57
+
58
+ ## Examples
59
+
60
+ Two `Actor`s playing a back and forth game of Ping Pong, adapted from the Scala example
61
+ [here](http://www.somewhere.com/find/the/blog/post):
62
+
63
+ ```ruby
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
+ sleep(0.1)
120
+
121
+ ping << :pong
122
+ ```
123
+
124
+ A pool of `Actor`s and a `Supervisor`
125
+
126
+ ```ruby
127
+ QUERIES = %w[YAHOO Microsoft google]
128
+
129
+ class FinanceActor < Concurrent::Actor
130
+ def act(query)
131
+ finance = Finance.new(query)
132
+ print "[#{Time.now}] RECEIVED '#{query}' to #{self} returned #{finance.update.suggested_symbols}\n\n"
133
+ end
134
+ end
135
+
136
+ financial, pool = FinanceActor.pool(5)
137
+
138
+ overlord = Concurrent::Supervisor.new
139
+ pool.each{|actor| overlord.add_worker(actor)}
140
+
141
+ overlord.run!
142
+
143
+ pool.post('YAHOO')
144
+
145
+ #>> [2013-10-18 09:35:28 -0400] SENT 'YAHOO' from main to worker pool
146
+ #>> [2013-10-18 09:35:28 -0400] RECEIVED 'YAHOO' to #<FinanceActor:0x0000010331af70>...
147
+ ```
148
+
149
+ The `#post!` method returns an `Obligation` (same API as `Future`) which can be queried
150
+ for value/reason on fulfillment/rejection.
151
+
152
+ ```ruby
153
+ class EverythingActor < Concurrent::Actor
154
+ def act(message)
155
+ sleep(1)
156
+ return 42
157
+ end
158
+ end
159
+
160
+ life = EverythingActor.new
161
+ life.run!
162
+ universe = life.post!('What do you get when you multiply six by nine?')
163
+ universe.pending? #=> true
164
+
165
+ # wait for it...
166
+
167
+ universe.fulfilled? #=> true
168
+ universe.value #=> 42
169
+ ```
170
+
171
+ The `#post?` method is a blocking call. It takes a number of seconds to wait as the
172
+ first parameter and any number of additional parameters as the message. If the message
173
+ is processed within the given number of seconds the call returns the result of the
174
+ operation. If message processing raises an exception the exception is raised again
175
+ by the `#post?` method. If the call to `#post?` times out a `Concurrent::Timeout`
176
+ exception is raised.
177
+
178
+ ```ruby
179
+ # needs code examples...
180
+ ```
181
+
182
+ ## Copyright
183
+
184
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
185
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
186
+
187
+ ## License
188
+
189
+ Released under the MIT license.
190
+
191
+ http://www.opensource.org/licenses/mit-license.php
192
+
193
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
194
+ > of this software and associated documentation files (the "Software"), to deal
195
+ > in the Software without restriction, including without limitation the rights
196
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
197
+ > copies of the Software, and to permit persons to whom the Software is
198
+ > furnished to do so, subject to the following conditions:
199
+ >
200
+ > The above copyright notice and this permission notice shall be included in
201
+ > all copies or substantial portions of the Software.
202
+ >
203
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
204
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
205
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
206
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
207
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
208
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
209
+ > THE SOFTWARE.
@@ -1,20 +1,20 @@
1
1
  # Secret Agent Man
2
2
 
3
- Agents are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) keyword.
4
- An agent is a single atomic value that represents an identity. The current value
5
- of the agent can be requested at any time (`deref`). Each agent has a work queue and operates on
3
+ `Agent`s are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) function.
4
+ An `Agent` is a single atomic value that represents an identity. The current value
5
+ of the `Agent` can be requested at any time (`deref`). Each `Agent` has a work queue and operates on
6
6
  the global thread pool (see below). Consumers can `post` code blocks to the
7
- agent. The code block (function) will receive the current value of the agent as its sole
8
- parameter. The return value of the block will become the new value of the agent. Agents support
9
- two error handling modes: fail and continue. A good example of an agent is a shared incrementing
7
+ `Agent`. The code block (function) will receive the current value of the `Agent` as its sole
8
+ parameter. The return value of the block will become the new value of the `Agent`. `Agent`s support
9
+ two error handling modes: fail and continue. A good example of an `Agent` is a shared incrementing
10
10
  counter, such as the score in a video game.
11
11
 
12
- An agent must be initialize with an initial value. This value is always accessible via the `value`
13
- (or `deref`) methods. Code blocks sent to the agent will be processed in the order received. As
12
+ An `Agent` must be initialize with an initial value. This value is always accessible via the `value`
13
+ (or `deref`) methods. Code blocks sent to the `Agent` will be processed in the order received. As
14
14
  each block is processed the current value is updated with the result from the block. This update
15
15
  is an atomic operation so a `deref` will never block and will always return the current value.
16
16
 
17
- When an agent is created it may be given an optional `validate` block and zero or more `rescue`
17
+ When an `Agent` is created it may be given an optional `validate` block and zero or more `rescue`
18
18
  blocks. When a new value is calculated the value will be checked against the validator, if present.
19
19
  If the validator returns `true` the new value will be accepted. If it returns `false` it will be
20
20
  rejected. If a block raises an exception during execution the list of `rescue` blocks will be
@@ -22,10 +22,29 @@ seacrhed in order until one matching the current exception is found. That `rescu
22
22
  then be called an passed the exception object. If no matching `rescue` block is found, or none
23
23
  were configured, then the exception will be suppressed.
24
24
 
25
- Agents also implement Ruby's [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html).
26
- Code that observes an agent will receive a callback with the new value any time the value
25
+ `Agent`s also implement Ruby's [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html).
26
+ Code that observes an `Agent` will receive a callback with the new value any time the value
27
27
  is changed.
28
28
 
29
+ ## Copy Options
30
+
31
+ Object references in Ruby are mutable. This can lead to serious problems when
32
+ the value of an `Agent` is a mutable reference. Which is always the case unless
33
+ the value is a `Fixnum`, `Symbol`, or similar "primative" data type. Each
34
+ `Agent` instance can be configured with a few options that can help protect the
35
+ program from potentially dangerous operations. Each of these options can be
36
+ optionally set when the `Agent` is created:
37
+
38
+ * `:dup_on_deref` when true the `Agent` will call the `#dup` method on the
39
+ `value` object every time the `#value` methid is called (default: false)
40
+ * `:freeze_on_deref` when true the `Agent` will call the `#freeze` method on the
41
+ `value` object every time the `#value` method is called (default: false)
42
+ * `:copy_on_deref` when given a `Proc` object the `Proc` will be run every time
43
+ the `#value` method is called. The `Proc` will be given the current `value` as
44
+ its only parameter and the result returned by the block will be the return
45
+ value of the `#value` call. When `nil` this option will be ignored (default:
46
+ nil)
47
+
29
48
  ## Examples
30
49
 
31
50
  A simple example:
@@ -1,25 +1,33 @@
1
1
  # We're Sending You Back to the Future!
2
2
 
3
- Futures are inspired by [Clojure's](http://clojure.org/) [future](http://clojuredocs.org/clojure_core/clojure.core/future) keyword.
4
- A future represents a promise to complete an action at some time in the future. The action is atomic and permanent.
5
- The idea behind a future is to send an action off for asynchronous operation, do other stuff, then return and
6
- retrieve the result of the async operation at a later time. Futures run on the global thread pool (see below).
7
-
8
- Futures have three possible states: *pending*, *rejected*, and *fulfilled*. When a future is created it is set
9
- to *pending* and will remain in that state until processing is complete. A completed future is either *rejected*,
10
- indicating that an exception was thrown during processing, or *fulfilled*, indicating succedd. If a future is
3
+ `Future`s are inspired by [Clojure's](http://clojure.org/) [future](http://clojuredocs.org/clojure_core/clojure.core/future)
4
+ function. A future represents a promise to complete an action at some time in the future.
5
+ The action is atomic and permanent. The idea behind a future is to send an operation for
6
+ asynchronous completion, do other stuff, then return and retrieve the result of the async
7
+ operation at a later time. `Future`s run on the global thread pool (see below).
8
+
9
+ `Future`s have three possible states: *pending*, *rejected*, and *fulfilled*. When a `Future` is created it is set
10
+ to *pending* and will remain in that state until processing is complete. A completed `Future` is either *rejected*,
11
+ indicating that an exception was thrown during processing, or *fulfilled*, indicating succedd. If a `Future` is
11
12
  *fulfilled* its `value` will be updated to reflect the result of the operation. If *rejected* the `reason` will
12
13
  be updated with a reference to the thrown exception. The predicate methods `pending?`, `rejected`, and `fulfilled?`
13
- can be called at any time to obtain the state of the future, as can the `state` method, which returns a symbol.
14
+ can be called at any time to obtain the state of the `Future`, as can the `state` method, which returns a symbol.
14
15
 
15
- Retrieving the value of a future is done through the `value` (alias: `deref`) method. Obtaining the value of
16
- a future is a potentially blocking operation. When a future is *rejected* a call to `value` will return `nil`
17
- immediately. When a future is *fulfilled* a call to `value` will immediately return the current value.
18
- When a future is *pending* a call to `value` will block until the future is either *rejected* or *fulfilled*.
16
+ Retrieving the value of a `Future` is done through the `value` (alias: `deref`) method. Obtaining the value of
17
+ a `Future` is a potentially blocking operation. When a `Future` is *rejected* a call to `value` will return `nil`
18
+ immediately. When a `Future` is *fulfilled* a call to `value` will immediately return the current value.
19
+ When a `Future` is *pending* a call to `value` will block until the `Future` is either *rejected* or *fulfilled*.
19
20
  A *timeout* value can be passed to `value` to limit how long the call will block. If `nil` the call will
20
21
  block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the
21
22
  maximum number of seconds to block.
22
23
 
24
+ The `Future` class also includes the Ruby standard library
25
+ [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html) module. On fulfillment
26
+ or rejection all observers will be notified according to the normal `Observable` behavior. The observer
27
+ callback function will be called with three parameters: the `Time` of fulfillment/rejection, the
28
+ final `value`, and the final `reason`. Observers added after fulfillment/rejection will still be
29
+ notified as normal. The notification will occur on the global thread pool.
30
+
23
31
  ## Examples
24
32
 
25
33
  A fulfilled example:
@@ -53,6 +61,40 @@ count.rejected? #=> true
53
61
  count.reason #=> #<StandardError: Boom!>
54
62
  ```
55
63
 
64
+ An example with observation:
65
+
66
+ ```ruby
67
+ class Ticker
68
+ Stock = Struct.new(:symbol, :name, :exchange)
69
+
70
+ def update(time, value, reason)
71
+ ticker = value.collect do |symbol|
72
+ Stock.new(symbol['symbol'], symbol['name'], symbol['exch'])
73
+ end
74
+
75
+ output = ticker.join("\n")
76
+ print "#{output}\n"
77
+ end
78
+ end
79
+
80
+ yahoo = Finance.new('YAHOO')
81
+ future = Concurrent::Future.new { yahoo.update.suggested_symbols }
82
+ future.add_observer(Ticker.new)
83
+
84
+ # do important stuff...
85
+
86
+ #>> #<struct Ticker::Stock symbol="YHOO", name="Yahoo! Inc.", exchange="NMS">
87
+ #>> #<struct Ticker::Stock symbol="YHO.DE", name="Yahoo! Inc.", exchange="GER">
88
+ #>> #<struct Ticker::Stock symbol="YAHOY", name="Yahoo Japan Corporation", exchange="PNK">
89
+ #>> #<struct Ticker::Stock symbol="YAHOF", name="YAHOO JAPAN CORP", exchange="PNK">
90
+ #>> #<struct Ticker::Stock symbol="YOJ.SG", name="YAHOO JAPAN", exchange="STU">
91
+ #>> #<struct Ticker::Stock symbol="YHO.SG", name="YAHOO", exchange="STU">
92
+ #>> #<struct Ticker::Stock symbol="YHOO.BA", name="Yahoo! Inc.", exchange="BUE">
93
+ #>> #<struct Ticker::Stock symbol="YHO.DU", name="YAHOO", exchange="DUS">
94
+ #>> #<struct Ticker::Stock symbol="YHO.HM", name="YAHOO", exchange="HAM">
95
+ #>> #<struct Ticker::Stock symbol="YHO.BE", name="YAHOO", exchange="BER">
96
+ ```
97
+
56
98
  ## Copyright
57
99
 
58
100
  *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).