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.
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)