concurrent-ruby 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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