concurrent-ruby 0.3.1 → 0.3.2

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: f88c2ee3113049d6c98628f54585643f62329249
4
- data.tar.gz: 710552f92a683ba9a66ec30f8ea07567890e5dfe
3
+ metadata.gz: de0e0da91bc658bb4ed962ec88d82b3997b95a09
4
+ data.tar.gz: 35d3a5d3111ff16f3a7782f38736e84b25007fc1
5
5
  SHA512:
6
- metadata.gz: d13cfa849aed3f685ba1282f93ee9558bca755cc4c52cfc2b6531f8f585282735f827f89a465fda7c8a0765cdd1aa343477b6d764ee7f1a775ab6048e06ed97d
7
- data.tar.gz: 0e70b81e3930680b8771ed2b754425d6786f57ac2b07ebbda3bf4d570b07533e0f388dd430835d8323e9d1c3e9687336543cc97d5dfdb43c601323211bac884a
6
+ metadata.gz: 804490a05bcf41198363ec31ec285535039dd9beb9a4a46cf2e266c7b26ca333f9dada1ecfafacb8a3f9146c1b63cf5b699385d011306cceea8d6ddbc78f9ff8
7
+ data.tar.gz: b2a9eaec2ce45a071ee8ef9dc3695f3842c8b23dea252d52e8c3ac5a0fd533aa290ff6e278055173388459a92f0d33c8e41d896d872b04870a9575d5e1a2e4c3
data/README.md CHANGED
@@ -1,10 +1,7 @@
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
3
  Modern concurrency tools including agents, futures, promises, thread pools, supervisors, and more.
4
- Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency patterns.
5
-
6
- If you find this gem useful you should check out my [functional-ruby](https://github.com/jdantonio/functional-ruby)
7
- gem, too. This gem uses several of the tools in that gem.
4
+ Inspired by Erlang, Clojure, Scala, Go, Java, JavaScript, and classic concurrency patterns.
8
5
 
9
6
  ## Introduction
10
7
 
@@ -82,6 +82,7 @@ module Concurrent
82
82
  end
83
83
  end
84
84
 
85
+ # FIXME: duplicate the block (thread safety)
85
86
  def self.pool(count, &block)
86
87
  raise ArgumentError.new('count must be greater than zero') unless count > 0
87
88
  mailbox = Queue.new
@@ -14,6 +14,7 @@ module Concurrent
14
14
  Thread.abort_on_exception = false
15
15
  runner.run
16
16
  end
17
+ @thread.join(0.1) # let the thread start
17
18
  end
18
19
  end
19
20
 
@@ -32,11 +33,11 @@ module Concurrent
32
33
 
33
34
  def run!(abort_on_exception = false)
34
35
  raise LifecycleError.new('already running') if @running
35
- thread = Thread.new{
36
+ thread = Thread.new do
36
37
  Thread.current.abort_on_exception = abort_on_exception
37
38
  self.run
38
- }
39
- Thread.pass
39
+ end
40
+ thread.join(0.1) # let the thread start
40
41
  return thread
41
42
  end
42
43
 
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
@@ -15,17 +15,25 @@ A good definition of "actor" is:
15
15
  The `Concurrent::Actor` class in this library is based solely on the
16
16
  [Actor](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor) task
17
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
18
+ Scala's `Actor` but its behavior for what *has* been implemented is nearly identical.
19
+ The excluded features mostly deal with Scala's message semantics, strong typing,
20
+ and other characteristics of Scala that don't really apply to Ruby.
21
+
22
+ Unlike most of the abstractions in this library, `Actor` takes an *object-oriented*
23
+ approach to asynchronous concurrency, rather than a *functional programming*
24
+ approach.
19
25
 
20
26
  ## Definition
21
27
 
22
28
  Actors are defined by subclassing the `Concurrent::Actor` class and overriding the
23
29
  `#act` method. The `#act` method can have any signature/arity but
24
30
 
25
- > def act(*args, &block)
31
+ ```ruby
32
+ def act(*args, &block)
33
+ ```
26
34
 
27
35
  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).
36
+ response to a message being post to the `Actor` instance (see *Behavior* below).
29
37
 
30
38
  ## Behavior
31
39
 
