concurrent-ruby 0.2.1 → 0.2.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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -21
  3. data/README.md +276 -275
  4. data/lib/concurrent.rb +28 -28
  5. data/lib/concurrent/agent.rb +114 -114
  6. data/lib/concurrent/cached_thread_pool.rb +131 -131
  7. data/lib/concurrent/defer.rb +65 -65
  8. data/lib/concurrent/event.rb +60 -60
  9. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  10. data/lib/concurrent/executor.rb +96 -96
  11. data/lib/concurrent/fixed_thread_pool.rb +99 -99
  12. data/lib/concurrent/functions.rb +120 -120
  13. data/lib/concurrent/future.rb +42 -42
  14. data/lib/concurrent/global_thread_pool.rb +24 -16
  15. data/lib/concurrent/goroutine.rb +29 -29
  16. data/lib/concurrent/null_thread_pool.rb +22 -22
  17. data/lib/concurrent/obligation.rb +67 -67
  18. data/lib/concurrent/promise.rb +174 -174
  19. data/lib/concurrent/reactor.rb +166 -166
  20. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  21. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  22. data/lib/concurrent/supervisor.rb +105 -105
  23. data/lib/concurrent/thread_pool.rb +76 -76
  24. data/lib/concurrent/utilities.rb +32 -32
  25. data/lib/concurrent/version.rb +3 -3
  26. data/lib/concurrent_ruby.rb +1 -1
  27. data/md/agent.md +123 -123
  28. data/md/defer.md +174 -174
  29. data/md/event.md +32 -32
  30. data/md/executor.md +187 -187
  31. data/md/future.md +83 -83
  32. data/md/goroutine.md +52 -52
  33. data/md/obligation.md +32 -32
  34. data/md/promise.md +227 -227
  35. data/md/thread_pool.md +224 -224
  36. data/spec/concurrent/agent_spec.rb +390 -386
  37. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  38. data/spec/concurrent/defer_spec.rb +199 -195
  39. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  40. data/spec/concurrent/event_spec.rb +134 -134
  41. data/spec/concurrent/executor_spec.rb +200 -200
  42. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  43. data/spec/concurrent/functions_spec.rb +217 -217
  44. data/spec/concurrent/future_spec.rb +112 -108
  45. data/spec/concurrent/global_thread_pool_spec.rb +11 -38
  46. data/spec/concurrent/goroutine_spec.rb +67 -67
  47. data/spec/concurrent/null_thread_pool_spec.rb +57 -57
  48. data/spec/concurrent/obligation_shared.rb +132 -132
  49. data/spec/concurrent/promise_spec.rb +316 -312
  50. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  51. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  52. data/spec/concurrent/reactor_spec.rb +364 -364
  53. data/spec/concurrent/supervisor_spec.rb +269 -269
  54. data/spec/concurrent/thread_pool_shared.rb +204 -204
  55. data/spec/concurrent/uses_global_thread_pool_shared.rb +64 -0
  56. data/spec/concurrent/utilities_spec.rb +74 -74
  57. data/spec/spec_helper.rb +32 -32
  58. metadata +17 -19
@@ -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.