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

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