concurrent-ruby 0.1.1.pre.3 → 0.1.1.pre.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -21
  3. data/README.md +275 -279
  4. data/lib/concurrent.rb +27 -28
  5. data/lib/concurrent/agent.rb +114 -108
  6. data/lib/concurrent/cached_thread_pool.rb +129 -130
  7. data/lib/concurrent/defer.rb +65 -67
  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 +93 -95
  11. data/lib/concurrent/fixed_thread_pool.rb +95 -89
  12. data/lib/concurrent/functions.rb +120 -120
  13. data/lib/concurrent/future.rb +42 -47
  14. data/lib/concurrent/global_thread_pool.rb +16 -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 -166
  19. data/lib/concurrent/reactor.rb +161 -162
  20. data/lib/concurrent/reactor/drb_async_demux.rb +74 -74
  21. data/lib/concurrent/reactor/tcp_sync_demux.rb +98 -98
  22. data/lib/concurrent/thread_pool.rb +76 -69
  23. data/lib/concurrent/utilities.rb +32 -34
  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 +176 -176
  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 -380
  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 -253
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +184 -184
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -84
  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 +54 -54
  47. data/spec/concurrent/obligation_shared.rb +135 -121
  48. data/spec/concurrent/promise_spec.rb +312 -305
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +12 -12
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +12 -12
  51. data/spec/concurrent/reactor_spec.rb +351 -10
  52. data/spec/concurrent/thread_pool_shared.rb +209 -210
  53. data/spec/concurrent/utilities_spec.rb +74 -74
  54. data/spec/spec_helper.rb +44 -30
  55. metadata +11 -22
  56. data/lib/concurrent/smart_mutex.rb +0 -66
  57. data/spec/concurrent/smart_mutex_spec.rb +0 -234