@@ -34,13 +42,133 @@ an `Actor` instance with the necessary methods for running and graceful stopping
34
42
  This also means that an `Actor` can be managed by a `Concurrent::Supervisor` for
35
43
  fault tolerance.
36
44
 
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
45
+ ### Message Passing
46
+
47
+ Messages from any thread can be sent (aka "post") to an `Actor` using several methods.
48
+ When a message is post all arguments are gathered together and queued for processing.
49
+ Messages are processed in the order they are received, one at a time, on a dedicated
50
+ thread. When a message is processed the subclass `#act` method is called and the return
51
+ value (or raised exception) is handled by the superclass based on the rules of the method
52
+ used to post the message.
53
+
54
+ All message posting methods are compatible with observation (see below).
55
+
56
+ Message processing within the `#act` method is not limited in any way, but care should
57
+ be taken to behave in a thread-safe, concurrency-friendly manner. A common practice is for
41
58
  one `Actor` to send messages to another `Actor` though this is hardly the only approach.
42
59
 
43
- ## Pools
60
+ Messages post to an `Actor` that is not running will be rejected.
61
+
62
+ #### Fire and Forget
63
+
64
+ The primary method of posting a message to an `Actor` is the simple `#post` method.
65
+ When this method is called the message is queued for processing. The method returns
66
+ false if it cannot be queued (the `Actor` is not running) otherwise it returns the
67
+ size of the queue (after queueing the new message). The caller thread has no way
68
+ to know the result of the message processing. When the `#post` method is used the
69
+ only way to act upon the result of the message processing is via observation
70
+ (see below).
71
+
72
+ #### Post with an Obligation
73
+
74
+ A common theme in modern asynchronous concurrency is for operations to return a
75
+ "future" (or "promise"). In this context a "future" is not an instance of the
76
+ `Concurrent::Future` class, but it is an object with similar behavior. Within
77
+ this library "future" behavior is genericized by the `Concurrent::Obligation`
78
+ mixin module (shared by `Future`, `Promise`, and others).
79
+
80
+ To post a message that returns a `Obligation` use the `#post?` method. If the message
81
+ cannot be queued the method will return `nil`. Otherwise an object implementing
82
+ `Obligation` will returned. The `Obligation` has the exteced states (`:pending`,
83
+ `:fulfilled`, and `:rejected`), the expected state-named predicate methods,
84
+ plus `#value` and `#reason`. These methods all behave identically to `Concurrent::Future`.
85
+
86
+ #### Post with Timeout
87
+
88
+ Threads posting messages to an `Actor` should generally not block. Blocking to wait
89
+ for an `Actor` to process a specific message defeats the purpose of asynchronous
90
+ concurrency. The `#post!` method is provided when the caller absolutely must block.
91
+ The first argument to `#post!` is a number of seconds to block while waiting for the
92
+ operation to complete. All subsequent arguments constitute the message and are
93
+ queued for delivery to the `#act` method. If the queued operation completes within
94
+ the timeout period the `#post!` method returns the result of the operation.
95
+
96
+ Unlike most methods in this library, the `#post!` method does not suppress exceptions.
97
+ Because the `#post!` method return value represents the result of message processing
98
+ the return value cannot effectively communicate failure. Instead, exceptions are used.
99
+ Calls to the `#post!` method should generally be wrapped in `rescue` guards. The
100
+ following exceptions may be raised by the `#post!` method:
101
+
102
+ * `Concurrent::Runnable::LifecycleError` will be raised if the message cannot be
103
+ queued, such as when the `Actor` is not running.
104
+ * `Concurrent::TimeoutError` will be raised if the message is not processed within
105
+ the designated timeout period
106
+ * Any exception raised during message processing will be re-raised after all
107
+ post-processing operations (such as observer callbacks) have completed
108
+
109
+ When the `#post!` method results in a timeout the `Actor` will attempt to cancel
110
+ message processing, but cancellation is not guaranteed. If message processing has
111
+ not begun the cancellation will normally occur. If message processing is in-progress
112
+ when `#post!` reaches timeout then processing will be allowed to complete. Code that
113
+ uses the `#post!` method must therefore not assume that a timeout means that message
114
+ processing did not occur.
115
+
116
+ #### Implicit Forward/Reply
117
+
118
+ A common idiom is for an `Actor` to send messages to another `Actor`. This creates
119
+ a "data flow" style of design not dissimilar to Unix-style pipe commands. Less common,
120
+ but still frequent, is for an `Actor` to send the result of message processing back
121
+ to the `Actor` that sent the message. In Scala this is easy to do. The underlying
122
+ message passing system implicitly communicates to the receiver the address of the
123
+ sender. Therefore, Scala actors can easily reply to the sender. Ruby has no similar
124
+ message passing subsystem to implicit knowledge of the sender is not possible. This
125
+ `Actor` implementation provides a `#forward` method that encapsulates both
126
+ aforementioned idioms. The first argument to the `#forward` method is a reference
127
+ to another `Actor` to which the receiving `Actor` should forward the result of
128
+ the processed messages. All subsequent arguments constitute the message and are
129
+ queued for delivery to the `#act` method.
130
+
131
+ Upon successful message processing the `Actor` superclass will automatically
132
+ forward the result to the receiver provided when `#forward` was called. If an
133
+ exception is raised no forwarding occurs.
134
+
135
+ ### Error Handling
136
+
137
+ Because `Actor` mixes in the `Concurrent::Runnable` module subclasses have access to
138
+ the `#on_error` method and can override it to implement custom error handling. The
139
+ `Actor` base class does not use `#on_error` so as to avoid conflit with subclasses
140
+ which override it. Generally speaking, `#on_error` should not be used. The `Actor`
141
+ base class provides concictent, reliable, and robust error handling already, and
142
+ error handling specifics are tied to the message posting method. Incorrect behavior
143
+ in an `#on_error` override can lead to inconsistent `Actor` behavior that may lead
144
+ to confusion and difficult debugging.
145
+
146
+ ### Observation
147
+
148
+ The `Actor` superclass mixes in the Ruby standard library
149
+ [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html)
150
+ module to provide consistent callbacks upon message processing completion. The normal
151
+ `Observable` methods, including `#add_observer` behave normally. Once an observer
152
+ is added to an `Actor` it will be notified of all messages processed *after*
153
+ addition. Notification will *not* occur for any messages that have already been
154
+ processed.
155
+
156
+ Observers will be notified regardless of whether the message processing is successful
157
+ or not. The `#update` method of the observer will receive four arguments. The
158
+ appropriate method signature is:
159
+
160
+ ```ruby
161
+ def update(time, message, result, reason)
162
+ ```
163
+
164
+ These four arguments represent:
165
+
166
+ * The time that message processing was completed
167
+ * An array containing all elements of the original message, in order
168
+ * The result of the call to `#act` (will be `nil` if an exception was raised)
169
+ * Any exception raised by `#act` (or `nil` if message processing was successful)
170
+
171
+ ### Actor Pools
44
172
 
