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 +4 -4
- data/README.md +1 -4
- data/lib/concurrent/actor.rb +1 -0
- data/lib/concurrent/runnable.rb +4 -3
- data/lib/concurrent/version.rb +1 -1
- data/md/actor.md +210 -15
- data/md/{event.md → dereferenceable.md} +21 -2
- data/md/scheduled_task.md +124 -2
- data/md/timer_task.md +68 -21
- data/spec/concurrent/runnable_spec.rb +2 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de0e0da91bc658bb4ed962ec88d82b3997b95a09
|
4
|
+
data.tar.gz: 35d3a5d3111ff16f3a7782f38736e84b25007fc1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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,
|
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
|
|
data/lib/concurrent/actor.rb
CHANGED
data/lib/concurrent/runnable.rb
CHANGED
@@ -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
|
-
|
39
|
+
end
|
40
|
+
thread.join(0.1) # let the thread start
|
40
41
|
return thread
|
41
42
|
end
|
42
43
|
|
data/lib/concurrent/version.rb
CHANGED
data/md/actor.md
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
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.
|
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
|
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(
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
#
|
1
|
+
# Dereferenceable
|
2
2
|
|
3
|
-
|
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
|
|
data/md/scheduled_task.md
CHANGED
@@ -1,8 +1,130 @@
|
|
1
1
|
# I'm late! For a very important date!
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
|
data/md/timer_task.md
CHANGED
@@ -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
|
-
|
47
|
+
task = Concurrent::TimerTask.new{ puts 'Boom!' }
|
48
|
+
task.run!
|
45
49
|
|
46
|
-
|
47
|
-
|
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
|
-
|
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
|
-
|
62
|
+
task = Concurrent::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
|
60
63
|
puts 'Boom!'
|
61
64
|
end
|
62
65
|
|
63
|
-
|
64
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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.
|
93
|
-
task.
|
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.
|
119
|
+
task.stop
|
99
120
|
|
100
|
-
task = Concurrent::TimerTask.
|
101
|
-
task.
|
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.
|
128
|
+
task.stop
|
107
129
|
|
108
|
-
task = Concurrent::TimerTask.
|
109
|
-
task.
|
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.
|
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
|
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.
|
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-
|
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/
|
64
|
+
- md/dereferenceable.md
|
65
65
|
- md/future.md
|
66
66
|
- md/goroutine.md
|
67
67
|
- md/obligation.md
|