concurrent-ruby 0.1.1.pre.5 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -48
  3. data/lib/concurrent.rb +0 -6
  4. data/lib/concurrent/agent.rb +40 -19
  5. data/lib/concurrent/cached_thread_pool.rb +11 -10
  6. data/lib/concurrent/defer.rb +12 -8
  7. data/lib/concurrent/fixed_thread_pool.rb +6 -12
  8. data/lib/concurrent/future.rb +20 -8
  9. data/lib/concurrent/global_thread_pool.rb +0 -13
  10. data/lib/concurrent/goroutine.rb +1 -5
  11. data/lib/concurrent/obligation.rb +64 -10
  12. data/lib/concurrent/promise.rb +60 -38
  13. data/lib/concurrent/thread_pool.rb +5 -16
  14. data/lib/concurrent/utilities.rb +0 -8
  15. data/lib/concurrent/version.rb +1 -1
  16. data/md/defer.md +4 -4
  17. data/md/promise.md +0 -2
  18. data/md/thread_pool.md +0 -27
  19. data/spec/concurrent/agent_spec.rb +27 -8
  20. data/spec/concurrent/cached_thread_pool_spec.rb +1 -14
  21. data/spec/concurrent/defer_spec.rb +21 -17
  22. data/spec/concurrent/event_machine_defer_proxy_spec.rb +149 -159
  23. data/spec/concurrent/fixed_thread_pool_spec.rb +3 -2
  24. data/spec/concurrent/future_spec.rb +10 -3
  25. data/spec/concurrent/goroutine_spec.rb +0 -15
  26. data/spec/concurrent/obligation_shared.rb +2 -16
  27. data/spec/concurrent/promise_spec.rb +13 -15
  28. data/spec/concurrent/thread_pool_shared.rb +5 -5
  29. data/spec/concurrent/utilities_spec.rb +1 -30
  30. data/spec/spec_helper.rb +0 -25
  31. metadata +7 -28
  32. data/lib/concurrent/executor.rb +0 -95
  33. data/lib/concurrent/functions.rb +0 -120
  34. data/lib/concurrent/null_thread_pool.rb +0 -22
  35. data/lib/concurrent/reactor.rb +0 -161
  36. data/lib/concurrent/reactor/drb_async_demux.rb +0 -74
  37. data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -98
  38. data/md/executor.md +0 -176
  39. data/spec/concurrent/executor_spec.rb +0 -200
  40. data/spec/concurrent/functions_spec.rb +0 -217
  41. data/spec/concurrent/global_thread_pool_spec.rb +0 -38
  42. data/spec/concurrent/null_thread_pool_spec.rb +0 -54
  43. data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -12
  44. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -12
  45. data/spec/concurrent/reactor_spec.rb +0 -351
