concurrent-ruby 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -1
  3. data/lib/concurrent.rb +8 -1
  4. data/lib/concurrent/agent.rb +19 -40
  5. data/lib/concurrent/cached_thread_pool.rb +10 -11
  6. data/lib/concurrent/defer.rb +8 -12
  7. data/lib/concurrent/executor.rb +95 -0
  8. data/lib/concurrent/fixed_thread_pool.rb +12 -6
  9. data/lib/concurrent/functions.rb +120 -0
  10. data/lib/concurrent/future.rb +8 -20
  11. data/lib/concurrent/global_thread_pool.rb +13 -0
  12. data/lib/concurrent/goroutine.rb +5 -1
  13. data/lib/concurrent/null_thread_pool.rb +22 -0
  14. data/lib/concurrent/obligation.rb +10 -64
  15. data/lib/concurrent/promise.rb +38 -60
  16. data/lib/concurrent/reactor.rb +166 -0
  17. data/lib/concurrent/reactor/drb_async_demux.rb +83 -0
  18. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -0
  19. data/lib/concurrent/supervisor.rb +100 -0
  20. data/lib/concurrent/thread_pool.rb +16 -5
  21. data/lib/concurrent/utilities.rb +8 -0
  22. data/lib/concurrent/version.rb +1 -1
  23. data/md/defer.md +4 -4
  24. data/md/executor.md +187 -0
  25. data/md/promise.md +2 -0
  26. data/md/thread_pool.md +27 -0
  27. data/spec/concurrent/agent_spec.rb +8 -27
  28. data/spec/concurrent/cached_thread_pool_spec.rb +14 -1
  29. data/spec/concurrent/defer_spec.rb +17 -21
  30. data/spec/concurrent/event_machine_defer_proxy_spec.rb +159 -149
  31. data/spec/concurrent/executor_spec.rb +200 -0
  32. data/spec/concurrent/fixed_thread_pool_spec.rb +2 -3
  33. data/spec/concurrent/functions_spec.rb +217 -0
  34. data/spec/concurrent/future_spec.rb +4 -11
  35. data/spec/concurrent/global_thread_pool_spec.rb +38 -0
  36. data/spec/concurrent/goroutine_spec.rb +15 -0
  37. data/spec/concurrent/null_thread_pool_spec.rb +54 -0
  38. data/spec/concurrent/obligation_shared.rb +127 -116
  39. data/spec/concurrent/promise_spec.rb +16 -14
  40. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -0
  41. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -0
  42. data/spec/concurrent/reactor_spec.rb +364 -0
  43. data/spec/concurrent/supervisor_spec.rb +258 -0
  44. data/spec/concurrent/thread_pool_shared.rb +156 -161
  45. data/spec/concurrent/utilities_spec.rb +30 -1
  46. data/spec/spec_helper.rb +13 -0
  47. metadata +38 -9
@@ -2,241 +2,251 @@ require 'spec_helper'
2
2
 
3
3
  require 'concurrent/agent'
4
4
  require 'concurrent/future'
5
+ require 'concurrent/goroutine'
5
6
  require 'concurrent/promise'
6
7
 
7
- module Concurrent
8
+ if Functional::PLATFORM.mri?
8
9
 
9
- describe EventMachineDeferProxy do
10
+ module Concurrent
10
11
 
11
- subject { EventMachineDeferProxy.new }
12
+ describe EventMachineDeferProxy do
12
13
 
13
- context '#post' do
14
+ subject { EventMachineDeferProxy.new }
14
15
 
15
- it 'proxies a call without arguments' do
16
- @expected = false
17
- EventMachine.run do
18
- subject.post{ @expected = true }
19
- sleep(0.1)
20
- EventMachine.stop
21
- end
22
- @expected.should eq true
23
- end
24
-
25
- it 'proxies a call with arguments' do
26
- @expected = []
27
- EventMachine.run do
28
- subject.post(1,2,3){|*args| @expected = args }
29
- sleep(0.1)
30
- EventMachine.stop
31
- end
32
- @expected.should eq [1,2,3]
16
+ after(:all) do
17
+ $GLOBAL_THREAD_POOL = FixedThreadPool.new(1)
33
18
  end
34
19
 
35
- it 'aliases #<<' do
36
- @expected = false
37
- EventMachine.run do
38
- subject << proc{ @expected = true }
39
- sleep(0.1)
40
- EventMachine.stop
41
- end
42
- @expected.should eq true
43
- end
44
- end
45
-
46
- context 'operation' do
47
-
48
- context 'goroutine' do
49
-
50
- it 'passes all arguments to the block' do
51
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
20
+ context '#post' do
52
21
 
22
+ it 'proxies a call without arguments' do
23
+ @expected = false
53
24
  EventMachine.run do
54
-
55
- @expected = nil
56
- go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
25
+ subject.post{ @expected = true }
57
26
  sleep(0.1)
58
- @expected.should eq [3, 2, 1]
59
-
60
27
  EventMachine.stop
61
28
  end
29
+ @expected.should eq true
62
30
  end