45
173
  Every `Actor` instance operates on its own thread. When one thread isn't enough capacity
46
174
  to manage all the messages being sent to an `Actor` a *pool* can be used instead. A pool
@@ -58,7 +186,7 @@ load balance.
58
186
  ## Examples
59
187
 
60
188
  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):
189
+ [here](http://www.scala-lang.org/old/node/242):
62
190
 
63
191
  ```ruby
64
192
  class Ping < Concurrent::Actor
@@ -146,20 +274,45 @@ pool.post('YAHOO')
146
274
  #>> [2013-10-18 09:35:28 -0400] RECEIVED 'YAHOO' to #<FinanceActor:0x0000010331af70>...
147
275
  ```
148
276
 
149
- The `#post!` method returns an `Obligation` (same API as `Future`) which can be queried
277
+ The `#post` method simply sends a message to an actor and returns. It's a
278
+ fire-and-forget interaction.
279
+
280
+ ```ruby
281
+ class EchoActor < Concurrent::Actor
282
+ def act(*message)
283
+ p message
284
+ end
285
+ end
286
+
287
+ echo = EchoActor.new
288
+ echo.run!
289
+
290
+ echo.post("Don't panic") #=> true
291
+ #=> ["Don't panic"]
292
+
293
+ echo.post(1, 2, 3, 4, 5) #=> true
294
+ #=> [1, 2, 3, 4, 5]
295
+
296
+ echo << "There's a frood who really knows where his towel is." #=> #<EchoActor:0x007fc8012b8448...
297
+ #=> ["There's a frood who really knows where his towel is."]
298
+ ```
299
+
300
+ The `#post?` method returns an `Obligation` (same API as `Future`) which can be queried
150
301
  for value/reason on fulfillment/rejection.
151
302
 
152
303
  ```ruby
153
304
  class EverythingActor < Concurrent::Actor
154
305
  def act(message)
155
- sleep(1)
306
+ sleep(5)
156
307
  return 42
157
308
  end
158
309
  end
159
310
 
160
311
  life = EverythingActor.new
161
312
  life.run!
162
- universe = life.post!('What do you get when you multiply six by nine?')
313
+ sleep(0.1)
314
+
315
+ universe = life.post?('What do you get when you multiply six by nine?')
163
316
  universe.pending? #=> true
164
317
 
165
318
  # wait for it...
@@ -168,15 +321,57 @@ universe.fulfilled? #=> true
168
321
  universe.value #=> 42
169
322
  ```
170
323
 
171
- The `#post?` method is a blocking call. It takes a number of seconds to wait as the
324
+ The `#post!` method is a blocking call. It takes a number of seconds to wait as the
172
325
  first parameter and any number of additional parameters as the message. If the message
173
326
  is processed within the given number of seconds the call returns the result of the
174
327
  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`
328
+ by the `#post!` method. If the call to `#post!` times out a `Concurrent::Timeout`
176
329
  exception is raised.
177
330
 
178
331
  ```ruby
179
- # needs code examples...
332
+ life = EverythingActor.new
333
+ life.run!
334
+ sleep(0.1)
335
+
336
+ life.post!(1, 'Mostly harmless.')
337
+
338
+ # wait for it...
339
+ #=> Concurrent::TimeoutError: Concurrent::TimeoutError
340
+ ```
341
+
342
+ And, of course, the `Actor` class mixes in Ruby's `Observable`.
343
+
344
+ ```ruby
345
+ class ActorObserver
346
+ def update(time, message, result, ex)
347
+ if result
348
+ print "(#{time}) Message #{message} returned #{result}\n"
349
+ elsif ex.is_a?(Concurrent::TimeoutError)
350
+ print "(#{time}) Message #{message} timed out\n"
351
+ else
352
+ print "(#{time}) Message #{message} failed with error #{ex}\n"
353
+ end
354
+ end
355
+ end
356
+
357
+ class SimpleActor < Concurrent::Actor
358
+ def act(*message)
359
+ message
360
+ end
361
+ end
362
+
363
+ actor = SimpleActor.new
364
+ actor.add_observer(ActorObserver.new)
365
+ actor.run!
366
+
367
+ actor.post(1)
368
+ #=> (2013-11-07 18:35:33 -0500) Message [1] returned [1]
369
+
370
+ actor.post(1,2,3)
371
+ #=> (2013-11-07 18:35:54 -0500) Message [1, 2, 3] returned [1, 2, 3]
372
+
373
+ actor.post('The Nightman Cometh')
374
+ #=> (2013-11-07 18:36:11 -0500) Message ["The Nightman Cometh"] returned ["The Nightman Cometh"]
180
375
  ```
181
376
 
182
377
  ## Copyright
@@ -1,6 +1,25 @@
1
- # Event
1
+ # Dereferenceable
2
2
 
3
- TBD...
3
+ Object references in Ruby are mutable. This can lead to serious problems when
4
+ the `#value` of a concurrent object is a mutable reference. Which is always the
5
+ case unless the value is a `Fixnum`, `Symbol`, or similar "primative" data type.
6
+ Most classes in this library that expose a `#value` getter method do so using
7
+ the Dereferenceable mixin module.
8
+
9
+
10
+ Each `Agent` instance can be configured with a few options that can help protect the
11
+ program from potentially dangerous operations. Each of these options can be
12
+ optionally set when the `Agent` is created:
13
+
14
+ * `:dup_on_deref` when true the `Agent` will call the `#dup` method on the
15
+ `value` object every time the `#value` methid is called (default: false)
16
+ * `:freeze_on_deref` when true the `Agent` will call the `#freeze` method on the
17
+ `value` object every time the `#value` method is called (default: false)
18
+ * `:copy_on_deref` when given a `Proc` object the `Proc` will be run every time
19
+ the `#value` method is called. The `Proc` will be given the current `value` as
20
+ its only parameter and the result returned by the block will be the return
21
+ value of the `#value` call. When `nil` this option will be ignored (default:
22
+ nil)
4
23
 
5
24
  ## Copyright
6
25
 
@@ -1,8 +1,130 @@
1
1
  # I'm late! For a very important date!
2
2
 
3
- TBD
3
+ `ScheduledTask` is a close relative of `Concurrent::Future` but with one
4
+ important difference. A `Future` is set to execute as soon as possible
5
+ whereas a `ScheduledTask` is set to execute at a specific time. This implementation
6
+ is loosely based on Java's
7
+ [ScheduledExecutorService](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html).
4
8
 
5
- [ScheduledExecutorService](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html)
9
+ ## Scheduling
10
+
11
+ The scheduled time of task execution is set on object construction. The first
12
+ parameter to `#new` is the task execution time. The time can be a numeric (floating
13
+ point or integer) representing a number of seconds in the future or it can ba a
14
+ `Time` object representing the approximate time of execution. Any other value,
15
+ a numeric equal to or less than zero, or a time in the past will result in
16
+ an `ArgumentError` being raised.
17
+
18
+ The constructor can also be given zero or more processing options. Currently the
19
+ only supported options are those recognized by the
20
+ [Dereferenceable](https://github.com/jdantonio/concurrent-ruby/blob/master/md/dereferenceable.md)
21
+ module.
22
+
23
+ The final constructor argument is a block representing the task to be performed
24
+ at the scheduled time. If no block is given an `ArgumentError` will be raised.
25
+
26
+ ### States
27
+
28
+ `ScheduledTask` mixes in the
29
+ [Obligation](https://github.com/jdantonio/concurrent-ruby/blob/master/md/obligation.md)
30
+ module thus giving it "future" behavior. This includes the expected lifecycle states.
31
+ `ScheduledTask` has one additional state, however. While the task (block) is being
32
+ executed the state of the object will be `:in_progress`. This additional state is
33
+ necessary because it has implications for task cancellation.
34
+
35
+ ### Cancellation
36
+
37
+ A `:pending` task can be cancelled using the `#cancel` method. A task in any other
38
+ state, including `:in_progress`, cannot be cancelled. The `#cancel` method returns
39
+ a boolean indicating the success of the cancellation attempt. A cancelled `ScheduledTask`
40
+ cannot be restarted. It is immutable.
41
+
42
+ ## Obligation and Observation
43
+
44
+ The result of a `ScheduledTask` can be obtained either synchronously or asynchronously.
45
+ `ScheduledTask` mixes in both the
46
+ [Obligation](https://github.com/jdantonio/concurrent-ruby/blob/master/md/obligation.md)
47
+ module and the
48
+ [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html)
49
+ module from the Ruby standard library. With one exception `ScheduledTask` behaves
50
+ identically to
51
+ [Concurrent::Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
52
+ with regard to these modules.
53
+
54
+ Unlike `Future`, however, an observer added to a `ScheduledTask` *after* the task
55
+ operation has completed will *not* receive notification. The reason for this is the
56
+ subtle but important difference in intent between the two abstractions. With a
57
+ `Future` there is no way to know when the operation will complete. Therefore the
58
+ *expected* behavior of an observer is to be notified. With a `ScheduledTask` however,
59
+ the approximate time of execution is known. It is often explicitly set as a constructor
60
+ argument. It is always available via the `#schedule_time` attribute reader. Therefore
61
+ it is always possible for calling code to know whether the observer is being added
62
+ prior to task execution. It is also easy to add an observer long before task
63
+ execution begins (since there is never a reason to create a scheduled task that starts
64
+ immediately). Subsequently, the *expectation* is that the caller of `#add_observer`
65
+ is making the call within an appropriate time.
66
+
67
+ ## Examples
68
+
69
+ Successful task execution using seconds for scheduling:
70
+
71
+ ```ruby
72
+ require 'concurrent'
73
+
74
+ task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }
75
+ task.pending? #=> true
76
+ task.schedule_time #=> 2013-11-07 12:20:07 -0500
77
+
78
+ # wait for it...
79
+ sleep(3)
80
+
81
+ task.pending? #=> false
82
+ task.fulfilled? #=> true
83
+ task.rejected? #=> false
84
+ task.value #=> 'What does the fox say?'
85
+ ```
86
+
87
+ Failed task execution using a `Time` object for scheduling:
88
+
89
+ ```ruby
90
+ require 'concurrent'
91
+
92
+ t = Time.now + 2
93
+ task = Concurrent::ScheduledTask.new(t){ raise StandardError.new('Call me maybe?') }
94
+ task.pending? #=> true
95
+ task.schedule_time #=> 2013-11-07 12:22:01 -0500
96
+
97
+ # wait for it...
98
+ sleep(3)
99
+
100
+ task.pending? #=> false
101
+ task.fulfilled? #=> false
102
+ task.rejected? #=> true
103
+ task.value #=> nil
104
+ task.reason #=> #<StandardError: Call me maybe?>
105
+ ```
106
+
107
+ Task execution with observation:
108
+
109
+ ```ruby
110
+ require 'concurrent'
111
+
112
+ observer = Class.new{
113
+ def update(time, value, reason)
114
+ puts "The task completed at #{time} with value '#{value}'"
115
+ end
116
+ }.new
117
+
118
+ task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }
119
+ task.add_observer(observer)
120
+ task.pending? #=> true
121
+ task.schedule_time #=> 2013-11-07 12:20:07 -0500
122
+
123
+ # wait for it...
124
+ sleep(3)
125
+
126
+ #>> The task completed at 2013-11-07 12:26:09 -0500 with value 'What does the fox say?'
127
+ ```
6
128
 
7
129
  ## Copyright
8
130
 
@@ -25,6 +25,9 @@ Unlike other abstraction in this library, `TimerTask` does not run on the global
25
25
  In my experience the types of tasks that will benefit from `TimerTask` tend to also be long
26
26
  running. For this reason they get their own thread every time the task is executed.
27
27
 
28
+ This class is based on the Java class
29
+ [of the same name](http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html).
30
+
28
31
  ## Observation
29
32
 
30
33
  `TimerTask` supports notification through the Ruby standard library
@@ -41,37 +44,54 @@ A basic example:
41
44
  ```ruby
42
45
  require 'concurrent'
43
46
 
44
- ec = Concurrent::TimerTask.run{ puts 'Boom!' }
47
+ task = Concurrent::TimerTask.new{ puts 'Boom!' }
48
+ task.run!
45
49
 
46
- ec.execution_interval #=> 60 == Concurrent::TimerTask::EXECUTION_INTERVAL
47
- ec.timeout_interval #=> 30 == Concurrent::TimerTask::TIMEOUT_INTERVAL
48
- ec.status #=> "sleep"
50
+ task.execution_interval #=> 60 (default)
51
+ task.timeout_interval #=> 30 (default)
49
52
 
50
53
  # wait 60 seconds...
51
54
  #=> 'Boom!'
52
55
 
53
- ec.kill #=> true
56
+ task.stop #=> true
54
57
  ```
55
58
 
56
59
  Both the execution_interval and the timeout_interval can be configured:
57
60
 
58
61
  ```ruby
59
- ec = Concurrent::TimerTask.run(execution_interval: 5, timeout_interval: 5) do
62
+ task = Concurrent::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
60
63
  puts 'Boom!'
61
64
  end
62
65
 
63
- ec.runner.execution_interval #=> 5
64
- ec.runner.timeout_interval #=> 5
66
+ task.execution_interval #=> 5
67
+ task.timeout_interval #=> 5
65
68
  ```
66
69
 
67
70
  By default an `TimerTask` will wait for `:execution_interval` seconds before running the block.
68
71
  To run the block immediately set the `:run_now` option to `true`:
69
72
 
70
73
  ```ruby
71
- ec = Concurrent::TimerTask.run(run_now: true){ puts 'Boom!' }
72
- #=> 'Boom!''
73
- ec.thread.status #=> "sleep"
74
- >>
74
+ task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' }
75
+ task.run!
76
+
77
+ #=> 'Boom!'
78
+ ```
79
+
80
+ The `TimerTask` class includes the `Dereferenceable` mixin module so the result of
81
+ the last execution is always available via the `#value` method. Derefencing options
82
+ can be passed to the `TimerTask` during construction or at any later time using the
83
+ `#set_deref_options` method.
84
+
85
+ ```ruby
86
+ task = Concurrent::TimerTask.new(
87
+ dup_on_deref: true,
88
+ execution_interval: 5
89
+ ){ Time.now }
90
+
91
+ task.run!
92
+ Time.now #=> 2013-11-07 18:06:50 -0500
93
+ sleep(10)
94
+ task.value #=> 2013-11-07 18:06:55 -0500
75
95
  ```
76
96
 
77
97
  A simple example with observation:
@@ -89,29 +109,56 @@ class TaskObserver
89
109
  end
90
110
  end
91
111
 
92
- task = Concurrent::TimerTask.run!(execution_interval: 1, timeout_interval: 1){ 42 }
93
- task.runner.add_observer(TaskObserver.new)
112
+ task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ 42 }
113
+ task.add_observer(TaskObserver.new)
114
+ task.run!
94
115
 
95
116
  #=> (2013-10-13 19:08:58 -0400) Execution successfully returned 42
96
117
  #=> (2013-10-13 19:08:59 -0400) Execution successfully returned 42
97
118
  #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42
98
- task.runner.stop
119
+ task.stop
99
120
 
100
- task = Concurrent::TimerTask.run!(execution_interval: 1, timeout_interval: 1){ sleep }
101
- task.runner.add_observer(TaskObserver.new)
121
+ task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ sleep }
122
+ task.add_observer(TaskObserver.new)
123
+ task.run!
102
124
 
103
125
  #=> (2013-10-13 19:07:25 -0400) Execution timed out
104
126
  #=> (2013-10-13 19:07:27 -0400) Execution timed out
105
127
  #=> (2013-10-13 19:07:29 -0400) Execution timed out
106
- task.runner.stop
128
+ task.stop
107
129
 
108
- task = Concurrent::TimerTask.run!(execution_interval: 1){ raise StandardError }
109
- task.runner.add_observer(TaskObserver.new)
130
+ task = Concurrent::TimerTask.new(execution_interval: 1){ raise StandardError }
131
+ task.add_observer(TaskObserver.new)
132
+ task.run!
110
133
 
111
134
  #=> (2013-10-13 19:09:37 -0400) Execution failed with error StandardError
112
135
  #=> (2013-10-13 19:09:38 -0400) Execution failed with error StandardError
113
136
  #=> (2013-10-13 19:09:39 -0400) Execution failed with error StandardError
114
- task.runner.stop
137
+ task.stop
138
+ ```
139
+
140
+ In some cases it may be necessary for a `TimerTask` to affect its own execution cycle.
141
+ To facilitate this a reference to the task object is passed into the block as a block
142
+ argument every time the task is executed.
143
+
144
+ ```ruby
145
+ timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
146
+ task.execution_interval.times{ print 'Boom! ' }
147
+ print "\n"
148
+ task.execution_interval += 1
149
+ if task.execution_interval > 5
150
+ puts 'Stopping...'
151
+ task.stop
152
+ end
153
+ end
154
+
155
+ timer_task.run # blocking call - this task will stop itself
156
+ #=> Boom!
157
+ #=> Boom! Boom!
158
+ #=> Boom! Boom! Boom!
159
+ #=> Boom! Boom! Boom! Boom!
160
+ #=> Boom! Boom! Boom! Boom! Boom!
161
+ #=> Stopping...
115
162
  ```
116
163
 
117
164
  ## Copyright
@@ -155,7 +155,8 @@ module Concurrent
155
155
  end
156
156
 
157
157
  it 'creates a new thread' do
158
- Thread.should_receive(:new).with(no_args())
158
+ t = Thread.new{ nil }
159
+ Thread.should_receive(:new).with(no_args()).and_return(t)
159
160
  @thread = subject.run!
160
161
  end
161
162
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: concurrent-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerry D'Antonio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-06 00:00:00.000000000 Z
11
+ date: 2013-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -61,7 +61,7 @@ files:
61
61
  - lib/concurrent_ruby.rb
62
62
  - md/actor.md
63
63
  - md/agent.md
64
- - md/event.md
64
+ - md/dereferenceable.md
65
65
  - md/future.md
66
66
  - md/goroutine.md
67
67
  - md/obligation.md