concurrent-ruby 0.2.2 → 0.3.0.pre.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.
- checksums.yaml +4 -4
- data/README.md +45 -42
- data/lib/concurrent.rb +5 -6
- data/lib/concurrent/agent.rb +29 -33
- data/lib/concurrent/cached_thread_pool.rb +26 -105
- data/lib/concurrent/channel.rb +94 -0
- data/lib/concurrent/event.rb +8 -17
- data/lib/concurrent/executor.rb +68 -72
- data/lib/concurrent/fixed_thread_pool.rb +15 -83
- data/lib/concurrent/functions.rb +7 -22
- data/lib/concurrent/future.rb +29 -9
- data/lib/concurrent/null_thread_pool.rb +5 -2
- data/lib/concurrent/obligation.rb +6 -16
- data/lib/concurrent/promise.rb +9 -10
- data/lib/concurrent/runnable.rb +103 -0
- data/lib/concurrent/supervisor.rb +271 -44
- data/lib/concurrent/thread_pool.rb +112 -39
- data/lib/concurrent/version.rb +1 -1
- data/md/executor.md +9 -3
- data/md/goroutine.md +11 -9
- data/md/reactor.md +32 -0
- data/md/supervisor.md +43 -0
- data/spec/concurrent/agent_spec.rb +128 -51
- data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
- data/spec/concurrent/channel_spec.rb +446 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
- data/spec/concurrent/event_spec.rb +0 -19
- data/spec/concurrent/executor_spec.rb +167 -119
- data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
- data/spec/concurrent/functions_spec.rb +0 -20
- data/spec/concurrent/future_spec.rb +88 -0
- data/spec/concurrent/null_thread_pool_spec.rb +23 -2
- data/spec/concurrent/obligation_shared.rb +0 -5
- data/spec/concurrent/promise_spec.rb +9 -10
- data/spec/concurrent/runnable_shared.rb +62 -0
- data/spec/concurrent/runnable_spec.rb +233 -0
- data/spec/concurrent/supervisor_spec.rb +912 -47
- data/spec/concurrent/thread_pool_shared.rb +18 -31
- data/spec/spec_helper.rb +10 -3
- metadata +17 -23
- data/lib/concurrent/defer.rb +0 -65
- data/lib/concurrent/reactor.rb +0 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
- data/lib/concurrent/utilities.rb +0 -32
- data/md/defer.md +0 -174
- data/spec/concurrent/defer_spec.rb +0 -199
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
- data/spec/concurrent/reactor_spec.rb +0 -364
- data/spec/concurrent/utilities_spec.rb +0 -74
@@ -86,7 +86,9 @@ if Functional::PLATFORM.mri?
|
|
86
86
|
subject.post{ @expected << 2 }
|
87
87
|
subject.post{ @expected << 3 }
|
88
88
|
sleep(0.1)
|
89
|
-
@expected.should
|
89
|
+
@expected.should include(1)
|
90
|
+
@expected.should include(2)
|
91
|
+
@expected.should include(3)
|
90
92
|
|
91
93
|
EventMachine.stop
|
92
94
|
end
|
@@ -54,25 +54,6 @@ module Concurrent
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
context '#pulse' do
|
58
|
-
|
59
|
-
it 'triggers the event' do
|
60
|
-
subject.reset
|
61
|
-
@expected = false
|
62
|
-
Thread.new{ subject.wait; @expected = true }
|
63
|
-
sleep(0.1)
|
64
|
-
subject.pulse
|
65
|
-
sleep(0.1)
|
66
|
-
@expected.should be_true
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'sets the state to unset' do
|
70
|
-
subject.pulse
|
71
|
-
sleep(0.1)
|
72
|
-
subject.should_not be_set
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
57
|
context '#wait' do
|
77
58
|
|
78
59
|
it 'returns immediately when the event has been set' do
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require_relative 'runnable_shared'
|
2
3
|
|
3
4
|
module Concurrent
|
4
5
|
|
@@ -14,109 +15,194 @@ module Concurrent
|
|
14
15
|
end
|
15
16
|
|
16
17
|
after(:each) do
|
17
|
-
@
|
18
|
+
@subject = @subject.runner if @subject.respond_to?(:runner)
|
19
|
+
@subject.kill unless @subject.nil?
|
20
|
+
@thread.kill unless @thread.nil?
|
21
|
+
sleep(0.1)
|
18
22
|
end
|
19
23
|
|
20
|
-
context '
|
24
|
+
context ':runnable' do
|
21
25
|
|
22
|
-
|
23
|
-
lambda {
|
24
|
-
@ec = Concurrent::Executor.run('Foo')
|
25
|
-
}.should raise_error
|
26
|
-
end
|
26
|
+
subject { Executor.new(':runnable'){ nil } }
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
@ec.execution_interval.should eq Executor::EXECUTION_INTERVAL
|
31
|
-
end
|
28
|
+
it_should_behave_like :runnable
|
29
|
+
end
|
32
30
|
|
33
|
-
|
34
|
-
@ec = Executor.run('Foo'){ nil }
|
35
|
-
@ec.timeout_interval.should eq Executor::TIMEOUT_INTERVAL
|
36
|
-
end
|
31
|
+
context 'created with #new' do
|
37
32
|
|
38
|
-
|
39
|
-
@ec = Executor.run('Foo', execution_interval: 5){ nil }
|
40
|
-
@ec.execution_interval.should eq 5
|
41
|
-
end
|
33
|
+
context '#initialize' do
|
42
34
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
35
|
+
it 'raises an exception if no block given' do
|
36
|
+
lambda {
|
37
|
+
@subject = Concurrent::Executor.new('Foo')
|
38
|
+
}.should raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'uses the default execution interval when no interval is given' do
|
42
|
+
@subject = Executor.new('Foo'){ nil }
|
43
|
+
@subject.execution_interval.should eq Executor::EXECUTION_INTERVAL
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'uses the default timeout interval when no interval is given' do
|
47
|
+
@subject = Executor.new('Foo'){ nil }
|
48
|
+
@subject.timeout_interval.should eq Executor::TIMEOUT_INTERVAL
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'uses the given execution interval' do
|
52
|
+
@subject = Executor.new('Foo', execution_interval: 5){ nil }
|
53
|
+
@subject.execution_interval.should eq 5
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'uses the given timeout interval' do
|
57
|
+
@subject = Executor.new('Foo', timeout_interval: 5){ nil }
|
58
|
+
@subject.timeout_interval.should eq 5
|
59
|
+
end
|
47
60
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
61
|
+
it 'sets the #name context variable' do
|
62
|
+
@subject = Executor.new('Foo'){ nil }
|
63
|
+
@subject.name.should eq 'Foo'
|
64
|
+
end
|
52
65
|
end
|
53
66
|
|
54
|
-
|
55
|
-
|
56
|
-
@ec.should be_a(Executor::ExecutionContext)
|
67
|
+
context '#kill' do
|
68
|
+
pending
|
57
69
|
end
|
58
70
|
|
59
|
-
|
60
|
-
|
61
|
-
|
71
|
+
context '#status' do
|
72
|
+
|
73
|
+
subject { Executor.new('Foo'){ nil } }
|
74
|
+
|
75
|
+
it 'returns the status of the executor thread when running' do
|
76
|
+
@thread = Thread.new { subject.run }
|
77
|
+
sleep(0.1)
|
78
|
+
subject.status.should eq 'sleep'
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns nil when not running' do
|
82
|
+
subject.status.should be_nil
|
83
|
+
end
|
62
84
|
end
|
63
85
|
end
|
64
86
|
|
65
|
-
context '
|
87
|
+
context 'created with Executor.run!' do
|
66
88
|
|
67
|
-
|
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
|
89
|
+
context 'arguments' do
|
74
90
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@expected.should be_true
|
81
|
-
end
|
91
|
+
it 'raises an exception if no block given' do
|
92
|
+
lambda {
|
93
|
+
@subject = Concurrent::Executor.run('Foo')
|
94
|
+
}.should raise_error
|
95
|
+
end
|
82
96
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@expected.should be_true
|
89
|
-
end
|
97
|
+
it 'passes the name to the new Executor' do
|
98
|
+
@subject = Executor.new('Foo'){ nil }
|
99
|
+
Executor.should_receive(:new).with('Foo').and_return(@subject)
|
100
|
+
Concurrent::Executor.run!('Foo')
|
101
|
+
end
|
90
102
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
103
|
+
it 'passes the options to the new Executor' do
|
104
|
+
opts = {
|
105
|
+
execution_interval: 100,
|
106
|
+
timeout_interval: 100,
|
107
|
+
run_now: false,
|
108
|
+
logger: proc{ nil },
|
109
|
+
block_args: %w[one two three]
|
110
|
+
}
|
111
|
+
@subject = Executor.new('Foo', opts){ nil }
|
112
|
+
Executor.should_receive(:new).with(anything(), opts).and_return(@subject)
|
113
|
+
Concurrent::Executor.run!('Foo', opts)
|
114
|
+
end
|
97
115
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
116
|
+
it 'passes the block to the new Executor' do
|
117
|
+
@expected = false
|
118
|
+
block = proc{ @expected = true }
|
119
|
+
@subject = Executor.run!('Foo', run_now: true, &block)
|
120
|
+
sleep(0.1)
|
121
|
+
@expected.should be_true
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'creates a new thread' do
|
125
|
+
thread = Thread.new{ sleep(1) }
|
126
|
+
Thread.should_receive(:new).with(any_args()).and_return(thread)
|
127
|
+
@subject = Executor.run!('Foo'){ nil }
|
103
128
|
end
|
104
|
-
sleep(1)
|
105
|
-
@expected.should eq args
|
106
129
|
end
|
107
130
|
|
108
|
-
|
109
|
-
|
110
|
-
|
131
|
+
context 'execution' do
|
132
|
+
|
133
|
+
it 'runs the block immediately when the :run_now option is true' do
|
134
|
+
@expected = false
|
135
|
+
@subject = Executor.run!('Foo', execution: 500, now: true){ @expected = true }
|
136
|
+
sleep(0.1)
|
137
|
+
@expected.should be_true
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'waits for :execution_interval seconds when the :run_now option is false' do
|
141
|
+
@expected = false
|
142
|
+
@subject = Executor.run!('Foo', execution: 0.5, now: false){ @expected = true }
|
143
|
+
@expected.should be_false
|
144
|
+
sleep(1)
|
145
|
+
@expected.should be_true
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'waits for :execution_interval seconds when the :run_now option is not given' do
|
149
|
+
@expected = false
|
150
|
+
@subject = Executor.run!('Foo', execution: 0.5){ @expected = true }
|
151
|
+
@expected.should be_false
|
152
|
+
sleep(1)
|
153
|
+
@expected.should be_true
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'yields to the execution block' do
|
157
|
+
@expected = false
|
158
|
+
@subject = Executor.run!('Foo', execution: 1){ @expected = true }
|
159
|
+
sleep(2)
|
160
|
+
@expected.should be_true
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'passes any given arguments to the execution block' do
|
164
|
+
args = [1,2,3,4]
|
165
|
+
@expected = nil
|
166
|
+
@subject = Executor.new('Foo', execution_interval: 0.5, args: args) do |*args|
|
167
|
+
@expected = args
|
168
|
+
end
|
169
|
+
@thread = Thread.new { @subject.run }
|
111
170
|
sleep(1)
|
112
|
-
|
171
|
+
@expected.should eq args
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'supresses exceptions thrown by the execution block' do
|
175
|
+
lambda {
|
176
|
+
@subject = Executor.new('Foo', execution_interval: 0.5) { raise StandardError }
|
177
|
+
@thread = Thread.new { @subject.run }
|
178
|
+
sleep(1)
|
179
|
+
}.should_not raise_error
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'kills the worker thread if the timeout is reached' do
|
183
|
+
# the after(:each) block will trigger this expectation
|
184
|
+
Thread.should_receive(:kill).at_least(1).with(any_args())
|
185
|
+
@subject = Executor.new('Foo', execution_interval: 0.5, timeout_interval: 0.5){ Thread.stop }
|
186
|
+
@thread = Thread.new { @subject.run }
|
187
|
+
sleep(1.5)
|
188
|
+
end
|
113
189
|
end
|
114
190
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
191
|
+
context '#status' do
|
192
|
+
|
193
|
+
it 'returns the status of the executor thread when running' do
|
194
|
+
@subject = Executor.run!('Foo'){ nil }
|
195
|
+
sleep(0.1)
|
196
|
+
@subject.runner.status.should eq 'sleep'
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'returns nil when not running' do
|
200
|
+
@subject = Executor.new('Foo'){ nil }
|
201
|
+
sleep(0.1)
|
202
|
+
@subject.kill
|
203
|
+
sleep(0.1)
|
204
|
+
@subject.status.should be_nil
|
205
|
+
end
|
120
206
|
end
|
121
207
|
end
|
122
208
|
|
@@ -135,66 +221,28 @@ module Concurrent
|
|
135
221
|
end
|
136
222
|
|
137
223
|
it 'uses a custom logger when given' do
|
138
|
-
@
|
224
|
+
@subject = Executor.run!('Foo', execution_interval: 0.1, logger: @logger){ nil }
|
139
225
|
sleep(0.5)
|
140
226
|
@name.should eq 'Foo'
|
141
227
|
end
|
142
228
|
|
143
229
|
it 'logs :info when execution is successful' do
|
144
|
-
@
|
230
|
+
@subject = Executor.run!('Foo', execution_interval: 0.1, logger: @logger){ nil }
|
145
231
|
sleep(0.5)
|
146
232
|
@level.should eq :info
|
147
233
|
end
|
148
234
|
|
149
235
|
it 'logs :warn when execution times out' do
|
150
|
-
@
|
236
|
+
@subject = Executor.run!('Foo', execution_interval: 0.1, timeout_interval: 0.1, logger: @logger){ Thread.stop }
|
151
237
|
sleep(0.5)
|
152
238
|
@level.should eq :warn
|
153
239
|
end
|
154
240
|
|
155
241
|
it 'logs :error when execution is fails' do
|
156
|
-
@
|
242
|
+
@subject = Executor.run!('Foo', execution_interval: 0.1, logger: @logger){ raise StandardError }
|
157
243
|
sleep(0.5)
|
158
244
|
@level.should eq :error
|
159
245
|
end
|
160
246
|
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
247
|
end
|
200
248
|
end
|
@@ -7,7 +7,12 @@ module Concurrent
|
|
7
7
|
|
8
8
|
subject { FixedThreadPool.new(5) }
|
9
9
|
|
10
|
-
|
10
|
+
after(:each) do
|
11
|
+
subject.kill
|
12
|
+
sleep(0.1)
|
13
|
+
end
|
14
|
+
|
15
|
+
it_should_behave_like :thread_pool
|
11
16
|
|
12
17
|
context '#initialize' do
|
13
18
|
|
@@ -17,35 +22,11 @@ module Concurrent
|
|
17
22
|
}.should raise_error(ArgumentError)
|
18
23
|
end
|
19
24
|
|
20
|
-
it 'raises an exception when the pool size is greater than
|
25
|
+
it 'raises an exception when the pool size is greater than MAX_POOL_SIZE' do
|
21
26
|
lambda {
|
22
|
-
FixedThreadPool.new(
|
27
|
+
FixedThreadPool.new(FixedThreadPool::MAX_POOL_SIZE + 1)
|
23
28
|
}.should raise_error(ArgumentError)
|
24
29
|
end
|
25
|
-
|
26
|
-
it 'creates a thread pool of the given size' do
|
27
|
-
thread = Thread.new{ nil }
|
28
|
-
# add one for the garbage collector
|
29
|
-
Thread.should_receive(:new).exactly(5+1).times.and_return(thread)
|
30
|
-
pool = FixedThreadPool.new(5)
|
31
|
-
pool.size.should eq 5
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'aliases Concurrent#new_fixed_thread_pool' do
|
35
|
-
pool = Concurrent.new_fixed_thread_pool(5)
|
36
|
-
pool.should be_a(FixedThreadPool)
|
37
|
-
pool.size.should eq 5
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
context '#kill' do
|
42
|
-
|
43
|
-
it 'kills all threads' do
|
44
|
-
Thread.should_receive(:kill).at_least(5).times
|
45
|
-
pool = FixedThreadPool.new(5)
|
46
|
-
pool.kill
|
47
|
-
sleep(0.1)
|
48
|
-
end
|
49
30
|
end
|
50
31
|
|
51
32
|
context '#size' do
|
@@ -53,8 +34,17 @@ module Concurrent
|
|
53
34
|
let(:pool_size) { 3 }
|
54
35
|
subject { FixedThreadPool.new(pool_size) }
|
55
36
|
|
56
|
-
it 'returns
|
57
|
-
subject.
|
37
|
+
it 'returns zero on start' do
|
38
|
+
subject.shutdown
|
39
|
+
subject.size.should eq 0
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns the size of the pool when running' do
|
43
|
+
pool_size.times do |i|
|
44
|
+
subject.post{ sleep }
|
45
|
+
sleep(0.1)
|
46
|
+
subject.size.should eq i+1
|
47
|
+
end
|
58
48
|
end
|
59
49
|
|
60
50
|
it 'returns zero while shutting down' do
|
@@ -69,11 +59,31 @@ module Concurrent
|
|
69
59
|
end
|
70
60
|
end
|
71
61
|
|
62
|
+
context 'worker creation and caching' do
|
63
|
+
|
64
|
+
it 'creates new workers when there are none available' do
|
65
|
+
pool = FixedThreadPool.new(5)
|
66
|
+
pool.size.should eq 0
|
67
|
+
5.times{ sleep(0.1); pool << proc{ sleep } }
|
68
|
+
sleep(0.1)
|
69
|
+
pool.size.should eq 5
|
70
|
+
pool.kill
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'never creates more than :max_threads threads' do
|
74
|
+
pool = FixedThreadPool.new(5)
|
75
|
+
100.times{ sleep(0.01); pool << proc{ sleep } }
|
76
|
+
sleep(0.1)
|
77
|
+
pool.length.should eq 5
|
78
|
+
pool.kill
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
72
82
|
context 'exception handling' do
|
73
83
|
|
74
84
|
it 'restarts threads that experience exception' do
|
75
85
|
pool = FixedThreadPool.new(5)
|
76
|
-
|
86
|
+
5.times{ pool << proc{ raise StandardError } }
|
77
87
|
sleep(5)
|
78
88
|
pool.size.should eq 5
|
79
89
|
pool.status.should_not include(nil)
|