concurrent-ruby 0.2.2 → 0.3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -42
  3. data/lib/concurrent.rb +5 -6
  4. data/lib/concurrent/agent.rb +29 -33
  5. data/lib/concurrent/cached_thread_pool.rb +26 -105
  6. data/lib/concurrent/channel.rb +94 -0
  7. data/lib/concurrent/event.rb +8 -17
  8. data/lib/concurrent/executor.rb +68 -72
  9. data/lib/concurrent/fixed_thread_pool.rb +15 -83
  10. data/lib/concurrent/functions.rb +7 -22
  11. data/lib/concurrent/future.rb +29 -9
  12. data/lib/concurrent/null_thread_pool.rb +5 -2
  13. data/lib/concurrent/obligation.rb +6 -16
  14. data/lib/concurrent/promise.rb +9 -10
  15. data/lib/concurrent/runnable.rb +103 -0
  16. data/lib/concurrent/supervisor.rb +271 -44
  17. data/lib/concurrent/thread_pool.rb +112 -39
  18. data/lib/concurrent/version.rb +1 -1
  19. data/md/executor.md +9 -3
  20. data/md/goroutine.md +11 -9
  21. data/md/reactor.md +32 -0
  22. data/md/supervisor.md +43 -0
  23. data/spec/concurrent/agent_spec.rb +128 -51
  24. data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
  25. data/spec/concurrent/channel_spec.rb +446 -0
  26. data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
  27. data/spec/concurrent/event_spec.rb +0 -19
  28. data/spec/concurrent/executor_spec.rb +167 -119
  29. data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
  30. data/spec/concurrent/functions_spec.rb +0 -20
  31. data/spec/concurrent/future_spec.rb +88 -0
  32. data/spec/concurrent/null_thread_pool_spec.rb +23 -2
  33. data/spec/concurrent/obligation_shared.rb +0 -5
  34. data/spec/concurrent/promise_spec.rb +9 -10
  35. data/spec/concurrent/runnable_shared.rb +62 -0
  36. data/spec/concurrent/runnable_spec.rb +233 -0
  37. data/spec/concurrent/supervisor_spec.rb +912 -47
  38. data/spec/concurrent/thread_pool_shared.rb +18 -31
  39. data/spec/spec_helper.rb +10 -3
  40. metadata +17 -23
  41. data/lib/concurrent/defer.rb +0 -65
  42. data/lib/concurrent/reactor.rb +0 -166
  43. data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
  44. data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
  45. data/lib/concurrent/utilities.rb +0 -32
  46. data/md/defer.md +0 -174
  47. data/spec/concurrent/defer_spec.rb +0 -199
  48. data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
  49. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
  50. data/spec/concurrent/reactor_spec.rb +0 -364
  51. 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 eq [1,2,3]
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
- @ec.kill unless @ec.nil?
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 '#run' do
24
+ context ':runnable' do
21
25
 
22
- it 'raises an exception if no block given' do
23
- lambda {
24
- @ec = Concurrent::Executor.run('Foo')
25
- }.should raise_error
26
- end
26
+ subject { Executor.new(':runnable'){ nil } }
27
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
28
+ it_should_behave_like :runnable
29
+ end
32
30
 
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
31
+ context 'created with #new' do
37
32
 
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
33
+ context '#initialize' do
42
34
 
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
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
- 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 }
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
- it 'returns an ExecutionContext' do
55
- @ec = Executor.run('Foo'){ nil }
56
- @ec.should be_a(Executor::ExecutionContext)
67
+ context '#kill' do
68
+ pending
57
69
  end
58
70
 
59
- it 'sets the #name context variable' do
60
- @ec = Executor.run('Foo'){ nil }
61
- @ec.name.should eq 'Foo'
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 'execution' do
87
+ context 'created with Executor.run!' do
66
88
 
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
89
+ context 'arguments' do
74
90
 
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
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
- 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
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
- 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
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
- 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
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
- it 'supresses exceptions thrown by the execution block' do
109
- lambda {
110
- @ec = Executor.run('Foo', execution_interval: 0.5) { raise StandardError }
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
- }.should_not raise_error
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
- 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)
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
- @ec = Executor.run('Foo', execution_interval: 0.1, logger: @logger){ nil }
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
- @ec = Executor.run('Foo', execution_interval: 0.1, logger: @logger){ nil }
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
- @ec = Executor.run('Foo', execution_interval: 0.1, timeout_interval: 0.1, logger: @logger){ Thread.stop }
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
- @ec = Executor.run('Foo', execution_interval: 0.1, logger: @logger){ raise StandardError }
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
- it_should_behave_like 'Thread Pool'
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 1024' do
25
+ it 'raises an exception when the pool size is greater than MAX_POOL_SIZE' do
21
26
  lambda {
22
- FixedThreadPool.new(1025)
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 the size of the subject when running' do
57
- subject.size.should eq pool_size
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
- 3.times{ pool << proc{ raise StandardError } }
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)