concurrent-ruby 0.3.1 → 0.3.2
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 +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 [](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [](https://coveralls.io/r/jdantonio/concurrent-ruby) [](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
|