concurrent-ruby 0.3.0.pre.1 → 0.3.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -33
  3. data/lib/concurrent.rb +5 -11
  4. data/lib/concurrent/{channel.rb → actor.rb} +14 -18
  5. data/lib/concurrent/agent.rb +5 -4
  6. data/lib/concurrent/cached_thread_pool.rb +116 -25
  7. data/lib/concurrent/cached_thread_pool/worker.rb +91 -0
  8. data/lib/concurrent/event.rb +13 -14
  9. data/lib/concurrent/event_machine_defer_proxy.rb +0 -1
  10. data/lib/concurrent/executor.rb +0 -1
  11. data/lib/concurrent/fixed_thread_pool.rb +111 -14
  12. data/lib/concurrent/fixed_thread_pool/worker.rb +54 -0
  13. data/lib/concurrent/future.rb +0 -2
  14. data/lib/concurrent/global_thread_pool.rb +21 -3
  15. data/lib/concurrent/goroutine.rb +1 -5
  16. data/lib/concurrent/obligation.rb +0 -19
  17. data/lib/concurrent/promise.rb +2 -5
  18. data/lib/concurrent/runnable.rb +2 -8
  19. data/lib/concurrent/supervisor.rb +9 -4
  20. data/lib/concurrent/utilities.rb +24 -0
  21. data/lib/concurrent/version.rb +1 -1
  22. data/md/agent.md +3 -3
  23. data/md/future.md +4 -4
  24. data/md/promise.md +15 -25
  25. data/md/thread_pool.md +9 -8
  26. data/spec/concurrent/actor_spec.rb +377 -0
  27. data/spec/concurrent/agent_spec.rb +2 -1
  28. data/spec/concurrent/cached_thread_pool_spec.rb +19 -29
  29. data/spec/concurrent/event_machine_defer_proxy_spec.rb +1 -1
  30. data/spec/concurrent/event_spec.rb +1 -1
  31. data/spec/concurrent/executor_spec.rb +0 -8
  32. data/spec/concurrent/fixed_thread_pool_spec.rb +27 -16
  33. data/spec/concurrent/future_spec.rb +0 -13
  34. data/spec/concurrent/global_thread_pool_spec.rb +73 -0
  35. data/spec/concurrent/goroutine_spec.rb +0 -15
  36. data/spec/concurrent/obligation_shared.rb +1 -38
  37. data/spec/concurrent/promise_spec.rb +28 -47
  38. data/spec/concurrent/supervisor_spec.rb +1 -2
  39. data/spec/concurrent/thread_pool_shared.rb +28 -7
  40. data/spec/concurrent/utilities_spec.rb +50 -0
  41. data/spec/spec_helper.rb +0 -1
  42. data/spec/support/functions.rb +17 -0
  43. metadata +12 -27
  44. data/lib/concurrent/functions.rb +0 -105
  45. data/lib/concurrent/null_thread_pool.rb +0 -25
  46. data/lib/concurrent/thread_pool.rb +0 -149
  47. data/md/reactor.md +0 -32
  48. data/spec/concurrent/channel_spec.rb +0 -446
  49. data/spec/concurrent/functions_spec.rb +0 -197
  50. data/spec/concurrent/null_thread_pool_spec.rb +0 -78
