concurrent-ruby 0.5.0 → 0.6.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,63 +0,0 @@
1
- require 'concurrent/actor'
2
- require 'concurrent/stoppable'
3
-
4
- module Concurrent
5
-
6
- # +Channel+ is a functional programming variation of +Actor+, based very loosely on the
7
- # *MailboxProcessor* agent in F#. +Actor+ is used to create objects that receive messages
8
- # from other threads then processes those messages based on the behavior of the class.
9
- # +Channel+ creates objects that receive messages and processe them using the block
10
- # given at construction. +Channel+ is implemented as a subclass of +Actor+ and supports
11
- # all message-passing methods of that class. +Channel+ also supports pools with a shared
12
- # mailbox.
13
- #
14
- # @example Basic usage
15
- # channel = Concurrent::Channel.new do |msg|
16
- # sleep(1)
17
- # puts "#{msg}\n"
18
- # end
19
- #
20
- # channel.run! => #<Thread:0x007fa123d95fc8 sleep>
21
- #
22
- # channel.post("Hello, World!") => 1
23
- # # wait...
24
- # => Hello, World!
25
- #
26
- # future = channel.post? "Don't Panic." => #<Concurrent::IVar:0x007fa123d6d9d8 @state=:pending...
27
- # future.pending? => true
28
- # # wait...
29
- # => "Don't Panic."
30
- # future.fulfilled? => true
31
- #
32
- # channel.stop => true
33
- #
34
- # @see http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx Async and Parallel Design Patterns in F#: Agents
35
- # @see http://msdn.microsoft.com/en-us/library/ee370357.aspx Control.MailboxProcessor<'Msg> Class (F#)
36
- class Channel < Actor
37
- include Stoppable
38
-
39
- # Initialize a new object with a block operation to be performed in response
40
- # to every received message.
41
- #
42
- # @yield [message] Removes the next message from the queue and processes it
43
- # @yieldparam [Array] msg The next message post to the channel
44
- def initialize(&block)
45
- raise ArgumentError.new('no block given') unless block_given?
46
- super()
47
- @task = block
48
- end
49
-
50
- protected
51
-
52
- def on_stop # :nodoc:
53
- before_stop_proc.call if before_stop_proc
54
- super
55
- end
56
-
57
- private
58
-
59
- def act(*message) # :nodoc:
60
- return @task.call(*message)
61
- end
62
- end
63
- end
@@ -1,54 +0,0 @@
1
- require 'thread'
2
-
3
- module Concurrent
4
-
5
- class FixedThreadPool
6
-
7
- class Worker
8
-
9
- def initialize(queue, parent)
10
- @queue = queue
11
- @parent = parent
12
- @mutex = Mutex.new
13
- end
14
-
15
- def dead?
16
- return @mutex.synchronize do
17
- @thread.nil? ? false : ! @thread.alive?
18
- end
19
- end
20
-
21
- def kill
22
- @mutex.synchronize do
23
- Thread.kill(@thread) unless @thread.nil?
24
- @thread = nil
25
- end
26
- end
27
-
28
- def run(thread = Thread.current)
29
- @mutex.synchronize do
30
- raise StandardError.new('already running') unless @thread.nil?
31
- @thread = thread
32
- end
33
-
34
- loop do
35
- task = @queue.pop
36
- if task == :stop
37
- @thread = nil
38
- @parent.on_worker_exit(self)
39
- break
40
- end
41
-
42
- @parent.on_start_task(self)
43
- begin
44
- task.last.call(*task.first)
45
- rescue
46
- # let it fail
47
- ensure
48
- @parent.on_end_task(self)
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,42 +0,0 @@
1
- module Concurrent
2
-
3
- class NullThreadPool
4
-
5
- def self.post(*args)
6
- Thread.new(*args) do
7
- Thread.current.abort_on_exception = false
8
- yield(*args)
9
- end
10
- return true
11
- end
12
-
13
- def post(*args, &block)
14
- return NullThreadPool.post(*args, &block)
15
- end
16
-
17
- def <<(block)
18
- NullThreadPool.post(&block)
19
- return self
20
- end
21
- end
22
-
23
- module UsesGlobalThreadPool
24
-
25
- def self.included(base)
26
- class << base
27
- def thread_pool
28
- @thread_pool || $GLOBAL_THREAD_POOL
29
- end
30
- def thread_pool=(pool)
31
- if pool == $GLOBAL_THREAD_POOL
32
- @thread_pool = nil
33
- else
34
- @thread_pool = pool
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
41
-
42
- $GLOBAL_THREAD_POOL ||= Concurrent::NullThreadPool.new
@@ -1,101 +0,0 @@
1
- require 'spec_helper'
2
- require_relative 'thread_pool_shared'
3
-
4
- module Concurrent
5
-
6
- describe CachedThreadPool do
7
-
8
- subject { CachedThreadPool.new(max_threads: 5) }
9
-
10
- after(:each) do
11
- subject.kill
12
- sleep(0.1)
13
- end
14
-
15
- it_should_behave_like :thread_pool
16
-
17
- context '#initialize' do
18
-
19
- it 'raises an exception when the pool size is less than one' do
20
- lambda {
21
- CachedThreadPool.new(max: 0)
22
- }.should raise_error(ArgumentError)
23
- end
24
-
25
- it 'raises an exception when the pool size is greater than MAX_POOL_SIZE' do
26
- lambda {
27
- CachedThreadPool.new(max: CachedThreadPool::MAX_POOL_SIZE + 1)
28
- }.should raise_error(ArgumentError)
29
- end
30
- end
31
-
32
- context '#length' do
33
-
34
- it 'returns zero for a new thread pool' do
35
- subject.length.should eq 0
36
- end
37
-
38
- it 'returns the length of the subject when running' do
39
- 5.times{ sleep(0.1); subject << proc{ sleep(1) } }
40
- subject.length.should eq 5
41
- end
42
-
43
- it 'returns zero once shut down' do
44
- subject.shutdown
45
- subject.length.should eq 0
46
- end
47
- end
48
-
49
- context 'worker creation and caching' do
50
-
51
- it 'creates new workers when there are none available' do
52
- subject.length.should eq 0
53
- 5.times{ sleep(0.1); subject << proc{ sleep } }
54
- sleep(1)
55
- subject.length.should eq 5
56
- end
57
-
58
- it 'uses existing idle threads' do
59
- 5.times{ subject << proc{ sleep(0.1) } }
60
- sleep(1)
61
- subject.length.should >= 5
62
- 3.times{ subject << proc{ sleep } }
63
- sleep(0.1)
64
- subject.length.should >= 5
65
- end
66
-
67
- it 'never creates more than :max_threads threads' do
68
- pool = CachedThreadPool.new(max: 5)
69
- 100.times{ sleep(0.01); pool << proc{ sleep } }
70
- sleep(0.1)
71
- pool.length.should eq 5
72
- pool.kill
73
- end
74
-
75
- it 'sets :max_threads to MAX_POOL_SIZE when not given' do
76
- CachedThreadPool.new.max_threads.should eq CachedThreadPool::MAX_POOL_SIZE
77
- end
78
- end
79
-
80
- context 'garbage collection' do
81
-
82
- subject{ CachedThreadPool.new(gc_interval: 1, idletime: 1) }
83
-
84
- it 'removes from pool any thread that has been idle too long' do
85
- 3.times { subject << proc{ sleep(0.1) } }
86
- subject.length.should eq 3
87
- sleep(2)
88
- subject << proc{ nil }
89
- subject.length.should < 3
90
- end
91
-
92
- it 'removed from pool any dead thread' do
93
- 3.times { subject << proc{ sleep(0.1); raise Exception } }
94
- subject.length.should == 3
95
- sleep(2)
96
- subject << proc{ nil }
97
- subject.length.should < 3
98
- end
99
- end
100
- end
101
- end
@@ -1,86 +0,0 @@
1
- require 'spec_helper'
2
- require_relative 'postable_shared'
3
- require_relative 'runnable_shared'
4
- require_relative 'stoppable_shared'
5
-
6
- module Concurrent
7
-
8
- describe Channel do
9
-
10
- context :runnable do
11
- subject{ Channel.new{ nil } }
12
- it_should_behave_like :runnable
13
- end
14
-
15
- context :stoppable do
16
- subject do
17
- task = Channel.new{ nil }
18
- task.run!
19
- task
20
- end
21
- it_should_behave_like :stoppable
22
- end
23
-
24
- context :postable do
25
-
26
- let!(:postable_class){ Channel }
27
-
28
- let(:sender) do
29
- Channel.new do |*message|
30
- if message.first.is_a?(Exception)
31
- raise message.first
32
- else
33
- message.first
34
- end
35
- end
36
- end
37
-
38
- let(:receiver){ Channel.new{|*message| message.first } }
39
-
40
- it_should_behave_like :postable
41
- end
42
-
43
- subject{ Channel.new{ nil } }
44
-
45
- context '#initialize' do
46
-
47
- it 'raises an exception if no block is given' do
48
- expect {
49
- Channel.new
50
- }.to raise_error(ArgumentError)
51
- end
52
- end
53
-
54
- context '#behavior' do
55
-
56
- it 'calls the block once for each message' do
57
- @expected = false
58
- channel = Channel.new{ @expected = true }
59
- channel.run!
60
- channel << 42
61
- sleep(0.1)
62
- channel.stop
63
- @expected.should be_true
64
- end
65
-
66
- it 'passes all arguments to the block' do
67
- @expected = []
68
- channel = Channel.new{|*message| @expected = message }
69
- channel.run!
70
- channel.post(1,2,3,4,5)
71
- sleep(0.1)
72
- channel.stop
73
- @expected.should eq [1,2,3,4,5]
74
- end
75
- end
76
-
77
- context '#pool' do
78
-
79
- it 'passes a duplicate of the block to each channel in the pool' do
80
- block = proc{ nil }
81
- block.should_receive(:dup).exactly(5).times.and_return(proc{ nil })
82
- mbox, pool = Channel.pool(5, &block)
83
- end
84
- end
85
- end
86
- end
@@ -1,92 +0,0 @@
1
- require 'spec_helper'
2
- require_relative 'thread_pool_shared'
3
-
4
- module Concurrent
5
-
6
- describe FixedThreadPool do
7
-
8
- subject { FixedThreadPool.new(5) }
9
-
10
- after(:each) do
11
- subject.kill
12
- sleep(0.1)
13
- end
14
-
15
- it_should_behave_like :thread_pool
16
-
17
- context '#initialize' do
18
-
19
- it 'raises an exception when the pool length is less than one' do
20
- lambda {
21
- FixedThreadPool.new(0)
22
- }.should raise_error(ArgumentError)
23
- end
24
-
25
- it 'raises an exception when the pool length is greater than MAX_POOL_SIZE' do
26
- lambda {
27
- FixedThreadPool.new(FixedThreadPool::MAX_POOL_SIZE + 1)
28
- }.should raise_error(ArgumentError)
29
- end
30
- end
31
-
32
- context '#length' do
33
-
34
- let(:pool_length) { 3 }
35
- subject { FixedThreadPool.new(pool_length) }
36
-
37
- it 'returns zero on start' do
38
- subject.shutdown
39
- subject.length.should eq 0
40
- end
41
-
42
- it 'returns the length of the pool when running' do
43
- pool_length.times do |i|
44
- subject.post{ sleep }
45
- sleep(0.1)
46
- subject.length.should eq pool_length
47
- end
48
- end
49
-
50
- it 'returns zero while shutting down' do
51
- subject.post{ sleep(1) }
52
- subject.shutdown
53
- subject.length.should eq 0
54
- end
55
-
56
- it 'returns zero once shut down' do
57
- subject.shutdown
58
- subject.length.should eq 0
59
- end
60
- end
61
-
62
- context 'worker creation and caching' do
63
-
64
- it 'creates new workers when there are none available' do
65
- pool = FixedThreadPool.new(5)
66
- pool.length.should eq 0
67
- 5.times{ pool << proc{ sleep } }
68
- sleep(0.1)
69
- pool.length.should eq 5
70
- pool.kill
71
- end
72
-
73
- it 'never creates more than :max_threads threads' do
74
- pool = FixedThreadPool.new(5)
75
- 100.times{ pool << proc{ sleep } }
76
- sleep(0.1)
77
- pool.length.should eq 5
78
- pool.kill
79
- end
80
- end
81
-
82
- context 'exception handling' do
83
-
84
- it 'restarts threads that experience exception' do
85
- pool = FixedThreadPool.new(5)
86
- 5.times{ pool << proc{ raise StandardError } }
87
- sleep(1)
88
- pool.length.should eq 5
89
- end
90
- end
91
- end
92
- end
@@ -1,64 +0,0 @@
1
- require 'spec_helper'
2
-
3
- share_examples_for Concurrent::UsesGlobalThreadPool do
4
-
5
- before(:each) do
6
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(1)
7
- end
8
-
9
- it 'defaults to the global thread pool' do
10
- clazz = Class.new(thread_pool_user)
11
- clazz.thread_pool.should eq $GLOBAL_THREAD_POOL
12
- end
13
-
14
- it 'sets and gets the thread pool for the class' do
15
- pool = Concurrent::NullThreadPool.new
16
- thread_pool_user.thread_pool = pool
17
- thread_pool_user.thread_pool.should eq pool
18
- end
19
-
20
- it 'gives each class its own thread pool' do
21
- subject1 = Class.new(thread_pool_user){ include Concurrent::UsesGlobalThreadPool }
22
- subject2 = Class.new(thread_pool_user){ include Concurrent::UsesGlobalThreadPool }
23
- subject3 = Class.new(thread_pool_user){ include Concurrent::UsesGlobalThreadPool }
24
-
25
- subject1.thread_pool = Concurrent::FixedThreadPool.new(1)
26
- subject2.thread_pool = Concurrent::CachedThreadPool.new
27
- subject3.thread_pool = Concurrent::NullThreadPool.new
28
-
29
- subject1.thread_pool.should_not eq subject2.thread_pool
30
- subject2.thread_pool.should_not eq subject3.thread_pool
31
- subject3.thread_pool.should_not eq subject1.thread_pool
32
- end
33
-
34
- it 'uses the new global thread pool after the global thread pool is changed' do
35
- null_thread_pool = Concurrent::NullThreadPool.new
36
- thread_pool_user.thread_pool = $GLOBAL_THREAD_POOL
37
-
38
- thread_pool_user.thread_pool.should eq $GLOBAL_THREAD_POOL
39
- thread_pool_user.thread_pool.should_not eq null_thread_pool
40
-
41
- $GLOBAL_THREAD_POOL = null_thread_pool
42
-
43
- thread_pool_user.thread_pool.should eq $GLOBAL_THREAD_POOL
44
- thread_pool_user.thread_pool.should eq null_thread_pool
45
- end
46
-
47
- it 'responds to multiple changes in the global thread pool' do
48
- thread_pool_user.thread_pool = $GLOBAL_THREAD_POOL
49
- thread_pool_user.thread_pool.should eq $GLOBAL_THREAD_POOL
50
-
51
- thread_pool_user.thread_pool = Concurrent::NullThreadPool.new
52
- thread_pool_user.thread_pool.should_not eq $GLOBAL_THREAD_POOL
53
-
54
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(1)
55
- thread_pool_user.thread_pool = $GLOBAL_THREAD_POOL
56
- thread_pool_user.thread_pool.should eq $GLOBAL_THREAD_POOL
57
-
58
- $GLOBAL_THREAD_POOL = Concurrent::CachedThreadPool.new
59
- thread_pool_user.thread_pool.should eq $GLOBAL_THREAD_POOL
60
-
61
- $GLOBAL_THREAD_POOL = Concurrent::NullThreadPool.new
62
- thread_pool_user.thread_pool.should eq $GLOBAL_THREAD_POOL
63
- end
64
- end