data/md/defer.md CHANGED
@@ -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.
data/md/event.md CHANGED
@@ -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.
data/md/executor.md CHANGED
@@ -1,176 +1,176 @@
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
- A simple example with timeout and task exception:
83
-
84
- ```ruby
85
- ec = Concurrent::Executor.run('Foo', execution_interval: 1, timeout_interval: 1){ sleep(10) }
86
-
87
- #=> WARN (2013-08-02 23:45:26) Foo: execution timed out after 1 seconds
88
- #=> WARN (2013-08-02 23:45:28) Foo: execution timed out after 1 seconds
89
- #=> WARN (2013-08-02 23:45:30) Foo: execution timed out after 1 seconds
90
-
91
- ec = Concurrent::Executor.run('Foo', execution_interval: 1){ raise StandardError }
92
-
93
- #=> ERROR (2013-08-02 23:47:31) Foo: execution failed with error 'StandardError'
94
- #=> ERROR (2013-08-02 23:47:32) Foo: execution failed with error 'StandardError'
95
- #=> ERROR (2013-08-02 23:47:33) Foo: execution failed with error 'StandardError'
96
- ```
97
-
98
- For custom logging, simply provide a `proc` when creating an executor:
99
-
100
- ```ruby
101
- file_logger = proc do |name, level, msg|
102
- open('executor.log', 'a') do |f|
103
- f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
104
- end
105
- end
106
-
107
- ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
108
- puts 'Boom!'
109
- end
110
-
111
- # the log file contains
112
- # INFO (2013-08-02 23:30:19) Foo: execution completed successfully
113
- # INFO (2013-08-02 23:30:24) Foo: execution completed successfully
114
- # INFO (2013-08-02 23:30:29) Foo: execution completed successfully
115
- # INFO (2013-08-02 23:30:34) Foo: execution completed successfully
116
- # INFO (2013-08-02 23:30:39) Foo: execution completed successfully
117
- # INFO (2013-08-02 23:30:44) Foo: execution completed successfully
118
- ```
119
-
120
- It is also possible to access the default stdout logger from within a logger `proc`:
121
-
122
- ```ruby
123
- file_logger = proc do |name, level, msg|
124
- Concurrent::Executor::STDOUT_LOGGER.call(name, level, msg)
125
- open('executor.log', 'a') do |f|
126
- f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
127
- end
128
- end
129
-
130
- ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
131
- puts 'Boom!'
132
- end
133
-
134
- # wait...
135
-
136
- #=> Boom!
137
- #=> INFO (2013-08-02 23:40:49) Foo: execution completed successfully
138
- #=> Boom!
139
- #=> INFO (2013-08-02 23:40:54) Foo: execution completed successfully
140
- #=> Boom!
141
- #=> INFO (2013-08-02 23:40:59) Foo: execution completed successfully
142
-
143
- # and the log file contains
144
- # INFO (2013-08-02 23:39:52) Foo: execution completed successfully
145
- # INFO (2013-08-02 23:39:57) Foo: execution completed successfully
146
- # INFO (2013-08-02 23:40:49) Foo: execution completed successfully
147
- ```
148
-
149
- ## Copyright
150
-
151
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
152
- It is free software and may be redistributed under the terms specified in the LICENSE file.
153
-
154
- ## License
155
-
156
- Released under the MIT license.
157
-
158
- http://www.opensource.org/licenses/mit-license.php
159
-
160
- > Permission is hereby granted, free of charge, to any person obtaining a copy
161
- > of this software and associated documentation files (the "Software"), to deal
162
- > in the Software without restriction, including without limitation the rights
163
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
164
- > copies of the Software, and to permit persons to whom the Software is
165
- > furnished to do so, subject to the following conditions:
166
- >
167
- > The above copyright notice and this permission notice shall be included in
168
- > all copies or substantial portions of the Software.
169
- >
170
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
171
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
172
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
173
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
174
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
175
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
176
- > 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
+ A simple example with timeout and task exception:
83
+
84
+ ```ruby
85
+ ec = Concurrent::Executor.run('Foo', execution_interval: 1, timeout_interval: 1){ sleep(10) }
86
+
87
+ #=> WARN (2013-08-02 23:45:26) Foo: execution timed out after 1 seconds
88
+ #=> WARN (2013-08-02 23:45:28) Foo: execution timed out after 1 seconds
89
+ #=> WARN (2013-08-02 23:45:30) Foo: execution timed out after 1 seconds
90
+
91
+ ec = Concurrent::Executor.run('Foo', execution_interval: 1){ raise StandardError }
92
+
93
+ #=> ERROR (2013-08-02 23:47:31) Foo: execution failed with error 'StandardError'
94
+ #=> ERROR (2013-08-02 23:47:32) Foo: execution failed with error 'StandardError'
95
+ #=> ERROR (2013-08-02 23:47:33) Foo: execution failed with error 'StandardError'
96
+ ```
97
+
98
+ For custom logging, simply provide a `proc` when creating an executor:
99
+
100
+ ```ruby
101
+ file_logger = proc do |name, level, msg|
102
+ open('executor.log', 'a') do |f|
103
+ f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
104
+ end
105
+ end
106
+
107
+ ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
108
+ puts 'Boom!'
109
+ end
110
+
111
+ # the log file contains
112
+ # INFO (2013-08-02 23:30:19) Foo: execution completed successfully
113
+ # INFO (2013-08-02 23:30:24) Foo: execution completed successfully
114
+ # INFO (2013-08-02 23:30:29) Foo: execution completed successfully
115
+ # INFO (2013-08-02 23:30:34) Foo: execution completed successfully
116
+ # INFO (2013-08-02 23:30:39) Foo: execution completed successfully
117
+ # INFO (2013-08-02 23:30:44) Foo: execution completed successfully
118
+ ```
119
+
120
+ It is also possible to access the default stdout logger from within a logger `proc`:
121
+
122
+ ```ruby
123
+ file_logger = proc do |name, level, msg|
124
+ Concurrent::Executor::STDOUT_LOGGER.call(name, level, msg)
125
+ open('executor.log', 'a') do |f|
126
+ f << ("%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg])
127
+ end
128
+ end
129
+
130
+ ec = Concurrent::Executor.run('Foo', execution_interval: 5, logger: file_logger) do
131
+ puts 'Boom!'
132
+ end
133
+
134
+ # wait...
135
+
136
+ #=> Boom!
137
+ #=> INFO (2013-08-02 23:40:49) Foo: execution completed successfully
138
+ #=> Boom!
139
+ #=> INFO (2013-08-02 23:40:54) Foo: execution completed successfully
140
+ #=> Boom!
141
+ #=> INFO (2013-08-02 23:40:59) Foo: execution completed successfully
142
+
143
+ # and the log file contains
144
+ # INFO (2013-08-02 23:39:52) Foo: execution completed successfully
145
+ # INFO (2013-08-02 23:39:57) Foo: execution completed successfully
146
+ # INFO (2013-08-02 23:40:49) Foo: execution completed successfully
147
+ ```
148
+
149
+ ## Copyright
150
+
151
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
152
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
153
+
154
+ ## License
155
+
156
+ Released under the MIT license.
157
+
158
+ http://www.opensource.org/licenses/mit-license.php
159
+
160
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
161
+ > of this software and associated documentation files (the "Software"), to deal
162
+ > in the Software without restriction, including without limitation the rights
163
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
164
+ > copies of the Software, and to permit persons to whom the Software is
165
+ > furnished to do so, subject to the following conditions:
166
+ >
167
+ > The above copyright notice and this permission notice shall be included in
168
+ > all copies or substantial portions of the Software.
169
+ >
170
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
171
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
172
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
173
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
174
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
175
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
176
+ > THE SOFTWARE.