@@ -1,149 +0,0 @@
1
- require 'thread'
2
- require 'functional'
3
-
4
- require 'concurrent/event'
5
-
6
- behavior_info(:global_thread_pool,
7
- post: -1,
8
- :<< => 1)
9
-
10
- module Concurrent
11
-
12
- class AbstractThreadPool
13
-
14
- WorkerContext = Struct.new(:status, :idletime, :thread)
15
-
16
- MIN_POOL_SIZE = 1
17
- MAX_POOL_SIZE = 256
18
-
19
- attr_accessor :max_threads
20
-
21
- def initialize(opts = {})
22
- @max_threads = opts[:max_threads] || opts[:max] || MAX_POOL_SIZE
23
- if @max_threads < MIN_POOL_SIZE || @max_threads > MAX_POOL_SIZE
24
- raise ArgumentError.new("pool size must be from #{MIN_POOL_SIZE} to #{MAX_POOL_SIZE}")
25
- end
26
-
27
- @state = :running
28
- @mutex ||= Mutex.new
29
- @terminator ||= Event.new
30
- @pool ||= []
31
- @queue ||= Queue.new
32
- @working = 0
33
- end
34
-
35
- def running?
36
- return @state == :running
37
- end
38
-
39
- def shutdown
40
- @mutex.synchronize do
41
- @collector.kill if @collector && @collector.status
42
- if @pool.empty?
43
- @state = :shutdown
44
- @terminator.set
45
- else
46
- @state = :shuttingdown
47
- @pool.size.times{ @queue << :stop }
48
- end
49
- end
50
- Thread.pass
51
- end
52
-
53
- def wait_for_termination(timeout = nil)
54
- return @terminator.wait(timeout)
55
- end
56
-
57
- def <<(block)
58
- self.post(&block)
59
- return self
60
- end
61
-
62
- def kill
63
- @mutex.synchronize do
64
- @collector.kill if @collector && @collector.status
65
- @state = :shuttingdown
66
- @pool.each{|t| Thread.kill(t.thread) }
67
- @terminator.set
68
- end
69
- Thread.pass
70
- end
71
-
72
- def size
73
- return @mutex.synchronize do
74
- @state == :running ? @pool.length : 0
75
- end
76
- end
77
- alias_method :length, :size
78
-
79
- def status
80
- @mutex.synchronize do
81
- @pool.collect do |worker|
82
- [
83
- worker.status,
84
- worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
85
- worker.thread.status
86
- ]
87
- end
88
- end
89
- end
90
-
91
- protected
92
-
93
- def timestamp
94
- return Time.now.to_i
95
- end
96
-
97
- private
98
-
99
- def create_worker_thread
100
- context = WorkerContext.new(:idle, timestamp, nil)
101
-
102
- context.thread = Thread.new do
103
- Thread.current.abort_on_exception = false
104
- loop do
105
- task = @queue.pop
106
- if task == :stop
107
- @mutex.synchronize do
108
- context.status = :stopping
109
- end
110
- break
111
- else
112
- @mutex.synchronize do
113
- context.status = :working
114
- @working += 1
115
- end
116
- task.last.call(*task.first)
117
- @mutex.synchronize do
118
- @working -= 1
119
- context.status = :idle
120
- context.idletime = timestamp
121
- end
122
- end
123
- end
124
- @mutex.synchronize do
125
- @pool.delete(context)
126
- if @pool.empty? && @state != :running
127
- @terminator.set
128
- @state = :shutdown
129
- end
130
- end
131
- end
132
-
133
- Thread.pass
134
- run_garbage_collector unless @collector && @collector.alive?
135
- return context
136
- end
137
-
138
- def run_garbage_collector
139
- @collector = Thread.new do
140
- Thread.current.abort_on_exception = false
141
- loop do
142
- sleep(1)
143
- @mutex.synchronize { collect_garbage }
144
- end
145
- end
146
- Thread.pass
147
- end
148
- end
149
- end
@@ -1,32 +0,0 @@
1
- # Event
2
-
3
- TBD...
4
-
5
- ## Copyright
6
-
7
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
- It is free software and may be redistributed under the terms specified in the LICENSE file.
9
-
10
- ## License
11
-
12
- Released under the MIT license.
13
-
14
- http://www.opensource.org/licenses/mit-license.php
15
-
16
- > Permission is hereby granted, free of charge, to any person obtaining a copy
17
- > of this software and associated documentation files (the "Software"), to deal
18
- > in the Software without restriction, including without limitation the rights
19
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
- > copies of the Software, and to permit persons to whom the Software is
21
- > furnished to do so, subject to the following conditions:
22
- >
23
- > The above copyright notice and this permission notice shall be included in
24
- > all copies or substantial portions of the Software.
25
- >
26
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
- > THE SOFTWARE.
@@ -1,446 +0,0 @@
1
- require 'spec_helper'
2
- require_relative 'runnable_shared'
3
-
4
- module Concurrent
5
-
6
- describe Channel do
7
-
8
- subject { Channel.new }
9
- let(:runnable) { Channel }
10
-
11
- it_should_behave_like :runnable
12
-
13
- after(:each) do
14
- subject.stop
15
- @thread.kill unless @thread.nil?
16
- sleep(0.1)
17
- end
18
-
19
- context '#post' do
20
-
21
- it 'returns false when not running' do
22
- subject.post.should be_false
23
- end
24
-
25
- it 'pushes a message onto the queue' do
26
- @expected = false
27
- channel = Channel.new{|msg| @expected = msg }
28
- @thread = Thread.new{ channel.run }
29
- @thread.join(0.1)
30
- channel.post(true)
31
- @thread.join(0.1)
32
- @expected.should be_true
33
- channel.stop
34
- end
35
-
36
- it 'returns the current size of the queue' do
37
- channel = Channel.new{|msg| sleep }
38
- @thread = Thread.new{ channel.run }
39
- @thread.join(0.1)
40
- channel.post(true).should == 1
41
- @thread.join(0.1)
42
- channel.post(true).should == 1
43
- @thread.join(0.1)
44
- channel.post(true).should == 2
45
- channel.stop
46
- end
47
-
48
- it 'is aliased a <<' do
49
- @expected = false
50
- channel = Channel.new{|msg| @expected = msg }
51
- @thread = Thread.new{ channel.run }
52
- @thread.join(0.1)
53
- channel << true
54
- @thread.join(0.1)
55
- @expected.should be_true
56
- channel.stop
57
- end
58
- end
59
-
60
- context '#run' do
61
-
62
- it 'empties the queue' do
63
- @thread = Thread.new{ subject.run }
64
- @thread.join(0.1)
65
- q = subject.instance_variable_get(:@queue)
66
- q.size.should == 0
67
- end
68
- end
69
-
70
- context '#stop' do
71
-
72
- it 'empties the queue' do
73
- channel = Channel.new{|msg| sleep }
74
- @thread = Thread.new{ channel.run }
75
- 10.times { channel.post(true) }
76
- @thread.join(0.1)
77
- channel.stop
78
- @thread.join(0.1)
79
- q = channel.instance_variable_get(:@queue)
80
- if q.size >= 1
81
- q.pop.should == :stop
82
- else
83
- q.size.should == 0
84
- end
85
- end
86
-
87
- it 'pushes a :stop message onto the queue' do
88
- @thread = Thread.new{ subject.run }
89
- @thread.join(0.1)
90
- q = subject.instance_variable_get(:@queue)
91
- q.should_receive(:push).once.with(:stop)
92
- subject.stop
93
- @thread.join(0.1)
94
- end
95
- end
96
-
97
- context 'message handling' do
98
-
99
- it 'runs the constructor block once for every message' do
100
- @expected = 0
101
- channel = Channel.new{|msg| @expected += 1 }
102
- @thread = Thread.new{ channel.run }
103
- @thread.join(0.1)
104
- 10.times { channel.post(true) }
105
- @thread.join(0.1)
106
- @expected.should eq 10
107
- channel.stop
108
- end
109
-
110
- it 'passes the message to the block' do
111
- @expected = []
112
- channel = Channel.new{|msg| @expected << msg }
113
- @thread = Thread.new{ channel.run }
114
- @thread.join(0.1)
115
- 10.times {|i| channel.post(i) }
116
- @thread.join(0.1)
117
- channel.stop
118
- @expected.should eq (0..9).to_a
119
- end
120
- end
121
-
122
- context 'exception handling' do
123
-
124
- it 'supresses exceptions thrown when handling messages' do
125
- channel = Channel.new{|msg| raise StandardError }
126
- @thread = Thread.new{ channel.run }
127
- expect {
128
- @thread.join(0.1)
129
- 10.times { channel.post(true) }
130
- }.not_to raise_error
131
- channel.stop
132
- end
133
-
134
- it 'calls the errorback with the time, message, and exception' do
135
- @expected = []
136
- errorback = proc{|*args| @expected = args }
137
- channel = Channel.new(errorback){|msg| raise StandardError }
138
- @thread = Thread.new{ channel.run }
139
- @thread.join(0.1)
140
- channel.post(42)
141
- @thread.join(0.1)
142
- @expected[0].should be_a(Time)
143
- @expected[1].should == [42]
144
- @expected[2].should be_a(StandardError)
145
- channel.stop
146
- end
147
- end
148
-
149
- context 'observer notification' do
150
-
151
- let(:observer) do
152
- Class.new {
153
- attr_reader :notice
154
- def update(*args) @notice = args; end
155
- }.new
156
- end
157
-
158
- it 'notifies observers when a message is successfully handled' do
159
- observer.should_receive(:update).exactly(10).times.with(any_args())
160
- subject.add_observer(observer)
161
- @thread = Thread.new{ subject.run }
162
- @thread.join(0.1)
163
- 10.times { subject.post(true) }
164
- @thread.join(0.1)
165
- end
166
-
167
- it 'does not notify observers when a message raises an exception' do
168
- observer.should_not_receive(:update).with(any_args())
169
- channel = Channel.new{|msg| raise StandardError }
170
- channel.add_observer(observer)
171
- @thread = Thread.new{ channel.run }
172
- @thread.join(0.1)
173
- 10.times { channel.post(true) }
174
- @thread.join(0.1)
175
- channel.stop
176
- end
177
-
178
- it 'passes the time, message, and result to the observer' do
179
- channel = Channel.new{|*msg| msg }
180
- channel.add_observer(observer)
181
- @thread = Thread.new{ channel.run }
182
- @thread.join(0.1)
183
- channel.post(42)
184
- @thread.join(0.1)
185
- observer.notice[0].should be_a(Time)
186
- observer.notice[1].should == [42]
187
- observer.notice[2].should == [42]
188
- channel.stop
189
- end
190
- end
191
-
192
- context '#pool' do
193
-
194
- let(:clazz){ Class.new(Channel) }
195
-
196
- it 'raises an exception if the count is zero or less' do
197
- expect {
198
- clazz.pool(0)
199
- }.to raise_error(ArgumentError)
200
- end
201
-
202
- it 'creates the requested number of channels' do
203
- mailbox, channels = clazz.pool(5)
204
- channels.size.should == 5
205
- end
206
-
207
- it 'passes the errorback to each channel' do
208
- errorback = proc{ nil }
209
- clazz.should_receive(:new).with(errorback)
210
- clazz.pool(1, errorback)
211
- end
212
-
213
- it 'passes the block to each channel' do
214
- block = proc{ nil }
215
- clazz.should_receive(:new).with(anything(), &block)
216
- clazz.pool(1, nil, &block)
217
- end
218
-
219
- it 'gives all channels the same mailbox' do
220
- mailbox, channels = clazz.pool(2)
221
- mbox1 = channels.first.instance_variable_get(:@queue)
222
- mbox2 = channels.last.instance_variable_get(:@queue)
223
- mbox1.should eq mbox2
224
- end
225
-
226
- it 'returns a Poolbox as the first retval' do
227
- mailbox, channels = clazz.pool(2)
228
- mailbox.should be_a(Channel::Poolbox)
229
- end
230
-
231
- it 'gives the Poolbox the same mailbox as the channels' do
232
- mailbox, channels = clazz.pool(1)
233
- mbox1 = mailbox.instance_variable_get(:@queue)
234
- mbox2 = channels.first.instance_variable_get(:@queue)
235
- mbox1.should eq mbox2
236
- end
237
-
238
- it 'returns an array of channels as the second retval' do
239
- mailbox, channels = clazz.pool(2)
240
- channels.each do |channel|
241
- channel.should be_a(clazz)
242
- end
243
- end
244
-
245
- it 'posts to the mailbox with Poolbox#post' do
246
- @expected = false
247
- mailbox, channels = clazz.pool(1){|msg| @expected = true }
248
- @thread = Thread.new{ channels.first.run }
249
- sleep(0.1)
250
- mailbox.post(42)
251
- sleep(0.1)
252
- channels.each{|channel| channel.stop }
253
- @thread.kill
254
- @expected.should be_true
255
- end
256
-
257
- it 'posts to the mailbox with Poolbox#<<' do
258
- @expected = false
259
- mailbox, channels = clazz.pool(1){|msg| @expected = true }
260
- @thread = Thread.new{ channels.first.run }
261
- sleep(0.1)
262
- mailbox << 42
263
- sleep(0.1)
264
- channels.each{|channel| channel.stop }
265
- @thread.kill
266
- @expected.should be_true
267
- end
268
- end
269
-
270
- context 'subclassing' do
271
-
272
- after(:each) do
273
- @thread.kill unless @thread.nil?
274
- end
275
-
276
- context '#pool' do
277
-
278
- it 'creates channels of the appropriate subclass' do
279
- actor = Class.new(Channel)
280
- mailbox, channels = actor.pool(1)
281
- channels.first.should be_a(actor)
282
- end
283
- end
284
-
285
- context '#receive overloading' do
286
-
287
- let(:actor) do
288
- Class.new(Channel) {
289
- attr_reader :last_message
290
- def receive(*message)
291
- @last_message = message
292
- end
293
- }
294
- end
295
-
296
- it 'ignores the constructor block' do
297
- @expected = false
298
- channel = actor.new{|*args| @expected = true }
299
- @thread = Thread.new{ channel.run }
300
- @thread.join(0.1)
301
- channel.post(:foo)
302
- @thread.join(0.1)
303
- @expected.should be_false
304
- channel.stop
305
- end
306
-
307
- it 'uses the subclass receive implementation' do
308
- channel = actor.new{|*args| @expected = true }
309
- @thread = Thread.new{ channel.run }
310
- @thread.join(0.1)
311
- channel.post(:foo)
312
- @thread.join(0.1)
313
- channel.last_message.should eq [:foo]
314
- channel.stop
315
- end
316
- end
317
-
318
- context '#receive pattern matching' do
319
-
320
- let(:actor) do
321
- Class.new(Channel) {
322
- include PatternMatching
323
- attr_reader :last_message
324
- defn(:receive, :foo){|*args| @last_message = 'FOO' }
325
- defn(:receive, :foo, :bar){|_, _| @last_message = 'FUBAR'}
326
- }
327
- end
328
-
329
- it 'recognizes #defn pattern matches' do
330
- channel = actor.new
331
- @thread = Thread.new{ channel.run }
332
- @thread.join(0.1)
333
-
334
- channel.post(:foo)
335
- @thread.join(0.1)
336
- channel.last_message.should eq 'FOO'
337
-
338
- channel.post(:foo, :bar)
339
- @thread.join(0.1)
340
- channel.last_message.should eq 'FUBAR'
341
-
342
- channel.stop
343
- end
344
-
345
- it 'falls back to the superclass #receive on no match' do
346
- @expected = false
347
- channel = actor.new{|*args| @expected = true }
348
- @thread = Thread.new{ channel.run }
349
- @thread.join(0.1)
350
-
351
- channel.post(1, 2, 3, 4, 5)
352
- @thread.join(0.1)
353
- @expected.should be_true
354
-
355
- channel.stop
356
- end
357
- end
358
-
359
- context '#on_error overloading' do
360
-
361
- let(:actor) do
362
- Class.new(Channel) {
363
- attr_reader :last_error
364
- def receive(*message)
365
- raise StandardError
366
- end
367
- def on_error(*args)
368
- @last_error = args
369
- end
370
- }
371
- end
372
-
373
- it 'ignores the constructor errorback' do
374
- @expected = false
375
- errorback = proc{|*args| @expected = true }
376
- channel = actor.new(errorback)
377
- @thread = Thread.new{ channel.run }
378
- @thread.join(0.1)
379
- channel.post(true)
380
- @thread.join(0.1)
381
- @expected.should be_false
382
- channel.stop
383
- end
384
-
385
- it 'uses the subclass #on_error implementation' do
386
- channel = actor.new
387
- @thread = Thread.new{ channel.run }
388
- @thread.join(0.1)
389
- channel.post(42)
390
- @thread.join(0.1)
391
- channel.last_error[0].should be_a(Time)
392
- channel.last_error[1].should eq [42]
393
- channel.last_error[2].should be_a(StandardError)
394
- channel.stop
395
- end
396
- end
397
- end
398
-
399
- context 'supervision' do
400
-
401
- it 'can be started by a Supervisor' do
402
- channel = Channel.new
403
- supervisor = Supervisor.new
404
- supervisor.add_worker(channel)
405
-
406
- channel.should_receive(:run).with(no_args())
407
- supervisor.run!
408
- sleep(0.1)
409
-
410
- supervisor.stop
411
- sleep(0.1)
412
- channel.stop
413
- end
414
-
415
- it 'can receive messages while under supervision' do
416
- @expected = false
417
- channel = Channel.new{|*args| @expected = true}
418
- supervisor = Supervisor.new
419
- supervisor.add_worker(channel)
420
- supervisor.run!
421
- sleep(0.1)
422
-
423
- channel.post(42)
424
- sleep(0.1)
425
- @expected.should be_true
426
-
427
- supervisor.stop
428
- sleep(0.1)
429
- channel.stop
430
- end
431
-
432
- it 'can be stopped by a supervisor' do
433
- channel = Channel.new
434
- supervisor = Supervisor.new
435
- supervisor.add_worker(channel)
436
-
437
- supervisor.run!
438
- sleep(0.1)
439
-
440
- channel.should_receive(:stop).with(no_args())
441
- supervisor.stop
442
- sleep(0.1)
443
- end
444
- end
445
- end
446
- end