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
@@ -41,7 +41,7 @@ module Concurrent
41
41
  context '#kill' do
42
42
 
43
43
  it 'kills all threads' do
44
- Thread.should_receive(:kill).exactly(5).times
44
+ Thread.should_receive(:kill).at_least(5).times
45
45
  pool = FixedThreadPool.new(5)
46
46
  pool.kill
47
47
  sleep(0.1)
@@ -74,10 +74,9 @@ module Concurrent
74
74
  it 'restarts threads that experience exception' do
75
75
  pool = FixedThreadPool.new(5)
76
76
  3.times{ pool << proc{ raise StandardError } }
77
- sleep(2)
77
+ sleep(5)
78
78
  pool.size.should eq 5
79
79
  pool.status.should_not include(nil)
80
- #pool.status.include?(nil).should be_false
81
80
  end
82
81
  end
83
82
  end
@@ -0,0 +1,217 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe 'functions' do
6
+
7
+ context '#post' do
8
+
9
+ it 'calls #post when supported by the object' do
10
+ object = Class.new{
11
+ def post() nil; end
12
+ }.new
13
+ object.should_receive(:post).with(no_args())
14
+ post(object){ nil }
15
+ end
16
+
17
+ it 'raises an exception when not supported by the object' do
18
+ object = Class.new{ }.new
19
+ lambda {
20
+ post(object){ nil }
21
+ }.should raise_error(ArgumentError)
22
+ end
23
+ end
24
+
25
+ context '#deref' do
26
+
27
+ it 'returns the value of the #deref function' do
28
+ object = Class.new{
29
+ def deref() nil; end
30
+ }.new
31
+ object.should_receive(:deref).with(nil)
32
+ deref(object, nil){ nil }
33
+ end
34
+
35
+ it 'returns the value of the #value function' do
36
+ object = Class.new{
37
+ def value() nil; end
38
+ }.new
39
+ object.should_receive(:value).with(nil)
40
+ deref(object, nil){ nil }
41
+ end
42
+
43
+ it 'raises an exception when not supported by the object' do
44
+ object = Class.new{ }.new
45
+ lambda {
46
+ deref(object, nil){ nil }
47
+ }.should raise_error(ArgumentError)
48
+ end
49
+ end
50
+
51
+ context '#pending?' do
52
+
53
+ it 'returns the value of the #pending? function' do
54
+ object = Class.new{
55
+ def pending?() nil; end
56
+ }.new
57
+ object.should_receive(:pending?).with(no_args())
58
+ pending?(object){ nil }
59
+ end
60
+
61
+ it 'raises an exception when not supported by the object' do
62
+ object = Class.new{ }.new
63
+ lambda {
64
+ pending?(object){ nil }
65
+ }.should raise_error(ArgumentError)
66
+ end
67
+ end
68
+
69
+ context '#fulfilled?' do
70
+
71
+ it 'returns the value of the #fulfilled? function' do
72
+ object = Class.new{
73
+ def fulfilled?() nil; end
74
+ }.new
75
+ object.should_receive(:fulfilled?).with(no_args())
76
+ fulfilled?(object){ nil }
77
+ end
78
+
79
+ it 'returns the value of the #realized? function' do
80
+ object = Class.new{
81
+ def realized?() nil; end
82
+ }.new
83
+ object.should_receive(:realized?).with(no_args())
84
+ fulfilled?(object){ nil }
85
+ end
86
+
87
+ it 'raises an exception when not supported by the object' do
88
+ object = Class.new{ }.new
89
+ lambda {
90
+ fulfilled?(object){ nil }
91
+ }.should raise_error(ArgumentError)
92
+ end
93
+ end
94
+
95
+ context '#realized?' do
96
+
97
+ it 'returns the value of the #realized? function' do
98
+ object = Class.new{
99
+ def realized?() nil; end
100
+ }.new
101
+ object.should_receive(:realized?).with(no_args())
102
+ realized?(object){ nil }
103
+ end
104
+
105
+ it 'returns the value of the #fulfilled? function' do
106
+ object = Class.new{
107
+ def fulfilled?() nil; end
108
+ }.new
109
+ object.should_receive(:fulfilled?).with(no_args())
110
+ realized?(object){ nil }
111
+ end
112
+
113
+ it 'raises an exception when not supported by the object' do
114
+ object = Class.new{ }.new
115
+ lambda {
116
+ realized?(object){ nil }
117
+ }.should raise_error(ArgumentError)
118
+ end
119
+ end
120
+
121
+ context '#rejected?' do
122
+
123
+ it 'returns the value of the #rejected? function' do
124
+ object = Class.new{
125
+ def rejected?() nil; end
126
+ }.new
127
+ object.should_receive(:rejected?).with(no_args())
128
+ rejected?(object){ nil }
129
+ end
130
+
131
+ it 'raises an exception when not supported by the object' do
132
+ object = Class.new{ }.new
133
+ lambda {
134
+ rejected?(object){ nil }
135
+ }.should raise_error(ArgumentError)
136
+ end
137
+ end
138
+ end
139
+
140
+ describe Agent do
141
+
142
+ before(:each) do
143
+ Agent.thread_pool = FixedThreadPool.new(1)
144
+ end
145
+
146
+ it 'aliases #<< for Agent#post' do
147
+ subject = Agent.new(0)
148
+ subject << proc{ 100 }
149
+ sleep(0.1)
150
+ subject.value.should eq 100
151
+ end
152
+
153
+ it 'aliases Kernel#agent for Agent.new' do
154
+ agent(10).should be_a(Agent)
155
+ end
156
+
157
+ it 'aliases Kernel#deref for #deref' do
158
+ deref(Agent.new(10)).should eq 10
159
+ deref(Agent.new(10), 10).should eq 10
160
+ end
161
+
162
+ it 'aliases Kernel:post for Agent#post' do
163
+ subject = Agent.new(0)
164
+ post(subject){ 100 }
165
+ sleep(0.1)
166
+ subject.value.should eq 100
167
+ end
168
+ end
169
+
170
+ describe Defer do
171
+
172
+ before(:each) do
173
+ Defer.thread_pool = FixedThreadPool.new(1)
174
+ end
175
+
176
+ it 'aliases Kernel#defer' do
177
+ defer{ nil }.should be_a(Defer)
178
+ end
179
+ end
180
+
181
+ describe Executor do
182
+
183
+ it 'aliases Kernel#executor' do
184
+ ex = executor('executor'){ nil }
185
+ ex.should be_a(Executor::ExecutionContext)
186
+ ex.kill
187
+ end
188
+ end
189
+
190
+ describe Future do
191
+
192
+ before(:each) do
193
+ Future.thread_pool = FixedThreadPool.new(1)
194
+ end
195
+
196
+ it 'aliases Kernel#future for Future.new' do
197
+ future().should be_a(Future)
198
+ future(){ nil }.should be_a(Future)
199
+ future(1, 2, 3).should be_a(Future)
200
+ future(1, 2, 3){ nil }.should be_a(Future)
201
+ end
202
+ end
203
+
204
+ describe Promise do
205
+
206
+ before(:each) do
207
+ Promise.thread_pool = FixedThreadPool.new(1)
208
+ end
209
+
210
+ it 'aliases Kernel#promise for Promise.new' do
211
+ promise().should be_a(Promise)
212
+ promise(){ nil }.should be_a(Promise)
213
+ promise(1, 2, 3).should be_a(Promise)
214
+ promise(1, 2, 3){ nil }.should be_a(Promise)
215
+ end
216
+ end
217
+ end
@@ -9,7 +9,7 @@ module Concurrent
9
9
  let!(:rejected_reason) { StandardError.new('mojo jojo') }
