concurrent-ruby 0.5.0 → 0.6.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -77
  3. data/lib/concurrent.rb +17 -2
  4. data/lib/concurrent/actor.rb +17 -0
  5. data/lib/concurrent/actor_context.rb +31 -0
  6. data/lib/concurrent/actor_ref.rb +39 -0
  7. data/lib/concurrent/agent.rb +12 -3
  8. data/lib/concurrent/async.rb +290 -0
  9. data/lib/concurrent/atomic.rb +5 -9
  10. data/lib/concurrent/cached_thread_pool.rb +39 -137
  11. data/lib/concurrent/channel/blocking_ring_buffer.rb +60 -0
  12. data/lib/concurrent/channel/buffered_channel.rb +83 -0
  13. data/lib/concurrent/channel/channel.rb +11 -0
  14. data/lib/concurrent/channel/probe.rb +19 -0
  15. data/lib/concurrent/channel/ring_buffer.rb +54 -0
  16. data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
  17. data/lib/concurrent/channel/waitable_list.rb +38 -0
  18. data/lib/concurrent/configuration.rb +92 -0
  19. data/lib/concurrent/dataflow.rb +9 -3
  20. data/lib/concurrent/delay.rb +88 -0
  21. data/lib/concurrent/exchanger.rb +31 -0
  22. data/lib/concurrent/fixed_thread_pool.rb +28 -122
  23. data/lib/concurrent/future.rb +10 -5
  24. data/lib/concurrent/immediate_executor.rb +3 -2
  25. data/lib/concurrent/ivar.rb +2 -1
  26. data/lib/concurrent/java_cached_thread_pool.rb +45 -0
  27. data/lib/concurrent/java_fixed_thread_pool.rb +37 -0
  28. data/lib/concurrent/java_thread_pool_executor.rb +194 -0
  29. data/lib/concurrent/per_thread_executor.rb +23 -0
  30. data/lib/concurrent/postable.rb +2 -0
  31. data/lib/concurrent/processor_count.rb +125 -0
  32. data/lib/concurrent/promise.rb +42 -18
  33. data/lib/concurrent/ruby_cached_thread_pool.rb +37 -0
  34. data/lib/concurrent/ruby_fixed_thread_pool.rb +31 -0
  35. data/lib/concurrent/ruby_thread_pool_executor.rb +268 -0
  36. data/lib/concurrent/ruby_thread_pool_worker.rb +69 -0
  37. data/lib/concurrent/simple_actor_ref.rb +124 -0
  38. data/lib/concurrent/thread_local_var.rb +1 -1
  39. data/lib/concurrent/thread_pool_executor.rb +30 -0
  40. data/lib/concurrent/timer_task.rb +13 -10
  41. data/lib/concurrent/tvar.rb +212 -0
  42. data/lib/concurrent/utilities.rb +1 -0
  43. data/lib/concurrent/version.rb +1 -1
  44. data/spec/concurrent/actor_context_spec.rb +37 -0
  45. data/spec/concurrent/actor_ref_shared.rb +313 -0
  46. data/spec/concurrent/actor_spec.rb +9 -1
  47. data/spec/concurrent/agent_spec.rb +97 -96
  48. data/spec/concurrent/async_spec.rb +320 -0
  49. data/spec/concurrent/cached_thread_pool_shared.rb +137 -0
  50. data/spec/concurrent/channel/blocking_ring_buffer_spec.rb +149 -0
  51. data/spec/concurrent/channel/buffered_channel_spec.rb +151 -0
  52. data/spec/concurrent/channel/channel_spec.rb +37 -0
  53. data/spec/concurrent/channel/probe_spec.rb +49 -0
  54. data/spec/concurrent/channel/ring_buffer_spec.rb +126 -0
  55. data/spec/concurrent/channel/unbuffered_channel_spec.rb +132 -0
  56. data/spec/concurrent/configuration_spec.rb +134 -0
  57. data/spec/concurrent/dataflow_spec.rb +109 -27
  58. data/spec/concurrent/delay_spec.rb +77 -0
  59. data/spec/concurrent/exchanger_spec.rb +66 -0
  60. data/spec/concurrent/fixed_thread_pool_shared.rb +136 -0
  61. data/spec/concurrent/future_spec.rb +60 -51
  62. data/spec/concurrent/global_thread_pool_shared.rb +33 -0
  63. data/spec/concurrent/immediate_executor_spec.rb +4 -25
  64. data/spec/concurrent/ivar_spec.rb +36 -23
  65. data/spec/concurrent/java_cached_thread_pool_spec.rb +64 -0
  66. data/spec/concurrent/java_fixed_thread_pool_spec.rb +64 -0
  67. data/spec/concurrent/java_thread_pool_executor_spec.rb +71 -0
  68. data/spec/concurrent/obligation_shared.rb +32 -20
  69. data/spec/concurrent/{global_thread_pool_spec.rb → per_thread_executor_spec.rb} +9 -13
  70. data/spec/concurrent/processor_count_spec.rb +20 -0
  71. data/spec/concurrent/promise_spec.rb +29 -41
  72. data/spec/concurrent/ruby_cached_thread_pool_spec.rb +69 -0
  73. data/spec/concurrent/ruby_fixed_thread_pool_spec.rb +39 -0
  74. data/spec/concurrent/ruby_thread_pool_executor_spec.rb +183 -0
  75. data/spec/concurrent/simple_actor_ref_spec.rb +219 -0
  76. data/spec/concurrent/thread_pool_class_cast_spec.rb +40 -0
  77. data/spec/concurrent/thread_pool_executor_shared.rb +155 -0
  78. data/spec/concurrent/thread_pool_shared.rb +98 -36
  79. data/spec/concurrent/tvar_spec.rb +137 -0
  80. data/spec/spec_helper.rb +4 -0
  81. data/spec/support/functions.rb +4 -0
  82. metadata +85 -20
  83. data/lib/concurrent/cached_thread_pool/worker.rb +0 -91
  84. data/lib/concurrent/channel.rb +0 -63
  85. data/lib/concurrent/fixed_thread_pool/worker.rb +0 -54
  86. data/lib/concurrent/global_thread_pool.rb +0 -42
  87. data/spec/concurrent/cached_thread_pool_spec.rb +0 -101
  88. data/spec/concurrent/channel_spec.rb +0 -86
  89. data/spec/concurrent/fixed_thread_pool_spec.rb +0 -92
  90. data/spec/concurrent/uses_global_thread_pool_shared.rb +0 -64