63
- end
64
-
65
- context Agent do
66
-
67
- subject { Agent.new(0) }
68
-
69
- it 'supports fulfillment' do
70
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
71
31
 
32
+ it 'proxies a call with arguments' do
33
+ @expected = []
72
34
  EventMachine.run do
73
-
74
- @expected = []
75
- subject.post{ @expected << 1 }
76
- subject.post{ @expected << 2 }
77
- subject.post{ @expected << 3 }
35
+ subject.post(1,2,3){|*args| @expected = args }
78
36
  sleep(0.1)
79
- @expected.should eq [1,2,3]
80
-
81
37
  EventMachine.stop
82
38
  end
39
+ @expected.should eq [1,2,3]
83
40
  end
84
41
 
85
- it 'supports validation' do
86
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
87
-
42
+ it 'aliases #<<' do
43
+ @expected = false
88
44
  EventMachine.run do
89
-
90
- @expected = nil
91
- subject.validate{ @expected = 10; true }
92
- subject.post{ nil }
45
+ subject << proc{ @expected = true }
93
46
  sleep(0.1)
94
- @expected.should eq 10
95
-
96
47
  EventMachine.stop
97
48
  end
49
+ @expected.should eq true
98
50
  end
51
+ end
99
52
 
100
- it 'supports rejection' do
101
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
102
-
103
- EventMachine.run do
53
+ context 'operation' do
104
54
 
105
- @expected = nil
106
- subject.
107
- on_error(StandardError){|ex| @expected = 1 }.
108
- on_error(StandardError){|ex| @expected = 2 }.
109
- on_error(StandardError){|ex| @expected = 3 }
110
- subject.post{ raise StandardError }
111
- sleep(0.1)
112
- @expected.should eq 1
55
+ context 'goroutine' do
113
56
 
114
- EventMachine.stop
115
- end
116
- end
117
- end
118
-
119
- context Future do
57
+ it 'passes all arguments to the block' do
58
+ $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
120
59
 
121
- it 'supports fulfillment' do
122
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
60
+ EventMachine.run do
123
61
 
124
- EventMachine.run do
62
+ @expected = nil
63
+ go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
64
+ sleep(0.1)
65
+ @expected.should eq [3, 2, 1]
125
66
 
126
- @a = @b = @c = nil
127
- f = Future.new(1, 2, 3) do |a, b, c|
128
- @a, @b, @c = a, b, c
67
+ EventMachine.stop
129
68
  end
130
- sleep(0.1)
131
- [@a, @b, @c].should eq [1, 2, 3]
132
-
133
- sleep(0.1)
134
- EventMachine.stop
135
69
  end
136
70
  end
137
- end
138
71
 
139
- context Promise do
72
+ context Agent do
140
73
 
141
- context 'fulfillment' do
74
+ subject { Agent.new(0) }
142
75
 
143
- it 'passes all arguments to the first promise in the chain' do
144
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
76
+ before(:each) do
77
+ Agent.thread_pool = EventMachineDeferProxy.new
78
+ end
79
+
80
+ it 'supports fulfillment' do
145
81
 
146
82
  EventMachine.run do
147
83
 
148
- @a = @b = @c = nil
149
- p = Promise.new(1, 2, 3) do |a, b, c|
150
- @a, @b, @c = a, b, c
151
- end
84
+ @expected = []
85
+ subject.post{ @expected << 1 }
86
+ subject.post{ @expected << 2 }
87
+ subject.post{ @expected << 3 }
152
88
  sleep(0.1)
153
- [@a, @b, @c].should eq [1, 2, 3]
89
+ @expected.should eq [1,2,3]
154
90
 
155
- sleep(0.1)
156
91
  EventMachine.stop
157
92
  end
158
93
  end
159
94
 
160
- it 'passes the result of each block to all its children' do
161
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
95
+ it 'supports validation' do
162
96
 
163
97
  EventMachine.run do
98
+
164
99
  @expected = nil
165
- Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
100
+ subject.validate{ @expected = 10; true }
101
+ subject.post{ nil }
166
102
  sleep(0.1)
167
- @expected.should eq 20
103
+ @expected.should eq 10
168
104
 
169
- sleep(0.1)
170
105
  EventMachine.stop
171
106
  end
172
107
  end
173
108
 
174
- it 'sets the promise value to the result if its block' do
175
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
109
+ it 'supports rejection' do
176
110
 
177
111
  EventMachine.run do
178
112
 
179
- p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
113
+ @expected = nil
114
+ subject.
115
+ on_error(StandardError){|ex| @expected = 1 }.
116
+ on_error(StandardError){|ex| @expected = 2 }.
117
+ on_error(StandardError){|ex| @expected = 3 }
118
+ subject.post{ raise StandardError }
180
119
  sleep(0.1)
181
- p.value.should eq 40
120
+ @expected.should eq 1
182
121
 
183
- sleep(0.1)
184
122
  EventMachine.stop
185
123
  end
186
124
  end
187
125
  end
188
126
 
189
- context 'rejection' do
127
+ context Future do
190
128
 