10
10
 
11
11
  let(:pending_subject) do
12
- Future.new{ sleep(2) }
12
+ Future.new{ sleep(3); fulfilled_value }
13
13
  end
14
14
 
15
15
  let(:fulfilled_subject) do
@@ -21,10 +21,10 @@ module Concurrent
21
21
  end
22
22
 
23
23
  before(:each) do
24
- $GLOBAL_THREAD_POOL = CachedThreadPool.new
24
+ Future.thread_pool = FixedThreadPool.new(1)
25
25
  end
26
26
 
27
- it_should_behave_like Obligation
27
+ it_should_behave_like Concurrent::Obligation
28
28
 
29
29
  context 'behavior' do
30
30
 
@@ -40,7 +40,7 @@ module Concurrent
40
40
  context '#initialize' do
41
41
 
42
42
  it 'spawns a new thread when a block is given' do
43
- $GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
43
+ Future.thread_pool.should_receive(:post).once.with(any_args())
44
44
  Future.new{ nil }
45
45
  end
46
46
 
@@ -102,13 +102,6 @@ module Concurrent
102
102
  it 'aliases #deref for #value' do
103
103
  fulfilled_subject.deref.should eq fulfilled_value
104
104
  end
105
-
106
- it 'aliases Kernel#future for Future.new' do
107
- future().should be_a(Future)
108
- future(){ nil }.should be_a(Future)
109
- future(1, 2, 3).should be_a(Future)
110
- future(1, 2, 3){ nil }.should be_a(Future)
111
- end
112
105
  end