@@ -0,0 +1,320 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe Async do
6
+
7
+ let(:executor) { PerThreadExecutor.new }
8
+
9
+ let(:async_class) do
10
+ Class.new do
11
+ include Concurrent::Async
12
+ attr_accessor :accessor
13
+ def echo(msg)
14
+ msg
15
+ end
16
+ def gather(first, second = nil)
17
+ return first, second
18
+ end
19
+ def boom(ex = StandardError.new)
20
+ raise ex
21
+ end
22
+ def wait(seconds)
23
+ sleep(seconds)
24
+ end
25
+ def with_block
26
+ yield
27
+ end
28
+ end
29
+ end
30
+
31
+ subject do
32
+ obj = async_class.new
33
+ obj.executor = executor
34
+ obj
35
+ end
36
+
37
+ context '#validate_argc' do
38
+
39
+ subject do
40
+ Class.new {
41
+ def zero() nil; end
42
+ def three(a, b, c, &block) nil; end
43
+ def two_plus_two(a, b, c=nil, d=nil, &block) nil; end
44
+ def many(*args, &block) nil; end
45
+ }.new
46
+ end
47
+
48
+ it 'raises an exception when the method is not defined' do
49
+ expect {
50
+ Async::validate_argc(subject, :bogus)
51
+ }.to raise_error(StandardError)
52
+ end
53
+
54
+ it 'raises an exception for too many args on a zero arity method' do
55
+ expect {
56
+ Async::validate_argc(subject, :zero, 1, 2, 3)
57
+ }.to raise_error(ArgumentError)
58
+ end
59
+
60
+ it 'does not raise an exception for correct zero arity' do
61
+ expect {
62
+ Async::validate_argc(subject, :zero)
63
+ }.not_to raise_error
64
+ end
65
+
66
+ it 'raises an exception for too many args on a method with positive arity' do
67
+ expect {
68
+ Async::validate_argc(subject, :three, 1, 2, 3, 4)
69
+ }.to raise_error(ArgumentError)
70
+ end
71
+
72
+ it 'raises an exception for too few args on a method with positive arity' do
73
+ expect {
74
+ Async::validate_argc(subject, :three, 1, 2)
75
+ }.to raise_error(ArgumentError)
76
+ end
77
+
78
+ it 'does not raise an exception for correct positive arity' do
79
+ expect {
80
+ Async::validate_argc(subject, :three, 1, 2, 3)
81
+ }.not_to raise_error
82
+ end
83
+
84
+ it 'raises an exception for too few args on a method with negative arity' do
85
+ expect {
86
+ Async::validate_argc(subject, :two_plus_two, 1)
87
+ }.to raise_error(ArgumentError)
88
+ end
89
+
90
+ it 'does not raise an exception for correct negative arity' do
91
+ expect {
92
+ Async::validate_argc(subject, :two_plus_two, 1, 2)
93
+ Async::validate_argc(subject, :two_plus_two, 1, 2, 3, 4)
94
+ Async::validate_argc(subject, :two_plus_two, 1, 2, 3, 4, 5, 6)
95
+
96
+ Async::validate_argc(subject, :many)
97
+ Async::validate_argc(subject, :many, 1, 2)
98
+ Async::validate_argc(subject, :many, 1, 2, 3, 4)
99
+ }.not_to raise_error
100
+ end
101
+ end
102
+
103
+ context 'executor' do
104
+
105
+ it 'returns the default executor when #executor= has never been called' do
106
+ Concurrent.configuration.should_receive(:global_task_pool).
107
+ and_return(ImmediateExecutor.new)
108
+ subject = async_class.new
109
+ subject.async.echo(:foo)
110
+ end
111
+
112
+ it 'returns the memo after #executor= has been called' do
113
+ executor = ImmediateExecutor.new
114
+ executor.should_receive(:post)
115
+ subject = async_class.new
116
+ subject.executor = executor
117
+ subject.async.echo(:foo)
118
+ end
119
+
120
+ it 'raises an exception if #executor= is called multiple times' do
121
+ executor = ImmediateExecutor.new
122
+ subject = async_class.new
123
+ subject.executor = executor
124
+ expect {
125
+ subject.executor = executor
126
+ }.to raise_error(ArgumentError)
127
+ end
128
+ end
129
+
130
+ context '#async' do
131
+
132
+ it 'raises an error when calling a method that does not exist' do
133
+ expect {
134
+ subject.async.bogus
135
+ }.to raise_error(StandardError)
136
+ end
137
+
138
+ it 'raises an error when passing too few arguments' do
139
+ expect {
140
+ subject.async.gather
141
+ }.to raise_error(ArgumentError)
142
+ end
143
+
144
+ it 'raises an error when pasing too many arguments (arity >= 0)' do
145
+ expect {
146
+ subject.async.echo(1, 2, 3, 4, 5)
147
+ }.to raise_error(StandardError)
148
+ end
149
+
150
+ it 'returns a :pending Future' do
151
+ val = subject.async.wait(5)
152
+ val.should be_a Concurrent::Future
153
+ val.should be_pending
154
+ end
155
+
156
+ it 'runs the Future on the memoized executor' do
157
+ executor = ImmediateExecutor.new
158
+ executor.should_receive(:post).with(any_args)
159
+ subject = async_class.new
160
+ subject.executor = executor
161
+ subject.async.echo(:foo)
162
+ end
163
+
164
+ it 'sets the value on success' do
165
+ val = subject.async.echo(:foo)
166
+ val.value.should eq :foo
167
+ val.should be_fulfilled
168
+ end
169
+
170
+ it 'sets the reason on failure' do
171
+ ex = ArgumentError.new
172
+ val = subject.async.boom(ex)
173
+ sleep(0.1)
174
+ val.reason.should eq ex
175
+ val.should be_rejected
176
+ end
177
+
178
+ it 'sets the reason when giving too many optional arguments' do
179
+ val = subject.async.gather(1, 2, 3, 4, 5)
180
+ sleep(0.1)
181
+ val.reason.should be_a StandardError
182
+ val.should be_rejected
183
+ end
184
+
185
+ it 'supports attribute accessors' do
186
+ subject.async.accessor = :foo
187
+ sleep(0.1)
188
+ val = subject.async.accessor
189
+ sleep(0.1)
190
+ val.value.should eq :foo
191
+ subject.accessor.should eq :foo
192
+ end
193
+
194
+ it 'supports methods with blocks' do
195
+ val = subject.async.with_block{ :foo }
196
+ sleep(0.1)
197
+ val.value.should eq :foo
198
+ end
199
+
200
+ it 'is aliased as #future' do
201
+ val = subject.future.wait(5)
202
+ val.should be_a Concurrent::Future
203
+ end
204
+
205
+ context '#method_missing' do
206
+
207
+ it 'defines the method after the first call' do
208
+ expect { subject.async.method(:echo) }.to raise_error(NameError)
209
+ subject.async.echo(:foo)
210
+ sleep(0.1)
211
+ expect { subject.async.method(:echo) }.not_to raise_error
212
+ end
213
+
214
+ it 'does not define the method on name/arity exception' do
215
+ expect { subject.async.method(:bogus) }.to raise_error(NameError)
216
+ expect { subject.async.bogus }.to raise_error(NameError)
217
+ expect { subject.async.method(:bogus) }.to raise_error(NameError)
218
+ end
219
+ end
220
+ end
221
+
222
+ context '#await' do
223
+
224
+ it 'raises an error when calling a method that does not exist' do
225
+ expect {
226
+ subject.await.bogus
227
+ }.to raise_error(StandardError)
228
+ end
229
+
230
+ it 'raises an error when passing too few arguments' do
231
+ expect {
232
+ subject.await.gather
233
+ }.to raise_error(ArgumentError)
234
+ end
235
+
236
+ it 'raises an error when pasing too many arguments (arity >= 0)' do
237
+ expect {
238
+ subject.await.echo(1, 2, 3, 4, 5)
239
+ }.to raise_error(StandardError)
240
+ end
241
+
242
+ it 'returns a :fulfilled IVar' do
243
+ val = subject.await.echo(5)
244
+ val.should be_a Concurrent::IVar
245
+ val.should be_fulfilled
246
+ end
247
+
248
+ it 'sets the value on success' do
249
+ val = subject.await.echo(:foo)
250
+ val.value.should eq :foo
251
+ val.should be_fulfilled
252
+ end
253
+
254
+ it 'sets the reason on failure' do
255
+ ex = ArgumentError.new
256
+ val = subject.await.boom(ex)
257
+ val.reason.should eq ex
258
+ val.should be_rejected
259
+ end
260
+
261
+ it 'sets the reason when giving too many optional arguments' do
262
+ val = subject.await.gather(1, 2, 3, 4, 5)
263
+ val.reason.should be_a StandardError
264
+ val.should be_rejected
265
+ end
266
+
267
+ it 'supports attribute accessors' do
268
+ subject.await.accessor = :foo
269
+ val = subject.await.accessor
270
+ val.value.should eq :foo
271
+ subject.accessor.should eq :foo
272
+ end
273
+
274
+ it 'supports methods with blocks' do
275
+ val = subject.await.with_block{ :foo }
276
+ val.value.should eq :foo
277
+ end
278
+
279
+ it 'is aliased as #delay' do
280
+ val = subject.delay.echo(5)
281
+ val.should be_a Concurrent::IVar
282
+ end
283
+
284
+ context '#method_missing' do
285
+
286
+ it 'defines the method after the first call' do
287
+ expect { subject.await.method(:echo) }.to raise_error(NameError)
288
+ subject.await.echo(:foo)
289
+ expect { subject.await.method(:echo) }.not_to raise_error
290
+ end
291
+
292
+ it 'does not define the method on name/arity exception' do
293
+ expect { subject.await.method(:bogus) }.to raise_error(NameError)
294
+ expect { subject.await.bogus }.to raise_error(NameError)
295
+ expect { subject.await.method(:bogus) }.to raise_error(NameError)
296
+ end
297
+ end
298
+ end
299
+
300
+ context 'locking' do
301
+
302
+ it 'uses the same mutex for both #async and #await' do
303
+ object = Class.new {
304
+ include Concurrent::Async
305
+ attr_reader :bucket
306
+ def gather(seconds, first, *rest)
307
+ sleep(seconds)
308
+ (@bucket ||= []).concat([first])
309
+ @bucket.concat(rest)
310
+ end
311
+ }.new
312
+
313
+ object.async.gather(0.5, :a, :b)
314
+ sleep(0.1)
315
+ object.await.gather(0, :c, :d)
316
+ object.bucket.should eq [:a, :b, :c, :d]
317
+ end
318
+ end
319
+ end
320
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+ require_relative 'thread_pool_shared'
3
+
4
+ share_examples_for :cached_thread_pool do
5
+
6
+ let!(:max_threads){ 5 }
7
+ subject do
8
+ described_class.new(
9
+ max_threads: max_threads,
10
+ overflow_policy: :discard
11
+ )
12
+ end
13
+
14
+ after(:each) do
15
+ subject.kill
16
+ sleep(0.1)
17
+ end
18
+
19
+ it_should_behave_like :thread_pool
20
+
21
+ context '#initialize' do
22
+
23
+ it 'raises an exception when the pool idletime is less than one' do
24
+ lambda {
25
+ described_class.new(idletime: 0)
26
+ }.should raise_error(ArgumentError)
27
+ end
28
+
29
+ it 'raises an exception when the pool size is less than one' do
30
+ lambda {
31
+ described_class.new(max_threads: 0)
32
+ }.should raise_error(ArgumentError)
33
+ end
34
+
35
+ it 'sets :max_length to DEFAULT_MAX_POOL_SIZE when not given' do
36
+ described_class.new.max_length.should eq described_class::DEFAULT_MAX_POOL_SIZE
37
+ end
38
+ end
39
+
40
+ context '#min_length' do
41
+
42
+ it 'returns zero on creation' do
43
+ subject.min_length.should eq 0
44
+ end
45
+
46
+ it 'returns zero while running' do
47
+ 10.times{ subject.post{ nil } }
48
+ sleep(0.1)
49
+ subject.min_length.should eq 0
50
+ end
51
+
52
+ it 'returns zero once shutdown' do
53
+ 10.times{ subject.post{ nil } }
54
+ sleep(0.1)
55
+ subject.shutdown
56
+ subject.wait_for_termination(1)
57
+ subject.min_length.should eq 0
58
+ end
59
+ end
60
+
61
+ context '#max_length' do
62
+
63
+ it 'returns :max_length on creation' do
64
+ subject.max_length.should eq max_threads
65
+ end
66
+
67
+ it 'returns :max_length while running' do
68
+ 10.times{ subject.post{ nil } }
69
+ sleep(0.1)
70
+ subject.max_length.should eq max_threads
71
+ end
72
+
73
+ it 'returns :max_length once shutdown' do
74
+ 10.times{ subject.post{ nil } }
75
+ sleep(0.1)
76
+ subject.shutdown
77
+ subject.wait_for_termination(1)
78
+ subject.max_length.should eq max_threads
79
+ end
80
+ end
81
+
82
+ context '#largest_length' do
83
+
84
+ it 'returns zero on creation' do
85
+ subject.largest_length.should eq 0
86
+ end
87
+
88
+ it 'returns a non-zero number once tasks have been received' do
89
+ 10.times{ subject.post{ sleep(0.1) } }
90
+ sleep(0.1)
91
+ subject.largest_length.should > 0
92
+ end
93
+
94
+ it 'returns a non-zero number after shutdown if tasks have been received' do
95
+ 10.times{ subject.post{ sleep(0.1) } }
96
+ sleep(0.1)
97
+ subject.shutdown
98
+ subject.wait_for_termination(1)
99
+ subject.largest_length.should > 0
100
+ end
101
+ end
102
+
103
+ context '#status' do
104
+
105
+ it 'returns an array' do
106
+ subject.stub(:warn)
107
+ subject.status.should be_kind_of(Array)
108
+ end
109
+ end
110
+
111
+ context '#idletime' do
112
+
113
+ subject{ described_class.new(idletime: 42) }
114
+
115
+ it 'returns the thread idletime' do
116
+ subject.idletime.should eq 42
117
+ end
118
+ end
119
+
120
+ context 'worker creation and caching' do
121
+
122
+ subject do
123
+ described_class.new(
124
+ idletime: 1,
125
+ max_threads: 5,
126
+ overflow_policy: :discard
127
+ )
128
+ end
129
+
130
+ it 'never creates more than :max_threads threads' do
131
+ 100.times{ subject << proc{ sleep(1) } }
132
+ sleep(0.1)
133
+ subject.length.should eq 5
134
+ subject.kill
135
+ end
136
+ end
137
+ end