191
- it 'sets the promise reason and error on exception' do
192
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
129
+ before(:each) do
130
+ Future.thread_pool = EventMachineDeferProxy.new
131
+ end
132
+
133
+ it 'supports fulfillment' do
193
134
 
194
135
  EventMachine.run do
195
136
 
196
- p = Promise.new{ raise StandardError.new('Boom!') }
137
+ @a = @b = @c = nil
138
+ f = Future.new(1, 2, 3) do |a, b, c|
139
+ @a, @b, @c = a, b, c
140
+ end
197
141
  sleep(0.1)
198
- p.reason.should be_a(Exception)
199
- p.reason.should.to_s =~ /Boom!/
200
- p.should be_rejected
142
+ [@a, @b, @c].should eq [1, 2, 3]
201
143
 
202
144
  sleep(0.1)
203
145
  EventMachine.stop
204
146
  end
205
147
  end
148
+ end
206
149
 
207
- it 'calls the first exception block with a matching class' do
208
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
150
+ context Promise do
209
151
 
210
- EventMachine.run do
152
+ before(:each) do
153
+ Promise.thread_pool = EventMachineDeferProxy.new
154
+ end
211
155
 
212
- @expected = nil
213
- Promise.new{ raise StandardError }.
214
- on_error(StandardError){|ex| @expected = 1 }.
215
- on_error(StandardError){|ex| @expected = 2 }.
216
- on_error(StandardError){|ex| @expected = 3 }
217
- sleep(0.1)
218
- @expected.should eq 1
156
+ context 'fulfillment' do
219
157
 
220
- sleep(0.1)
221
- EventMachine.stop
158
+ it 'passes all arguments to the first promise in the chain' do
159
+
160
+ EventMachine.run do
161
+
162
+ @a = @b = @c = nil
163
+ p = Promise.new(1, 2, 3) do |a, b, c|
164
+ @a, @b, @c = a, b, c
165
+ end
166
+ sleep(0.1)
167
+ [@a, @b, @c].should eq [1, 2, 3]
168
+
169
+ sleep(0.1)
170
+ EventMachine.stop
171
+ end
172
+ end
173
+
174
+ it 'passes the result of each block to all its children' do
175
+
176
+ EventMachine.run do
177
+ @expected = nil
178
+ Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
179
+ sleep(0.1)
180
+ @expected.should eq 20
181
+
182
+ sleep(0.1)
183
+ EventMachine.stop
184
+ end
185
+ end
186
+
187
+ it 'sets the promise value to the result if its block' do
188
+
189
+ EventMachine.run do
190
+
191
+ p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
192
+ sleep(0.1)
193
+ p.value.should eq 40
194
+
195
+ sleep(0.1)
196
+ EventMachine.stop
197
+ end
222
198
  end
223
199
  end
224
200
 
225
- it 'passes the exception object to the matched block' do
226
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
201
+ context 'rejection' do
227
202
 
228
- EventMachine.run do
203
+ it 'sets the promise reason and error on exception' do
229
204
 
230
- @expected = nil
231
- Promise.new{ raise StandardError }.
232
- on_error(ArgumentError){|ex| @expected = ex }.
233
- on_error(LoadError){|ex| @expected = ex }.
234
- on_error(Exception){|ex| @expected = ex }
235
- sleep(0.1)
236
- @expected.should be_a(StandardError)
205
+ EventMachine.run do
237
206
 
238
- sleep(0.1)
239
- EventMachine.stop
207
+ p = Promise.new{ raise StandardError.new('Boom!') }
208
+ sleep(0.1)
209
+ p.reason.should be_a(Exception)
210
+ p.reason.should.to_s =~ /Boom!/
211
+ p.should be_rejected
212
+
213
+ sleep(0.1)
214
+ EventMachine.stop
215
+ end
216
+ end
217
+
218
+ it 'calls the first exception block with a matching class' do
219
+
220
+ EventMachine.run do
221
+
222
+ @expected = nil
223
+ Promise.new{ raise StandardError }.
224
+ on_error(StandardError){|ex| @expected = 1 }.
225
+ on_error(StandardError){|ex| @expected = 2 }.
226
+ on_error(StandardError){|ex| @expected = 3 }
227
+ sleep(0.1)
228
+ @expected.should eq 1
229
+
230
+ sleep(0.1)
231
+ EventMachine.stop
232
+ end
233
+ end
234
+
235
+ it 'passes the exception object to the matched block' do
236
+
237
+ EventMachine.run do
238
+
239
+ @expected = nil
240
+ Promise.new{ raise StandardError }.
241
+ on_error(ArgumentError){|ex| @expected = ex }.
242
+ on_error(LoadError){|ex| @expected = ex }.
243
+ on_error(Exception){|ex| @expected = ex }
244
+ sleep(0.1)
245
+ @expected.should be_a(StandardError)
246
+
247
+ sleep(0.1)
248
+ EventMachine.stop
249
+ end
240
250
  end
241
251
  end
242
252
  end
@@ -0,0 +1,200 @@
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