113
106
  end
114
107
  end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe UsesGlobalThreadPool do
6
+
7
+ before(:each) do
8
+ $GLOBAL_THREAD_POOL = FixedThreadPool.new(1)
9
+ end
10
+
11
+ it 'defaults to the global thread pool' do
12
+ clazz = Class.new{ include UsesGlobalThreadPool }
13
+ clazz.thread_pool.should eq $GLOBAL_THREAD_POOL
14
+ end
15
+
16
+ it 'sets and gets the thread pool for the class' do
17
+ pool = NullThreadPool.new
18
+ clazz = Class.new{ include UsesGlobalThreadPool }
19
+
20
+ clazz.thread_pool = pool
21
+ clazz.thread_pool.should eq pool
22
+ end
23
+
24
+ it 'gives each class its own thread pool' do
25
+ clazz1 = Class.new{ include UsesGlobalThreadPool }
26
+ clazz2 = Class.new{ include UsesGlobalThreadPool }
27
+ clazz3 = Class.new{ include UsesGlobalThreadPool }
28
+
29
+ clazz1.thread_pool = FixedThreadPool.new(1)
30
+ clazz2.thread_pool = CachedThreadPool.new
31
+ clazz3.thread_pool = NullThreadPool.new
32
+
33
+ clazz1.thread_pool.should_not eq clazz2.thread_pool
34
+ clazz2.thread_pool.should_not eq clazz3.thread_pool
35
+ clazz3.thread_pool.should_not eq clazz1.thread_pool
36
+ end
37
+ end
38
+ end
@@ -48,5 +48,20 @@ module Concurrent
48
48
  sleep(0.1)
49
49
  @expected.should eq [1,2,3]
50
50
  end
51
+
52
+ it 'accepts an alternate thread pool as the first argument' do
53
+ pool = Concurrent::FixedThreadPool.new(2)
54
+ pool.should_receive(:post).with(no_args())
55
+ go(pool){ sleep(0.1) }
56
+ sleep(0.2)
57
+ end
58
+
59
+ it 'passes all other arguments to the block when a thread pool is given' do
60
+ @expected = nil
61
+ pool = Concurrent::FixedThreadPool.new(2)
62
+ go(pool, 1, 2, 3){|a, b, c| @expected = [c, b, a] }
63
+ sleep(0.1)
64
+ @expected.should eq [3, 2, 1]
65
+ end
51
66
  end
52
67
  end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ require 'concurrent/goroutine'
4
+
5
+ module Concurrent
6
+
7
+ describe NullThreadPool do
8
+
9
+ subject { NullThreadPool.new }
10
+
11
+ after(:all) do
12
+ $GLOBAL_THREAD_POOL = FixedThreadPool.new(1)
13
+ end
14
+
15
+ context '#post' do
16
+
17
+ it 'proxies a call without arguments' do
18
+ Thread.should_receive(:new).with(no_args())
19
+ $GLOBAL_THREAD_POOL.should_not_receive(:post).with(any_args())
20
+ subject.post{ nil }
21
+ end
22
+
23
+ it 'proxies a call with arguments' do
24
+ Thread.should_receive(:new).with(1,2,3)
25
+ $GLOBAL_THREAD_POOL.should_not_receive(:post).with(any_args())
26
+ subject.post(1,2,3){ nil }
27
+ end
28
+
29
+ it 'aliases #<<' do
30
+ Thread.should_receive(:new).with(no_args())
31
+ $GLOBAL_THREAD_POOL.should_not_receive(:post).with(any_args())
32
+ subject << proc{ nil }
33
+ end
34
+ end
35
+
36
+ context 'operation' do
37
+
38
+ context 'goroutine' do
39
+
40
+ it 'gets a new thread' do
41
+ $GLOBAL_THREAD_POOL = subject
42
+
43
+ t = Thread.new{ nil }
44
+
45
+ Thread.should_receive(:new).with(no_args()).and_return(t)
46
+ go{ nil }
47
+
48
+ Thread.should_receive(:new).with(1,2,3).and_return(t)
49
+ go(1,2,3){ nil }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,121 +1,132 @@
1
1
  require 'spec_helper'
