concurrent-ruby 0.1.1.pre.5 → 0.1.1

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 (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