concurrent-ruby 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +275 -275
  3. data/lib/concurrent.rb +28 -28
  4. data/lib/concurrent/agent.rb +114 -114
  5. data/lib/concurrent/cached_thread_pool.rb +131 -129
  6. data/lib/concurrent/defer.rb +65 -65
  7. data/lib/concurrent/event.rb +60 -60
  8. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  9. data/lib/concurrent/executor.rb +96 -95
  10. data/lib/concurrent/fixed_thread_pool.rb +99 -95
  11. data/lib/concurrent/functions.rb +120 -120
  12. data/lib/concurrent/future.rb +42 -42
  13. data/lib/concurrent/global_thread_pool.rb +16 -16
  14. data/lib/concurrent/goroutine.rb +29 -29
  15. data/lib/concurrent/null_thread_pool.rb +22 -22
  16. data/lib/concurrent/obligation.rb +67 -67
  17. data/lib/concurrent/promise.rb +174 -174
  18. data/lib/concurrent/reactor.rb +166 -166
  19. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  20. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  21. data/lib/concurrent/supervisor.rb +105 -100
  22. data/lib/concurrent/thread_pool.rb +76 -76
  23. data/lib/concurrent/utilities.rb +32 -32
  24. data/lib/concurrent/version.rb +3 -3
  25. data/lib/concurrent_ruby.rb +1 -1
  26. data/md/agent.md +123 -123
  27. data/md/defer.md +174 -174
  28. data/md/event.md +32 -32
  29. data/md/executor.md +187 -187
  30. data/md/future.md +83 -83
  31. data/md/goroutine.md +52 -52
  32. data/md/obligation.md +32 -32
  33. data/md/promise.md +227 -227
  34. data/md/thread_pool.md +224 -224
  35. data/spec/concurrent/agent_spec.rb +386 -386
  36. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  37. data/spec/concurrent/defer_spec.rb +195 -195
  38. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +200 -200
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  42. data/spec/concurrent/functions_spec.rb +217 -217
  43. data/spec/concurrent/future_spec.rb +108 -108
  44. data/spec/concurrent/global_thread_pool_spec.rb +38 -38
  45. data/spec/concurrent/goroutine_spec.rb +67 -67
  46. data/spec/concurrent/null_thread_pool_spec.rb +57 -54
  47. data/spec/concurrent/obligation_shared.rb +132 -132
  48. data/spec/concurrent/promise_spec.rb +312 -312
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  51. data/spec/concurrent/reactor_spec.rb +364 -364
  52. data/spec/concurrent/supervisor_spec.rb +269 -258
  53. data/spec/concurrent/thread_pool_shared.rb +204 -204
  54. data/spec/concurrent/utilities_spec.rb +74 -74
  55. data/spec/spec_helper.rb +32 -32
  56. metadata +20 -16
  57. checksums.yaml +0 -7