@@ -1,176 +0,0 @@
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,200 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Concurrent
4
-
5
- describe Executor do
6
-
7
- before(:each) do
8
- @orig_stdout = $stdout
9
- $stdout = StringIO.new
10
- end
11
-
12
- after(:each) do
13
- $stdout = @orig_stdout
14
- end
15
-
16
- after(:each) do
17
- @ec.kill unless @ec.nil?
18
- end
19
-
20
- context '#run' do
21
-
22
- it 'raises an exception if no block given' do
23
- lambda {
24
- @ec = Concurrent::Executor.run('Foo')
25
- }.should raise_error
26
- end
27
-
28
- it 'uses the default execution interval when no interval is given' do
29
- @ec = Executor.run('Foo'){ nil }
30
- @ec.execution_interval.should eq Executor::EXECUTION_INTERVAL
31
- end
32
-
33
- it 'uses the default timeout interval when no interval is given' do
34
- @ec = Executor.run('Foo'){ nil }
35
- @ec.timeout_interval.should eq Executor::TIMEOUT_INTERVAL
36
- end
37
-
38
- it 'uses the given execution interval' do
39
- @ec = Executor.run('Foo', execution_interval: 5){ nil }
40
- @ec.execution_interval.should eq 5
41
- end
42
-
43
- it 'uses the given timeout interval' do
44
- @ec = Executor.run('Foo', timeout_interval: 5){ nil }
45
- @ec.timeout_interval.should eq 5
46
- end
47
-
48
- it 'creates a new thread' do
49
- thread = Thread.new{ sleep(1) }
50
- Thread.should_receive(:new).with(any_args()).and_return(thread)
51
- @ec = Executor.run('Foo'){ nil }
52
- end
53
-
54
- it 'returns an ExecutionContext' do
55
- @ec = Executor.run('Foo'){ nil }
56
- @ec.should be_a(Executor::ExecutionContext)
57
- end
58
-
59
- it 'sets the #name context variable' do
60
- @ec = Executor.run('Foo'){ nil }
61
- @ec.name.should eq 'Foo'
62
- end
63
- end
64
-
65
- context 'execution' do
66
-
67
- it 'runs the block immediately when the :run_now option is true' do
68
- @expected = false
69
- @ec = Executor.run('Foo', execution: 500, now: true){ @expected = true }
70
- @expected.should be_false
71
- sleep(1)
72
- @expected.should be_true
73
- end
74
-
75
- it 'waits for :execution_interval seconds when the :run_now option is false' do
76
- @expected = false
77
- @ec = Executor.run('Foo', execution: 0.5, now: false){ @expected = true }
78
- @expected.should be_false
79
- sleep(1)
80
- @expected.should be_true
81
- end
82
-
83
- it 'waits for :execution_interval seconds when the :run_now option is not given' do
84
- @expected = false
85
- @ec = Executor.run('Foo', execution: 0.5){ @expected = true }
86
- @expected.should be_false
87
- sleep(1)
88
- @expected.should be_true
89
- end
90
-
91
- it 'yields to the execution block' do
92
- @expected = false
93
- @ec = Executor.run('Foo', execution: 1){ @expected = true }
94
- sleep(2)
95
- @expected.should be_true
96
- end
97
-
98
- it 'passes any given arguments to the execution block' do
99
- args = [1,2,3,4]
100
- @expected = nil
101
- @ec = Executor.run('Foo', execution_interval: 0.5, args: args) do |*args|
102
- @expected = args
103
- end
104
- sleep(1)
105
- @expected.should eq args
106
- end
107
-
108
- it 'supresses exceptions thrown by the execution block' do
109
- lambda {
110
- @ec = Executor.run('Foo', execution_interval: 0.5) { raise StandardError }
111
- sleep(1)
112
- }.should_not raise_error
113
- end
114
-
115
- it 'kills the worker thread if the timeout is reached' do
116
- # the after(:each) block will trigger this expectation
117
- Thread.should_receive(:kill).at_least(1).with(any_args())
118
- @ec = Executor.run('Foo', execution_interval: 0.5, timeout_interval: 0.5){ Thread.stop }
119
- sleep(1.5)
120
- end
121
- end
122
-
123
- context 'logging' do
124
-
125
- before(:each) do
126
- @name = nil
127
- @level = nil
128
- @msg = nil
129
-
130
- @logger = proc do |name, level, msg|
131
- @name = name
132
- @level = level
133
- @msg = msg
134
- end
135
- end
136
-
137
- it 'uses a custom logger when given' do
138
- @ec = Executor.run('Foo', execution_interval: 0.1, logger: @logger){ nil }
139
- sleep(0.5)
140
- @name.should eq 'Foo'
141
- end
142
-
143
- it 'logs :info when execution is successful' do
144
- @ec = Executor.run('Foo', execution_interval: 0.1, logger: @logger){ nil }
145
- sleep(0.5)
146
- @level.should eq :info
147
- end
148
-
149
- it 'logs :warn when execution times out' do
150
- @ec = Executor.run('Foo', execution_interval: 0.1, timeout_interval: 0.1, logger: @logger){ Thread.stop }
151
- sleep(0.5)
152
- @level.should eq :warn
153
- end
154
-
155
- it 'logs :error when execution is fails' do
156
- @ec = Executor.run('Foo', execution_interval: 0.1, logger: @logger){ raise StandardError }
157
- sleep(0.5)
158
- @level.should eq :error
159
- end
160
- end
161
-
162
- context '#status' do
163
-
164
- it 'returns the status of the executor thread when running' do
165
- @ec = Executor.run('Foo'){ nil }
166
- sleep(0.1)
167
- @ec.status.should eq 'sleep'
168
- end
169
-
170
- it 'returns nil when not running' do
171
- @ec = Executor.run('Foo'){ nil }
172
- @ec.kill
173
- sleep(0.1)
174
- @ec.status.should be_nil
175
- end
176
- end
177
-
178
- context '#join' do
179
-
180
- it 'joins the executor thread when running' do
181
- @ec = Executor.run('Foo'){ nil }
182
- Thread.new{ sleep(1); @ec.kill }
183
- @ec.join.should be_a(Thread)
184
- end
185
-
186
- it 'joins the executor thread with timeout when running' do
187
- @ec = Executor.run('Foo'){ nil }
188
- @ec.join(1).should be_nil
189
- end
190
-
191
- it 'immediately returns nil when not running' do
192
- @ec = Executor.run('Foo'){ nil }
193
- @ec.kill
194
- sleep(0.1)
195
- @ec.join.should be_nil
196
- @ec.join(1).should be_nil
197
- end
198
- end
199
- end
200
- end
@@ -1,217 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Concurrent
4
-
5
- describe 'functions' do
6
-
7
- context '#post' do
8
-
9
- it 'calls #post when supported by the object' do
10
- object = Class.new{
11
- def post() nil; end
12
- }.new
13
- object.should_receive(:post).with(no_args())
14
- post(object){ nil }
15
- end
16
-
17
- it 'raises an exception when not supported by the object' do
18
- object = Class.new{ }.new
19
- lambda {
20
- post(object){ nil }
21
- }.should raise_error(ArgumentError)
22
- end
23
- end
24
-
25
- context '#deref' do
26
-
27
- it 'returns the value of the #deref function' do
28
- object = Class.new{
29
- def deref() nil; end
30
- }.new
31
- object.should_receive(:deref).with(nil)
32
- deref(object, nil){ nil }
33
- end
34
-
35
- it 'returns the value of the #value function' do
36
- object = Class.new{
37
- def value() nil; end
38
- }.new
39
- object.should_receive(:value).with(nil)
40
- deref(object, nil){ nil }
41
- end
42
-
43
- it 'raises an exception when not supported by the object' do
44
- object = Class.new{ }.new
45
- lambda {
46
- deref(object, nil){ nil }
47
- }.should raise_error(ArgumentError)
48
- end
49
- end
50
-
51
- context '#pending?' do
52
-
53
- it 'returns the value of the #pending? function' do
54
- object = Class.new{
55
- def pending?() nil; end
56
- }.new
57
- object.should_receive(:pending?).with(no_args())
58
- pending?(object){ nil }
59
- end
60
-
61
- it 'raises an exception when not supported by the object' do
62
- object = Class.new{ }.new
63
- lambda {
64
- pending?(object){ nil }
65
- }.should raise_error(ArgumentError)
66
- end
67
- end
68
-
69
- context '#fulfilled?' do
70
-
71
- it 'returns the value of the #fulfilled? function' do
72
- object = Class.new{
73
- def fulfilled?() nil; end
74
- }.new
75
- object.should_receive(:fulfilled?).with(no_args())
76
- fulfilled?(object){ nil }
77
- end
78
-
79
- it 'returns the value of the #realized? function' do
80
- object = Class.new{
81
- def realized?() nil; end
82
- }.new
83
- object.should_receive(:realized?).with(no_args())
84
- fulfilled?(object){ nil }
85
- end
86
-
87
- it 'raises an exception when not supported by the object' do
88
- object = Class.new{ }.new
89
- lambda {
90
- fulfilled?(object){ nil }
91
- }.should raise_error(ArgumentError)
92
- end
93
- end
94
-
95
- context '#realized?' do
96
-
97
- it 'returns the value of the #realized? function' do
98
- object = Class.new{
99
- def realized?() nil; end
100
- }.new
101
- object.should_receive(:realized?).with(no_args())
102
- realized?(object){ nil }
103
- end
104
-
105
- it 'returns the value of the #fulfilled? function' do
106
- object = Class.new{
107
- def fulfilled?() nil; end
108
- }.new
109
- object.should_receive(:fulfilled?).with(no_args())
110
- realized?(object){ nil }
111
- end
112
-
113
- it 'raises an exception when not supported by the object' do
114
- object = Class.new{ }.new
115
- lambda {
116
- realized?(object){ nil }
117
- }.should raise_error(ArgumentError)
118
- end
119
- end
120
-
121
- context '#rejected?' do
122
-
123
- it 'returns the value of the #rejected? function' do
124
- object = Class.new{
125
- def rejected?() nil; end
126
- }.new
127
- object.should_receive(:rejected?).with(no_args())
128
- rejected?(object){ nil }
129
- end
130
-
131
- it 'raises an exception when not supported by the object' do
132
- object = Class.new{ }.new
133
- lambda {
134
- rejected?(object){ nil }
135
- }.should raise_error(ArgumentError)
136
- end
137
- end
138
- end
139
-
140
- describe Agent do
141
-
142
- before(:each) do
143
- Agent.thread_pool = FixedThreadPool.new(1)
144
- end
145
-
146
- it 'aliases #<< for Agent#post' do
147
- subject = Agent.new(0)
148
- subject << proc{ 100 }
149
- sleep(0.1)
150
- subject.value.should eq 100
151
- end
152
-
153
- it 'aliases Kernel#agent for Agent.new' do
154
- agent(10).should be_a(Agent)
155
- end
156
-
157
- it 'aliases Kernel#deref for #deref' do
158
- deref(Agent.new(10)).should eq 10
159
- deref(Agent.new(10), 10).should eq 10
160
- end
161
-
162
- it 'aliases Kernel:post for Agent#post' do
163
- subject = Agent.new(0)
164
- post(subject){ 100 }
165
- sleep(0.1)
166
- subject.value.should eq 100
167
- end
168
- end
169
-
170
- describe Defer do
171
-
172
- before(:each) do
173
- Defer.thread_pool = FixedThreadPool.new(1)
174
- end
175
-
176
- it 'aliases Kernel#defer' do
177
- defer{ nil }.should be_a(Defer)
178
- end
179
- end
180
-
181
- describe Executor do
182
-
183
- it 'aliases Kernel#executor' do
184
- ex = executor('executor'){ nil }
185
- ex.should be_a(Executor::ExecutionContext)
186
- ex.kill
187
- end
188
- end
189
-
190
- describe Future do
191
-
192
- before(:each) do
193
- Future.thread_pool = FixedThreadPool.new(1)
194
- end
195
-
196
- it 'aliases Kernel#future for Future.new' do
197
- future().should be_a(Future)
198
- future(){ nil }.should be_a(Future)
199
- future(1, 2, 3).should be_a(Future)
200
- future(1, 2, 3){ nil }.should be_a(Future)
201
- end
202
- end
203
-
204
- describe Promise do
205
-
206
- before(:each) do
207
- Promise.thread_pool = FixedThreadPool.new(1)
208
- end
209
-
210
- it 'aliases Kernel#promise for Promise.new' do
211
- promise().should be_a(Promise)
212
- promise(){ nil }.should be_a(Promise)
213
- promise(1, 2, 3).should be_a(Promise)
214
- promise(1, 2, 3){ nil }.should be_a(Promise)
215
- end
216
- end
217
- end