concurrent-ruby 0.1.1 → 0.2.0

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