@@ -1,174 +1,174 @@
1
- # I Can't Think of a Movie or Music Reference for Defer
2
-
3
- In the pantheon of concurrency objects a `Defer` sits somewhere between `Future` and `Promise`.
4
- Inspired by [EventMachine's *defer* method](https://github.com/eventmachine/eventmachine/wiki/EM::Deferrable-and-EM.defer),
5
- a `Defer` can be considered a non-blocking `Future` or a simplified, non-blocking `Promise`. Defers run on the global thread pool.
6
-
7
- Unlike `Future` and `Promise` a defer is non-blocking. The deferred *operation* is performed on another
8
- thread. If the *operation* is successful an optional *callback* is called on the same thread as the *operation*.
9
- The result of the *operation* is passed to the *callbacl*. If the *operation* fails (by raising an exception)
10
- then an optional *errorback* (error callback) is called on the same thread as the *operation*. The raised
11
- exception is passed to the *errorback*. The calling thread is never aware of the result of the *operation*.
12
- This approach fits much more cleanly within an
13
- [event-driven](http://en.wikipedia.org/wiki/Event-driven_programming) application.
14
-
15
- The operation of a `Defer` can easily be simulated using either `Future` or `Promise` and traditional branching
16
- (if/then/else) logic. This approach works but it is more verbose and partitions the work across two threads.
17
- Whenever you find yourself checking the result of a `Future` or a `Promise` then branching based on the result,
18
- consider a `Defer` instead.
19
-
20
- For programmer convenience there are two syntaxes for creating and running a `Defer`. One is idiomatic of Ruby
21
- and uses chained method calls. The other is more isiomatic of [functional programming](http://en.wikipedia.org/wiki/Concurrentprogramming)
22
- and passes one or more `proc` objects as arguments. Do not mix syntaxes on a single `Defer` invocation.
23
-
24
- ## Examples
25
-
26
- A simple `Defer` using idiomatic Ruby syntax:
27
-
28
- ```ruby
29
- require 'concurrent'
30
-
31
- deferred = Concurrent::Defer.new{ puts 'w00t!' }
32
- # when using idiomatic syntax the #go method must be called
33
- deferred.go
34
- sleep(0.1)
35
-
36
- #=> 'w00t!'
37
- ```
38
-
39
- A simple `Defer` using functional programming syntax:
40
-
41
- ```ruby
42
- operation = proc{ puts 'w00t!' }
43
- Concurrent::Defer.new(operation) # NOTE: a call to #go is unnecessary
44
- sleep(0.1)
45
-
46
- #=> 'w00t!'
47
-
48
- defer(operation)
49
- sleep(0.1)
50
-
51
- #=> 'w00t!'
52
- ```
53
-
54
- Adding a *callback*:
55
-
56
- ```ruby
57
- Concurrent::Defer.new{ "Jerry D'Antonio" }.
58
- then{|result| puts "Hello, #{result}!" }.
59
- go
60
-
61
- #=> Hello, Jerry D'Antonio!
62
-
63
- operation = proc{ "Jerry D'Antonio" }
64
- callback = proc{|result| puts "Hello, #{result}!" }
65
- defer(operation, callback, nil)
66
- sleep(0.1)
67
-
68
- #=> Hello, Jerry D'Antonio!
69
- ```
70
-
71
- Adding an *errorback*:
72
-
73
- ```ruby
74
- Concurrent::Defer.new{ raise StandardError.new('Boom!') }.
75
- rescue{|ex| puts ex.message }.
76
- go
77
- sleep(0.1)
78
-
79
- #=> "Boom!"
80
-
81
- operation = proc{ raise StandardError.new('Boom!') }
82
- errorback = proc{|ex| puts ex.message }
83
- defer(operation, nil, errorback)
84
-
85
- #=> "Boom!"
86
- ```
87
-
88
- Putting it all together:
89
-
90
- ```ruby
91
- Concurrent::Defer.new{ "Jerry D'Antonio" }.
92
- then{|result| puts "Hello, #{result}!" }.
93
- rescue{|ex| puts ex.message }.
94
- go
95
-
96
- #=> Hello, Jerry D'Antonio!
97
-
98
- operation = proc{ raise StandardError.new('Boom!') }
99
- callback = proc{|result| puts result }
100
- errorback = proc{|ex| puts ex.message }
101
- defer(operation, callback, errorback)
102
- sleep(0.1)
103
-
104
- #=> "Boom!"
105
- ```
106
-
107
- Crossing the streams:
108
-
109
- ```ruby
110
- operation = proc{ true }
111
- callback = proc{|result| puts result }
112
- errorback = proc{|ex| puts ex.message }
113
-
114
- Concurrent::Defer.new(operation, nil, nil){ false }
115
- #=> ArgumentError: two operations given
116
-
117
- defer(nil, callback, errorback)
118
- # => ArgumentError: no operation given
119
-
120
- Concurrent::Defer.new.go
121
- # => ArgumentError: no operation given
122
-
123
- defer(nil, nil, nil)
124
- # => ArgumentError: no operation given
125
-
126
- Concurrent::Defer.new(operation, nil, nil).
127
- then{|result| puts result }.
128
- go
129
- #=> Concurrent::IllegalMethodCallError: the defer is already running
130
-
131
- defer(callback, nil, nil).then{|result| puts result }
132
- #=> Concurrent::IllegalMethodCallError: the defer is already running
133
-
134
- Concurrent::Defer.new{ true }.
135
- then{|result| puts "Boom!" }.
136
- then{|result| puts "Bam!" }.
137
- go
138
- #=> Concurrent::IllegalMethodCallError: a callback has already been provided
139
-
140
- Concurrent::Defer.new{ raise StandardError }.
141
- rescue{|ex| puts "Boom!" }.
142
- rescue{|ex| puts "Bam!" }.
143
- go
144
- #=> Concurrent::IllegalMethodCallError: a errorback has already been provided
145
- ```
146
-
147
- ## Copyright
148
-
149
- *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
150
- It is free software and may be redistributed under the terms specified in the LICENSE file.
151
-
152
- ## License
153
-
154
- Released under the MIT license.
155
-
156
- http://www.opensource.org/licenses/mit-license.php
157
-
158
- > Permission is hereby granted, free of charge, to any person obtaining a copy
159
- > of this software and associated documentation files (the "Software"), to deal
160
- > in the Software without restriction, including without limitation the rights
161
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
162
- > copies of the Software, and to permit persons to whom the Software is
163
- > furnished to do so, subject to the following conditions:
164
- >
165
- > The above copyright notice and this permission notice shall be included in
166
- > all copies or substantial portions of the Software.
167
- >
168
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
169
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
170
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
171
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
172
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
173
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
174
- > THE SOFTWARE.
1
+ # I Can't Think of a Movie or Music Reference for Defer
2
+
3
+ In the pantheon of concurrency objects a `Defer` sits somewhere between `Future` and `Promise`.
4
+ Inspired by [EventMachine's *defer* method](https://github.com/eventmachine/eventmachine/wiki/EM::Deferrable-and-EM.defer),
5
+ a `Defer` can be considered a non-blocking `Future` or a simplified, non-blocking `Promise`. Defers run on the global thread pool.
6
+
7
+ Unlike `Future` and `Promise` a defer is non-blocking. The deferred *operation* is performed on another
8
+ thread. If the *operation* is successful an optional *callback* is called on the same thread as the *operation*.
9
+ The result of the *operation* is passed to the *callbacl*. If the *operation* fails (by raising an exception)
10
+ then an optional *errorback* (error callback) is called on the same thread as the *operation*. The raised
11
+ exception is passed to the *errorback*. The calling thread is never aware of the result of the *operation*.
12
+ This approach fits much more cleanly within an
13
+ [event-driven](http://en.wikipedia.org/wiki/Event-driven_programming) application.
14
+
15
+ The operation of a `Defer` can easily be simulated using either `Future` or `Promise` and traditional branching
16
+ (if/then/else) logic. This approach works but it is more verbose and partitions the work across two threads.
17
+ Whenever you find yourself checking the result of a `Future` or a `Promise` then branching based on the result,
18
+ consider a `Defer` instead.
19
+
20
+ For programmer convenience there are two syntaxes for creating and running a `Defer`. One is idiomatic of Ruby
21
+ and uses chained method calls. The other is more isiomatic of [functional programming](http://en.wikipedia.org/wiki/Concurrentprogramming)
22
+ and passes one or more `proc` objects as arguments. Do not mix syntaxes on a single `Defer` invocation.
23
+
24
+ ## Examples
25
+
26
+ A simple `Defer` using idiomatic Ruby syntax:
27
+
28
+ ```ruby
29
+ require 'concurrent'
30
+
31
+ deferred = Concurrent::Defer.new{ puts 'w00t!' }
32
+ # when using idiomatic syntax the #go method must be called
33
+ deferred.go
34
+ sleep(0.1)
35
+
36
+ #=> 'w00t!'
37
+ ```
38
+
39
+ A simple `Defer` using functional programming syntax:
40
+
41
+ ```ruby
42
+ operation = proc{ puts 'w00t!' }
43
+ Concurrent::Defer.new(operation) # NOTE: a call to #go is unnecessary
44
+ sleep(0.1)
45
+
46
+ #=> 'w00t!'
47
+
48
+ defer(operation)
49
+ sleep(0.1)
50
+
51
+ #=> 'w00t!'
52
+ ```
53
+
54
+ Adding a *callback*:
55
+
56
+ ```ruby
57
+ Concurrent::Defer.new{ "Jerry D'Antonio" }.
58
+ then{|result| puts "Hello, #{result}!" }.
59
+ go
60
+
61
+ #=> Hello, Jerry D'Antonio!
62
+
63
+ operation = proc{ "Jerry D'Antonio" }
64
+ callback = proc{|result| puts "Hello, #{result}!" }
65
+ defer(operation, callback, nil)
66
+ sleep(0.1)
67
+
68
+ #=> Hello, Jerry D'Antonio!
69
+ ```
70
+
71
+ Adding an *errorback*:
72
+
73
+ ```ruby
74
+ Concurrent::Defer.new{ raise StandardError.new('Boom!') }.
75
+ rescue{|ex| puts ex.message }.
76
+ go
77
+ sleep(0.1)
78
+
79
+ #=> "Boom!"
80
+
81
+ operation = proc{ raise StandardError.new('Boom!') }
82
+ errorback = proc{|ex| puts ex.message }
83
+ defer(operation, nil, errorback)
84
+
85
+ #=> "Boom!"
86
+ ```
87
+
88
+ Putting it all together:
89
+
90
+ ```ruby
91
+ Concurrent::Defer.new{ "Jerry D'Antonio" }.
92
+ then{|result| puts "Hello, #{result}!" }.
93
+ rescue{|ex| puts ex.message }.
94
+ go
95
+
96
+ #=> Hello, Jerry D'Antonio!
97
+
98
+ operation = proc{ raise StandardError.new('Boom!') }
99
+ callback = proc{|result| puts result }
100
+ errorback = proc{|ex| puts ex.message }
101
+ defer(operation, callback, errorback)
102
+ sleep(0.1)
103
+
104
+ #=> "Boom!"
105
+ ```
106
+
107
+ Crossing the streams:
108
+
109
+ ```ruby
110
+ operation = proc{ true }
111
+ callback = proc{|result| puts result }
112
+ errorback = proc{|ex| puts ex.message }
113
+
114
+ Concurrent::Defer.new(operation, nil, nil){ false }
115
+ #=> ArgumentError: two operations given
116
+
117
+ defer(nil, callback, errorback)
118
+ # => ArgumentError: no operation given
119
+
120
+ Concurrent::Defer.new.go
121
+ # => ArgumentError: no operation given
122
+
123
+ defer(nil, nil, nil)
124
+ # => ArgumentError: no operation given
125
+
126
+ Concurrent::Defer.new(operation, nil, nil).
127
+ then{|result| puts result }.
128
+ go
129
+ #=> Concurrent::IllegalMethodCallError: the defer is already running
130
+
131
+ defer(callback, nil, nil).then{|result| puts result }
132
+ #=> Concurrent::IllegalMethodCallError: the defer is already running
133
+
134
+ Concurrent::Defer.new{ true }.
135
+ then{|result| puts "Boom!" }.
136
+ then{|result| puts "Bam!" }.
137
+ go
138
+ #=> Concurrent::IllegalMethodCallError: a callback has already been provided
139
+
140
+ Concurrent::Defer.new{ raise StandardError }.
141
+ rescue{|ex| puts "Boom!" }.
142
+ rescue{|ex| puts "Bam!" }.
143
+ go
144
+ #=> Concurrent::IllegalMethodCallError: a errorback has already been provided
145
+ ```
146
+
147
+ ## Copyright
148
+
149
+ *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
150
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
151
+
152
+ ## License
153
+
154
+ Released under the MIT license.
155
+
156
+ http://www.opensource.org/licenses/mit-license.php
157
+
158
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
159
+ > of this software and associated documentation files (the "Software"), to deal
160
+ > in the Software without restriction, including without limitation the rights
161
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
162
+ > copies of the Software, and to permit persons to whom the Software is
163
+ > furnished to do so, subject to the following conditions:
164
+ >
165
+ > The above copyright notice and this permission notice shall be included in
166
+ > all copies or substantial portions of the Software.
167
+ >
168
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
169
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
170
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
171
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
172
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
173
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
174
+ > THE SOFTWARE.
@@ -1,32 +1,32 @@
1
- # Event
2
-
3
- TBD...
4
-
5
- ## Copyright
6
-
7
- *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
- It is free software and may be redistributed under the terms specified in the LICENSE file.
9
-
10
- ## License
11
-
12
- Released under the MIT license.
13
-
14
- http://www.opensource.org/licenses/mit-license.php
15
-
16
- > Permission is hereby granted, free of charge, to any person obtaining a copy
17
- > of this software and associated documentation files (the "Software"), to deal
18
- > in the Software without restriction, including without limitation the rights
19
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
- > copies of the Software, and to permit persons to whom the Software is
21
- > furnished to do so, subject to the following conditions:
22
- >
23
- > The above copyright notice and this permission notice shall be included in
24
- > all copies or substantial portions of the Software.
25
- >
26
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
- > THE SOFTWARE.
1
+ # Event
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -1,187 +1,187 @@
1
- # Being of Sound Mind
2
-
3
- A very common currency pattern is to run a thread that performs a task at regular
4
- intervals. The thread that peforms the task sleeps for the given interval then
5
- waked up and performs the task. Later, rinse, repeat... This pattern causes two
6
- problems. First, it is difficult to test the business logic of the task becuse the
7
- task itself is tightly couple with the threading. Second, an exception in the task
8
- can cause the entire thread to abend. In a long-running application where the task
9
- thread is intended to run for days/weeks/years a crashed task thread can pose a real
10
- problem. The `Executor` class alleviates both problems.
11
-
12
- When an executor is launched it starts a thread for monitoring the execution interval.
13
- The executor thread does not perform the task, however. Instead, the executor
14
- launches the task on a separat thread. The advantage of this approach is that if
15
- the task crashes it will only kill the task thread, not the executor thread. The
16
- executor thread can then log the success or failure of the task. The executor
17
- can even be configured with a timeout value allowing it to kill a task that runs
18
- to long and then log the error.
19
-
20
- One other advantage of the `Executor` class is that it forces the bsiness logic to
21
- be completely decoupled from the threading logic. The business logic can be tested
22
- separately then passed to the an executor for scheduling and running.
23
-
24
- Unlike some of the others concurrency objects in the library, executors do not
25
- run on the global. In my experience the types of tasks that will benefit from
26
- the `Executor` class tend to also be long running. For this reason they get their
27
- own thread every time the task is executed.
28
-
29
- ## ExecutionContext
30
-
31
- When an executor is run the return value is an `ExecutionContext` object. An
32
- `ExecutionContext` object has several attribute readers (`#name`, `#execution_interval`,
33
- and `#timeout_interval`). It also provides several `Thread` operations which can
34
- be performed against the internal thread. These include `#status`, `#join`, and
35
- `kill`.
36
-
37
- ## Custom Logging
38
-
39
- An executor will write a log message to standard out at the completion of every
40
- task run. When the task is successful the log message is tagged at the `:info`
41
- level. When the task times out the log message is tagged at the `warn` level.
42
- When the task fails tocomplete (most likely because of exception) the log
43
- message is tagged at the `error` level.
44
-
45
- The default logging behavior can be overridden by passing a `proc` to the executor
46
- on creation. The block will be passes three (3) arguments every time it is run:
47
- executor `name`, log `level`, and the log `msg` (message). The `proc` can do
48
- whatever it wanst with these arguments.
49
-
50
- ## Examples
51
-
52
- A basic example:
53
-
54
- ```ruby
55
- require 'concurrent'
56
-
57
- ec = Concurrent::Executor.run('Foo'){ puts 'Boom!' }
58
-
59
- ec.name #=> "Foo"
60
- ec.execution_interval #=> 60 == Concurrent::Executor::EXECUTION_INTERVAL
61
- ec.timeout_interval #=> 30 == Concurrent::Executor::TIMEOUT_INTERVAL
62
- ec.status #=> "sleep"
63
-
64
- # wait 60 seconds...
65
- #=> 'Boom!'
66
- #=> ' INFO (2013-08-02 23:20:15) Foo: execution completed successfully'
67
-
68
- ec.kill #=> true
69
- ```
70
-
71
- Both the execution_interval and the timeout_interval can be configured:
72
-
73
- ```ruby
74
- ec = Concurrent::Executor.run('Foo', execution_interval: 5, timeout_interval: 5) do
75
- puts 'Boom!'
76
- end
77
-
78
- ec.execution_interval #=> 5
79
- ec.timeout_interval #=> 5
80
- ```
81
-
82
- By default an `Executor` will wait for `:execution_interval` seconds before running the block.
83
- To run the block immediately set the `:run_now` option to `true`:
84
-
85
- ```ruby
86
- ec = Concurrent::Executor.run('Foo', run_now: true){ puts 'Boom!' }
87
- #=> 'Boom!''
88
- #=> ' INFO (2013-08-15 21:35:14) Foo: execution completed successfully'
89
- ec.status #=> "sleep"
90
- >>
91
- ```
92
-
93
- A simple example with timeout and task exception:
94
-
95
- ```ruby
96
- ec = Concurrent::Executor.run('Foo', execution_interval: 1, timeout_interval: 1){ sleep(10) }
97
-
98
- #=> WARN (2013-08-02 23:45:26) Foo: execution timed out after 1 seconds
99
- #=> WARN (2013-08-02 23:45:28) Foo: execution timed out after 1 seconds
100
- #=> WARN (2013-08-02 23:45:30) Foo: execution timed out after 1 seconds
101
-
102
- ec = Concurrent::Executor.run('Foo', execution_interval: 1){ raise StandardError }
103
-
104
- #=> ERROR (2013-08-02 23:47:31) Foo: execution failed with error 'StandardError'
105
- #=> ERROR (2013-08-02 23:47:32) Foo: execution failed with error 'StandardError'
106
- #=> ERROR (2013-08-02 23:47:33) Foo: execution failed with error 'StandardError'
107
- ```
108
-
109
- For custom logging, simply provide a `proc` when creating an executor:
110
-
111
- ```ruby
112
- file_logger = proc do |name, level, msg|
113
- open('executor.log', 'a') do |f|
114
- f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
115
- end
116
- end
117
-
118
- ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
119
- puts 'Boom!'
120
- end
121
-
122
- # the log file contains
123
- # INFO (2013-08-02 23:30:19) Foo: execution completed successfully
124
- # INFO (2013-08-02 23:30:24) Foo: execution completed successfully
125
- # INFO (2013-08-02 23:30:29) Foo: execution completed successfully
126
- # INFO (2013-08-02 23:30:34) Foo: execution completed successfully
127
- # INFO (2013-08-02 23:30:39) Foo: execution completed successfully
128
- # INFO (2013-08-02 23:30:44) Foo: execution completed successfully
129
- ```
130
-
131
- It is also possible to access the default stdout logger from within a logger `proc`:
132
-
133
- ```ruby
134
- file_logger = proc do |name, level, msg|
135
- Concurrent::Executor::STDOUT_LOGGER.call(name, level, msg)
136
- open('executor.log', 'a') do |f|
137
- f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
138
- end
139
- end
140
-
141
- ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
142
- puts 'Boom!'
143
- end
144
-
145
- # wait...
146
-
147
- #=> Boom!
148
- #=> INFO (2013-08-02 23:40:49) Foo: execution completed successfully
149
- #=> Boom!
150
- #=> INFO (2013-08-02 23:40:54) Foo: execution completed successfully
151
- #=> Boom!
152
- #=> INFO (2013-08-02 23:40:59) Foo: execution completed successfully
153
-
154
- # and the log file contains
155
- # INFO (2013-08-02 23:39:52) Foo: execution completed successfully
156
- # INFO (2013-08-02 23:39:57) Foo: execution completed successfully
157
- # INFO (2013-08-02 23:40:49) Foo: execution completed successfully
158
- ```
159
-
160
- ## Copyright
161
-
162
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
163
- It is free software and may be redistributed under the terms specified in the LICENSE file.
164
-
165
- ## License
166
-
167
- Released under the MIT license.
168
-
169
- http://www.opensource.org/licenses/mit-license.php
170
-
171
- > Permission is hereby granted, free of charge, to any person obtaining a copy
172
- > of this software and associated documentation files (the "Software"), to deal
173
- > in the Software without restriction, including without limitation the rights
174
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
175
- > copies of the Software, and to permit persons to whom the Software is
176
- > furnished to do so, subject to the following conditions:
177
- >
178
- > The above copyright notice and this permission notice shall be included in
179
- > all copies or substantial portions of the Software.
180
- >
181
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
182
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
183
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
184
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
185
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
186
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
187
- > THE SOFTWARE.
1
+ # Being of Sound Mind
2
+
3
+ A very common currency pattern is to run a thread that performs a task at regular
4
+ intervals. The thread that peforms the task sleeps for the given interval then
5
+ waked up and performs the task. Later, rinse, repeat... This pattern causes two
6
+ problems. First, it is difficult to test the business logic of the task becuse the
7
+ task itself is tightly couple with the threading. Second, an exception in the task
8
+ can cause the entire thread to abend. In a long-running application where the task
9
+ thread is intended to run for days/weeks/years a crashed task thread can pose a real
10
+ problem. The `Executor` class alleviates both problems.
11
+
12
+ When an executor is launched it starts a thread for monitoring the execution interval.
13
+ The executor thread does not perform the task, however. Instead, the executor
14
+ launches the task on a separat thread. The advantage of this approach is that if
15
+ the task crashes it will only kill the task thread, not the executor thread. The
16
+ executor thread can then log the success or failure of the task. The executor
17
+ can even be configured with a timeout value allowing it to kill a task that runs
18
+ to long and then log the error.
19
+
20
+ One other advantage of the `Executor` class is that it forces the bsiness logic to
21
+ be completely decoupled from the threading logic. The business logic can be tested
22
+ separately then passed to the an executor for scheduling and running.
23
+
24
+ Unlike some of the others concurrency objects in the library, executors do not
25
+ run on the global. In my experience the types of tasks that will benefit from
26
+ the `Executor` class tend to also be long running. For this reason they get their
27
+ own thread every time the task is executed.
28
+
29
+ ## ExecutionContext
30
+
31
+ When an executor is run the return value is an `ExecutionContext` object. An
32
+ `ExecutionContext` object has several attribute readers (`#name`, `#execution_interval`,
33
+ and `#timeout_interval`). It also provides several `Thread` operations which can
34
+ be performed against the internal thread. These include `#status`, `#join`, and
35
+ `kill`.
36
+
37
+ ## Custom Logging
38
+
39
+ An executor will write a log message to standard out at the completion of every
40
+ task run. When the task is successful the log message is tagged at the `:info`
41
+ level. When the task times out the log message is tagged at the `warn` level.
42
+ When the task fails tocomplete (most likely because of exception) the log
43
+ message is tagged at the `error` level.
44
+
45
+ The default logging behavior can be overridden by passing a `proc` to the executor
46
+ on creation. The block will be passes three (3) arguments every time it is run:
47
+ executor `name`, log `level`, and the log `msg` (message). The `proc` can do
48
+ whatever it wanst with these arguments.
49
+
50
+ ## Examples
51
+
52
+ A basic example:
53
+
54
+ ```ruby
55
+ require 'concurrent'
56
+
57
+ ec = Concurrent::Executor.run('Foo'){ puts 'Boom!' }
58
+
59
+ ec.name #=> "Foo"
60
+ ec.execution_interval #=> 60 == Concurrent::Executor::EXECUTION_INTERVAL
61
+ ec.timeout_interval #=> 30 == Concurrent::Executor::TIMEOUT_INTERVAL
62
+ ec.status #=> "sleep"
63
+
64
+ # wait 60 seconds...
65
+ #=> 'Boom!'
66
+ #=> ' INFO (2013-08-02 23:20:15) Foo: execution completed successfully'
67
+
68
+ ec.kill #=> true
69
+ ```
70
+
71
+ Both the execution_interval and the timeout_interval can be configured:
72
+
73
+ ```ruby
74
+ ec = Concurrent::Executor.run('Foo', execution_interval: 5, timeout_interval: 5) do
75
+ puts 'Boom!'
76
+ end
77
+
78
+ ec.execution_interval #=> 5
79
+ ec.timeout_interval #=> 5
80
+ ```
81
+
82
+ By default an `Executor` will wait for `:execution_interval` seconds before running the block.
83
+ To run the block immediately set the `:run_now` option to `true`:
84
+
85
+ ```ruby
86
+ ec = Concurrent::Executor.run('Foo', run_now: true){ puts 'Boom!' }
87
+ #=> 'Boom!''
88
+ #=> ' INFO (2013-08-15 21:35:14) Foo: execution completed successfully'
89
+ ec.status #=> "sleep"
90
+ >>
91
+ ```
92
+
93
+ A simple example with timeout and task exception:
94
+
95
+ ```ruby
96
+ ec = Concurrent::Executor.run('Foo', execution_interval: 1, timeout_interval: 1){ sleep(10) }
97
+
98
+ #=> WARN (2013-08-02 23:45:26) Foo: execution timed out after 1 seconds
99
+ #=> WARN (2013-08-02 23:45:28) Foo: execution timed out after 1 seconds
100
+ #=> WARN (2013-08-02 23:45:30) Foo: execution timed out after 1 seconds
101
+
102
+ ec = Concurrent::Executor.run('Foo', execution_interval: 1){ raise StandardError }
103
+
104
+ #=> ERROR (2013-08-02 23:47:31) Foo: execution failed with error 'StandardError'
105
+ #=> ERROR (2013-08-02 23:47:32) Foo: execution failed with error 'StandardError'
106
+ #=> ERROR (2013-08-02 23:47:33) Foo: execution failed with error 'StandardError'
107
+ ```
108
+
109
+ For custom logging, simply provide a `proc` when creating an executor:
110
+
111
+ ```ruby
112
+ file_logger = proc do |name, level, msg|
113
+ open('executor.log', 'a') do |f|
114
+ f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
115
+ end
116
+ end
117
+
118
+ ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
119
+ puts 'Boom!'
120
+ end
121
+
122
+ # the log file contains
123
+ # INFO (2013-08-02 23:30:19) Foo: execution completed successfully
124
+ # INFO (2013-08-02 23:30:24) Foo: execution completed successfully
125
+ # INFO (2013-08-02 23:30:29) Foo: execution completed successfully
126
+ # INFO (2013-08-02 23:30:34) Foo: execution completed successfully
127
+ # INFO (2013-08-02 23:30:39) Foo: execution completed successfully
128
+ # INFO (2013-08-02 23:30:44) Foo: execution completed successfully
129
+ ```
130
+
131
+ It is also possible to access the default stdout logger from within a logger `proc`:
132
+
133
+ ```ruby
134
+ file_logger = proc do |name, level, msg|
135
+ Concurrent::Executor::STDOUT_LOGGER.call(name, level, msg)
136
+ open('executor.log', 'a') do |f|
137
+ f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
138
+ end
139
+ end
140
+
141
+ ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
142
+ puts 'Boom!'
143
+ end
144
+
145
+ # wait...
146
+
147
+ #=> Boom!
148
+ #=> INFO (2013-08-02 23:40:49) Foo: execution completed successfully
149
+ #=> Boom!
150
+ #=> INFO (2013-08-02 23:40:54) Foo: execution completed successfully
151
+ #=> Boom!
152
+ #=> INFO (2013-08-02 23:40:59) Foo: execution completed successfully
153
+
154
+ # and the log file contains
155
+ # INFO (2013-08-02 23:39:52) Foo: execution completed successfully
156
+ # INFO (2013-08-02 23:39:57) Foo: execution completed successfully
157
+ # INFO (2013-08-02 23:40:49) Foo: execution completed successfully
158
+ ```
159
+
160
+ ## Copyright
161
+
162
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
163
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
164
+
165
+ ## License
166
+
167
+ Released under the MIT license.
168
+
169
+ http://www.opensource.org/licenses/mit-license.php
170
+
171
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
172
+ > of this software and associated documentation files (the "Software"), to deal
173
+ > in the Software without restriction, including without limitation the rights
174
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
175
+ > copies of the Software, and to permit persons to whom the Software is
176
+ > furnished to do so, subject to the following conditions:
177
+ >
178
+ > The above copyright notice and this permission notice shall be included in
179
+ > all copies or substantial portions of the Software.
180
+ >
181
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
182
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
183
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
184
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
185
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
186
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
187
+ > THE SOFTWARE.