2
2
 
3
- module Concurrent
4
-
5
- share_examples_for Obligation do
6
-
7
- context '#state' do
8
-
9
- it 'is :pending when first created' do
10
- f = pending_subject
11
- f.state.should == :pending
12
- f.should be_pending
13
- end
14
-
15
- it 'is :fulfilled when the handler completes' do
16
- f = fulfilled_subject
17
- f.state.should == :fulfilled
18
- f.should be_fulfilled
19
- end
20
-
21
- it 'is :rejected when the handler raises an exception' do
22
- f = rejected_subject
23
- f.state.should == :rejected
24
- f.should be_rejected
25
- end
26
- end
27
-
28
- context '#value' do
29
-
30
- it 'blocks the caller when :pending and timeout is nil' do
31
- f = pending_subject
32
- sleep(0.1)
33
- f.value.should be_true
34
- f.should be_fulfilled
35
- end
36
-
37
- it 'returns nil when reaching the optional timeout value' do
38
- f = pending_subject
39
- sleep(0.1)
40
- f.value(0.1).should be_nil
41
- f.should be_pending
42
- end
43
-
44
- it 'returns immediately when timeout is zero' do
45
- Timeout.should_not_receive(:timeout).with(any_args())
46
- f = pending_subject
47
- sleep(0.1)
48
- f.value(0).should be_nil
49
- f.should be_pending
50
- end
51
-
52
- it 'is nil when :pending' do
53
- expected = pending_subject.value
54
- expected.should be_nil
55
- end
56
-
57
- it 'is nil when :rejected' do
58
- expected = rejected_subject.value
59
- expected.should be_nil
60
- end
61
-
62
- it 'is set to the return value of the block when :fulfilled' do
63
- expected = fulfilled_subject.value
64
- expected.should eq fulfilled_value
65
- end
66
- end
67
-
68
- context '#reason' do
69
-
70
- it 'is nil when :pending' do
71
- pending_subject.reason.should be_nil
72
- end
73
-
74
- it 'is nil when :fulfilled' do
75
- fulfilled_subject.reason.should be_nil
76
- end
77
-
78
- it 'is set to error object of the exception when :rejected' do
79
- rejected_subject.reason.should be_a(Exception)
80
- rejected_subject.reason.to_s.should =~ /#{rejected_reason}/
81
- end
82
- end
83
-
84
- context 'Kernel aliases' do
85
-
86
- it 'aliases Kernel#deref for #deref' do
87
- deref(fulfilled_subject).should eq fulfilled_value
88
- deref(fulfilled_subject, 0).should eq fulfilled_value
89
- end
90
-
91
- it 'aliases Kernel#pending? for #pending?' do
92
- #NOTE: was structured like others but was incorrectly failing
93
- # on fulfilled_subject
94
- fulfilled_subject.should_receive(:pending?).once
95
- pending?(fulfilled_subject)
96
- pending_subject.should_receive(:pending?).once
97
- pending?(pending_subject)
98
- rejected_subject.should_receive(:pending?).once
99
- pending?(rejected_subject)
100
- end
101
-
102
- it 'aliases Kernel#fulfilled? for #fulfilled?' do
103
- fulfilled?(fulfilled_subject).should be_true
104
- fulfilled?(pending_subject).should be_false
105
- fulfilled?(rejected_subject).should be_false
106
- end
107
-
108
- it 'aliases Kernel#realized? for #realized?' do
109
- realized?(fulfilled_subject).should be_true
110
- realized?(pending_subject).should be_false
111
- realized?(rejected_subject).should be_false
112
- end
113
-
114
- it 'aliases Kernel#rejected? for #rejected?' do
115
- rejected?(rejected_subject).should be_true
116
- rejected?(fulfilled_subject).should be_false
117
- rejected?(pending_subject).should be_false
118
- end
3
+ share_examples_for Concurrent::Obligation do
4
+
5
+ context '#state' do
6
+
7
+ it 'is :pending when first created' do
8
+ f = pending_subject
9
+ f.state.should == :pending
10
+ f.should be_pending
11
+ end
12
+
13
+ it 'is :fulfilled when the handler completes' do
14
+ f = fulfilled_subject
15
+ f.state.should == :fulfilled
16
+ f.should be_fulfilled
17
+ end
18
+
19
+ it 'is :rejected when the handler raises an exception' do
20
+ f = rejected_subject
21
+ f.state.should == :rejected
22
+ f.should be_rejected
23
+ end
24
+ end
25
+
26
+ context '#value' do
27
+
28
+ it 'blocks the caller when :pending and timeout is nil' do
29
+ f = pending_subject
30
+ sleep(0.1)
31
+ f.value.should be_true
32
+ f.should be_fulfilled
33
+ end
34
+
35
+ it 'returns nil when reaching the optional timeout value' do
36
+ f = pending_subject
37
+ sleep(0.1)
38
+ f.value(0).should be_nil
39
+ f.should be_pending
40
+ end
41
+
42
+ it 'returns immediately when timeout is zero' do
43
+ Timeout.should_not_receive(:timeout).with(any_args())
44
+ f = pending_subject
45
+ sleep(0.1)
46
+ f.value(0).should be_nil
47
+ f.should be_pending
48
+ end
49
+
50
+ it 'returns the value when fulfilled before timeout' do
51
+ f = pending_subject
52
+ sleep(0.1)
53
+ f.value(10).should be_true
54
+ f.should be_fulfilled
55
+ end
56
+
57
+ it 'returns nil when timeout reached' do
58
+ f = pending_subject
59
+ sleep(0.1)
60
+ f.value(0.1).should be_nil
61
+ f.should be_pending
62
+ end
63
+
64
+ it 'is nil when :pending' do
65
+ expected = pending_subject.value(0)
66
+ expected.should be_nil
67
+ end
68
+
69
+ it 'is nil when :rejected' do
70
+ expected = rejected_subject.value
71
+ expected.should be_nil
72
+ end
73
+
74
+ it 'is set to the return value of the block when :fulfilled' do
75
+ expected = fulfilled_subject.value
76
+ expected.should eq fulfilled_value
77
+ end
78
+ end
79
+
80
+ context '#reason' do
81
+
82
+ it 'is nil when :pending' do
83
+ pending_subject.reason.should be_nil
84
+ end
85
+
86
+ it 'is nil when :fulfilled' do
87
+ fulfilled_subject.reason.should be_nil
88
+ end
89
+
90
+ it 'is set to error object of the exception when :rejected' do
91
+ rejected_subject.reason.should be_a(Exception)
92
+ rejected_subject.reason.to_s.should =~ /#{rejected_reason}/
93
+ end
94
+ end
95
+
96
+ context 'Kernel aliases' do
97
+
98
+ it 'aliases Kernel#deref for #deref' do
99
+ deref(fulfilled_subject).should eq fulfilled_value
100
+ deref(fulfilled_subject, 0).should eq fulfilled_value
101
+ end
102
+
103
+ it 'aliases Kernel#pending? for #pending?' do
104
+ #NOTE: was structured like others but was incorrectly failing
105
+ # on fulfilled_subject
106
+ fulfilled_subject.should_receive(:pending?).once
107
+ pending?(fulfilled_subject)
108
+ pending_subject.should_receive(:pending?).once
109
+ pending?(pending_subject)
110
+ rejected_subject.should_receive(:pending?).once
111
+ pending?(rejected_subject)
112
+ end
113
+
114
+ it 'aliases Kernel#fulfilled? for #fulfilled?' do
115
+ fulfilled?(fulfilled_subject).should be_true
116
+ fulfilled?(pending_subject).should be_false
117
+ fulfilled?(rejected_subject).should be_false
118
+ end
119
+
120
+ it 'aliases Kernel#realized? for #realized?' do
121
+ realized?(fulfilled_subject).should be_true
122
+ realized?(pending_subject).should be_false
123
+ realized?(rejected_subject).should be_false
124
+ end
125
+
126
+ it 'aliases Kernel#rejected? for #rejected?' do
127
+ rejected?(rejected_subject).should be_true
128
+ rejected?(fulfilled_subject).should be_false
129
+ rejected?(pending_subject).should be_false
119
130
  end
120
131
  end
121
132
  end