functional-ruby 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +154 -562
- data/lib/functional/agent.rb +130 -0
- data/lib/functional/all.rb +9 -1
- data/lib/functional/behavior.rb +72 -39
- data/lib/functional/cached_thread_pool.rb +122 -0
- data/lib/functional/concurrency.rb +32 -24
- data/lib/functional/core.rb +2 -62
- data/lib/functional/event.rb +53 -0
- data/lib/functional/event_machine_defer_proxy.rb +23 -0
- data/lib/functional/fixed_thread_pool.rb +89 -0
- data/lib/functional/future.rb +42 -0
- data/lib/functional/global_thread_pool.rb +3 -0
- data/lib/functional/obligation.rb +121 -0
- data/lib/functional/promise.rb +194 -0
- data/lib/functional/thread_pool.rb +61 -0
- data/lib/functional/utilities.rb +114 -0
- data/lib/functional/version.rb +1 -1
- data/lib/functional.rb +1 -0
- data/lib/functional_ruby.rb +1 -0
- data/md/behavior.md +147 -0
- data/md/concurrency.md +465 -0
- data/md/future.md +32 -0
- data/md/obligation.md +32 -0
- data/md/pattern_matching.md +512 -0
- data/md/promise.md +220 -0
- data/md/utilities.md +53 -0
- data/spec/functional/agent_spec.rb +405 -0
- data/spec/functional/behavior_spec.rb +12 -33
- data/spec/functional/cached_thread_pool_spec.rb +112 -0
- data/spec/functional/concurrency_spec.rb +55 -0
- data/spec/functional/event_machine_defer_proxy_spec.rb +246 -0
- data/spec/functional/event_spec.rb +114 -0
- data/spec/functional/fixed_thread_pool_spec.rb +84 -0
- data/spec/functional/future_spec.rb +115 -0
- data/spec/functional/obligation_shared.rb +121 -0
- data/spec/functional/pattern_matching_spec.rb +10 -8
- data/spec/functional/promise_spec.rb +310 -0
- data/spec/functional/thread_pool_shared.rb +209 -0
- data/spec/functional/utilities_spec.rb +149 -0
- data/spec/spec_helper.rb +2 -0
- metadata +55 -5
@@ -1,37 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
#class Foo
|
6
|
-
#behavior(:gen_foo)
|
7
|
-
|
8
|
-
#def foo
|
9
|
-
#return 'foo/0'
|
10
|
-
#end
|
11
|
-
|
12
|
-
#def bar(one, &block)
|
13
|
-
#return 'bar/1'
|
14
|
-
#end
|
15
|
-
|
16
|
-
#def baz(one, two)
|
17
|
-
#return 'baz/2'
|
18
|
-
#end
|
19
|
-
|
20
|
-
#def boom(*args)
|
21
|
-
#return 'boom/-1'
|
22
|
-
#end
|
23
|
-
|
24
|
-
#def bam
|
25
|
-
#return 'bam!'
|
26
|
-
#end
|
27
|
-
#end
|
28
|
-
|
29
|
-
describe 'behavior/interface definitions' do
|
3
|
+
describe '-behavior' do
|
30
4
|
|
31
5
|
before(:each) do
|
6
|
+
@__behavior_info__ = $__behavior_info__
|
32
7
|
$__behavior_info__ = {}
|
33
8
|
end
|
34
9
|
|
10
|
+
after(:each) do
|
11
|
+
$__behavior_info__ = @__behavior_info__
|
12
|
+
end
|
13
|
+
|
35
14
|
context 'behavior_info/2' do
|
36
15
|
|
37
16
|
it 'accepts a symbol name' do
|
@@ -77,7 +56,7 @@ describe 'behavior/interface definitions' do
|
|
77
56
|
Class.new{
|
78
57
|
behavior(:gen_foo)
|
79
58
|
}
|
80
|
-
}.should raise_error(
|
59
|
+
}.should raise_error(BehaviorError)
|
81
60
|
end
|
82
61
|
|
83
62
|
it 'can be called multiple times for one class' do
|
@@ -93,7 +72,7 @@ describe 'behavior/interface definitions' do
|
|
93
72
|
end
|
94
73
|
end
|
95
74
|
|
96
|
-
context '
|
75
|
+
context 'object creation' do
|
97
76
|
|
98
77
|
it 'raises an exception when one or more function definitions are missing' do
|
99
78
|
behavior_info(:gen_foo, foo: 0, bar: 1)
|
@@ -104,7 +83,7 @@ describe 'behavior/interface definitions' do
|
|
104
83
|
|
105
84
|
lambda {
|
106
85
|
clazz.new
|
107
|
-
}.should raise_error(
|
86
|
+
}.should raise_error(BehaviorError)
|
108
87
|
end
|
109
88
|
|
110
89
|
it 'raises an exception when one or more functions do not have proper arity' do
|
@@ -116,7 +95,7 @@ describe 'behavior/interface definitions' do
|
|
116
95
|
|
117
96
|
lambda {
|
118
97
|
clazz.new
|
119
|
-
}.should raise_error(
|
98
|
+
}.should raise_error(BehaviorError)
|
120
99
|
end
|
121
100
|
|
122
101
|
it 'accepts any arity when function arity is set to :any' do
|
@@ -128,7 +107,7 @@ describe 'behavior/interface definitions' do
|
|
128
107
|
|
129
108
|
lambda {
|
130
109
|
clazz.new
|
131
|
-
}.should_not raise_error(
|
110
|
+
}.should_not raise_error(BehaviorError)
|
132
111
|
end
|
133
112
|
|
134
113
|
it 'creates the object when function definitions match' do
|
@@ -141,7 +120,7 @@ describe 'behavior/interface definitions' do
|
|
141
120
|
|
142
121
|
lambda {
|
143
122
|
clazz.new
|
144
|
-
}.should_not raise_error(
|
123
|
+
}.should_not raise_error(BehaviorError)
|
145
124
|
end
|
146
125
|
end
|
147
126
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'thread_pool_shared'
|
3
|
+
|
4
|
+
module Functional
|
5
|
+
|
6
|
+
describe CachedThreadPool do
|
7
|
+
|
8
|
+
subject { CachedThreadPool.new }
|
9
|
+
|
10
|
+
it_should_behave_like 'Thread Pool'
|
11
|
+
|
12
|
+
context '#initialize' do
|
13
|
+
it 'aliases Functional#new_cached_thread_pool' do
|
14
|
+
pool = Functional.new_cached_thread_pool
|
15
|
+
pool.should be_a(CachedThreadPool)
|
16
|
+
pool.size.should eq 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context '#kill' do
|
21
|
+
|
22
|
+
it 'kills all threads' do
|
23
|
+
Thread.should_receive(:kill).exactly(5).times
|
24
|
+
pool = CachedThreadPool.new
|
25
|
+
5.times{ sleep(0.1); pool << proc{ sleep(1) } }
|
26
|
+
sleep(1)
|
27
|
+
pool.kill
|
28
|
+
sleep(0.1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context '#size' do
|
33
|
+
|
34
|
+
it 'returns zero for a new thread pool' do
|
35
|
+
subject.size.should eq 0
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns the size of the subject when running' do
|
39
|
+
5.times{ sleep(0.1); subject << proc{ sleep(1) } }
|
40
|
+
subject.size.should eq 5
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns zero once shut down' do
|
44
|
+
subject.shutdown
|
45
|
+
subject.size.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.size.should eq 0
|
53
|
+
5.times{ sleep(0.1); subject << proc{ sleep(1000) } }
|
54
|
+
sleep(1)
|
55
|
+
subject.size.should eq 5
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'uses existing idle threads' do
|
59
|
+
5.times{ sleep(0.05); subject << proc{ sleep(0.5) } }
|
60
|
+
sleep(1)
|
61
|
+
3.times{ sleep(0.1); subject << proc{ sleep(0.5) } }
|
62
|
+
subject.size.should eq 5
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'garbage collection' do
|
67
|
+
|
68
|
+
subject{ CachedThreadPool.new(gc_interval: 1, thread_idleime: 1) }
|
69
|
+
|
70
|
+
it 'starts when the first thread is added to the pool' do
|
71
|
+
subject.should_receive(:collect_garbage)
|
72
|
+
subject << proc{ nil }
|
73
|
+
sleep(0.1)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'removes from pool any thread that has been idle too long' do
|
77
|
+
subject << proc{ nil }
|
78
|
+
subject.size.should eq 1
|
79
|
+
sleep(1.5)
|
80
|
+
subject.size.should eq 0
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'removed from pool any dead thread' do
|
84
|
+
subject << proc{ raise StandardError }
|
85
|
+
subject.size.should eq 1
|
86
|
+
sleep(1.5)
|
87
|
+
subject.size.should eq 0
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'resets the working count appropriately' do
|
91
|
+
subject << proc{ sleep(1000) }
|
92
|
+
sleep(0.1)
|
93
|
+
subject << proc{ raise StandardError }
|
94
|
+
sleep(0.1)
|
95
|
+
subject << proc{ nil }
|
96
|
+
|
97
|
+
sleep(0.1)
|
98
|
+
subject.working.should eq 2
|
99
|
+
|
100
|
+
sleep(1.5)
|
101
|
+
subject.working.should eq 1
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'stops collection when the pool size becomes zero' do
|
105
|
+
3.times{ sleep(0.1); subject << proc{ sleep(0.5) } }
|
106
|
+
subject.instance_variable_get(:@collector).status.should eq 'sleep'
|
107
|
+
sleep(1.5)
|
108
|
+
subject.instance_variable_get(:@collector).status.should be_false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
describe 'concurrency' do
|
6
|
+
|
7
|
+
context '#go' do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
$GLOBAL_THREAD_POOL = CachedThreadPool.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'passes all arguments to the block' do
|
14
|
+
@expected = nil
|
15
|
+
go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
|
16
|
+
sleep(0.1)
|
17
|
+
@expected.should eq [3, 2, 1]
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns true if the thread is successfully created' do
|
21
|
+
$GLOBAL_THREAD_POOL.should_receive(:post).and_return(true)
|
22
|
+
go{ nil }.should be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns false if the thread cannot be created' do
|
26
|
+
$GLOBAL_THREAD_POOL.should_receive(:post).and_return(false)
|
27
|
+
go{ nil }.should be_false
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'immediately returns false if no block is given' do
|
31
|
+
go().should be_false
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not create a thread if no block is given' do
|
35
|
+
$GLOBAL_THREAD_POOL.should_not_receive(:post)
|
36
|
+
go()
|
37
|
+
sleep(0.1)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'supresses exceptions on the thread' do
|
41
|
+
lambda{
|
42
|
+
go{ raise StandardError }
|
43
|
+
sleep(0.1)
|
44
|
+
}.should_not raise_error
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'processes the block' do
|
48
|
+
@expected = false
|
49
|
+
go(1,2,3){|*args| @expected = args }
|
50
|
+
sleep(0.1)
|
51
|
+
@expected.should eq [1,2,3]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'functional/agent'
|
4
|
+
require 'functional/future'
|
5
|
+
require 'functional/promise'
|
6
|
+
|
7
|
+
module Functional
|
8
|
+
|
9
|
+
describe EventMachineDeferProxy do
|
10
|
+
|
11
|
+
subject { EventMachineDeferProxy.new }
|
12
|
+
|
13
|
+
context '#post' do
|
14
|
+
|
15
|
+
it 'proxies a call without arguments' do
|
16
|
+
@expected = false
|
17
|
+
EventMachine.run do
|
18
|
+
subject.post{ @expected = true }
|
19
|
+
sleep(0.1)
|
20
|
+
EventMachine.stop
|
21
|
+
end
|
22
|
+
@expected.should eq true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'proxies a call with arguments' do
|
26
|
+
@expected = []
|
27
|
+
EventMachine.run do
|
28
|
+
subject.post(1,2,3){|*args| @expected = args }
|
29
|
+
sleep(0.1)
|
30
|
+
EventMachine.stop
|
31
|
+
end
|
32
|
+
@expected.should eq [1,2,3]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'aliases #<<' do
|
36
|
+
@expected = false
|
37
|
+
EventMachine.run do
|
38
|
+
subject << proc{ @expected = true }
|
39
|
+
sleep(0.1)
|
40
|
+
EventMachine.stop
|
41
|
+
end
|
42
|
+
@expected.should eq true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'operation' do
|
47
|
+
|
48
|
+
context 'goroutine' do
|
49
|
+
|
50
|
+
it 'passes all arguments to the block' do
|
51
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
52
|
+
|
53
|
+
EventMachine.run do
|
54
|
+
|
55
|
+
@expected = nil
|
56
|
+
go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
|
57
|
+
sleep(0.1)
|
58
|
+
@expected.should eq [3, 2, 1]
|
59
|
+
|
60
|
+
EventMachine.stop
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context Agent do
|
66
|
+
|
67
|
+
subject { Agent.new(0) }
|
68
|
+
|
69
|
+
it 'supports fulfillment' do
|
70
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
71
|
+
|
72
|
+
EventMachine.run do
|
73
|
+
|
74
|
+
@expected = []
|
75
|
+
subject.post{ @expected << 1 }
|
76
|
+
subject.post{ @expected << 2 }
|
77
|
+
subject.post{ @expected << 3 }
|
78
|
+
sleep(0.1)
|
79
|
+
@expected.should eq [1,2,3]
|
80
|
+
|
81
|
+
EventMachine.stop
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'supports validation' do
|
86
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
87
|
+
|
88
|
+
EventMachine.run do
|
89
|
+
|
90
|
+
@expected = nil
|
91
|
+
subject.validate{ @expected = 10; true }
|
92
|
+
subject.post{ nil }
|
93
|
+
sleep(0.1)
|
94
|
+
@expected.should eq 10
|
95
|
+
|
96
|
+
EventMachine.stop
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'supports rejection' do
|
101
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
102
|
+
|
103
|
+
EventMachine.run do
|
104
|
+
|
105
|
+
@expected = nil
|
106
|
+
subject.
|
107
|
+
on_error(StandardError){|ex| @expected = 1 }.
|
108
|
+
on_error(StandardError){|ex| @expected = 2 }.
|
109
|
+
on_error(StandardError){|ex| @expected = 3 }
|
110
|
+
subject.post{ raise StandardError }
|
111
|
+
sleep(0.1)
|
112
|
+
@expected.should eq 1
|
113
|
+
|
114
|
+
EventMachine.stop
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context Future do
|
120
|
+
|
121
|
+
it 'supports fulfillment' do
|
122
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
123
|
+
|
124
|
+
EventMachine.run do
|
125
|
+
|
126
|
+
@a = @b = @c = nil
|
127
|
+
f = Future.new(1, 2, 3) do |a, b, c|
|
128
|
+
@a, @b, @c = a, b, c
|
129
|
+
end
|
130
|
+
sleep(0.1)
|
131
|
+
[@a, @b, @c].should eq [1, 2, 3]
|
132
|
+
|
133
|
+
sleep(0.1)
|
134
|
+
EventMachine.stop
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context Promise do
|
140
|
+
|
141
|
+
context 'fulfillment' do
|
142
|
+
|
143
|
+
it 'passes all arguments to the first promise in the chain' do
|
144
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
145
|
+
|
146
|
+
EventMachine.run do
|
147
|
+
|
148
|
+
@a = @b = @c = nil
|
149
|
+
p = Promise.new(1, 2, 3) do |a, b, c|
|
150
|
+
@a, @b, @c = a, b, c
|
151
|
+
end
|
152
|
+
sleep(0.1)
|
153
|
+
[@a, @b, @c].should eq [1, 2, 3]
|
154
|
+
|
155
|
+
sleep(0.1)
|
156
|
+
EventMachine.stop
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'passes the result of each block to all its children' do
|
161
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
162
|
+
|
163
|
+
EventMachine.run do
|
164
|
+
@expected = nil
|
165
|
+
Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
|
166
|
+
sleep(0.1)
|
167
|
+
@expected.should eq 20
|
168
|
+
|
169
|
+
sleep(0.1)
|
170
|
+
EventMachine.stop
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'sets the promise value to the result if its block' do
|
175
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
176
|
+
|
177
|
+
EventMachine.run do
|
178
|
+
|
179
|
+
p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
|
180
|
+
sleep(0.1)
|
181
|
+
p.value.should eq 40
|
182
|
+
|
183
|
+
sleep(0.1)
|
184
|
+
EventMachine.stop
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'rejection' do
|
190
|
+
|
191
|
+
it 'sets the promise reason and error on exception' do
|
192
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
193
|
+
|
194
|
+
EventMachine.run do
|
195
|
+
|
196
|
+
p = Promise.new{ raise StandardError.new('Boom!') }
|
197
|
+
sleep(0.1)
|
198
|
+
p.reason.should be_a(Exception)
|
199
|
+
p.reason.should.to_s =~ /Boom!/
|
200
|
+
p.should be_rejected
|
201
|
+
|
202
|
+
sleep(0.1)
|
203
|
+
EventMachine.stop
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'calls the first exception block with a matching class' do
|
208
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
209
|
+
|
210
|
+
EventMachine.run do
|
211
|
+
|
212
|
+
@expected = nil
|
213
|
+
Promise.new{ raise StandardError }.
|
214
|
+
on_error(StandardError){|ex| @expected = 1 }.
|
215
|
+
on_error(StandardError){|ex| @expected = 2 }.
|
216
|
+
on_error(StandardError){|ex| @expected = 3 }
|
217
|
+
sleep(0.1)
|
218
|
+
@expected.should eq 1
|
219
|
+
|
220
|
+
sleep(0.1)
|
221
|
+
EventMachine.stop
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'passes the exception object to the matched block' do
|
226
|
+
$GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
|
227
|
+
|
228
|
+
EventMachine.run do
|
229
|
+
|
230
|
+
@expected = nil
|
231
|
+
Promise.new{ raise StandardError }.
|
232
|
+
on_error(ArgumentError){|ex| @expected = ex }.
|
233
|
+
on_error(LoadError){|ex| @expected = ex }.
|
234
|
+
on_error(Exception){|ex| @expected = ex }
|
235
|
+
sleep(0.1)
|
236
|
+
@expected.should be_a(StandardError)
|
237
|
+
|
238
|
+
sleep(0.1)
|
239
|
+
EventMachine.stop
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
describe Event do
|
6
|
+
|
7
|
+
subject{ Event.new }
|
8
|
+
|
9
|
+
context '#initialize' do
|
10
|
+
|
11
|
+
it 'sets the state to unset' do
|
12
|
+
subject.should_not be_set
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context '#set?' do
|
17
|
+
|
18
|
+
it 'returns true when the event has been set' do
|
19
|
+
subject.set
|
20
|
+
subject.should be_set
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns false if the event is unset' do
|
24
|
+
subject.reset
|
25
|
+
subject.should_not be_set
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context '#set' do
|
30
|
+
|
31
|
+
it 'triggers the event' do
|
32
|
+
subject.reset
|
33
|
+
@expected = false
|
34
|
+
Thread.new{ sleep(0.5); subject.wait; @expected = true }
|
35
|
+
subject.set
|
36
|
+
sleep(1)
|
37
|
+
@expected.should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'sets the state to set' do
|
41
|
+
subject.set
|
42
|
+
subject.should be_set
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '#reset' do
|
47
|
+
|
48
|
+
it 'sets the state to unset' do
|
49
|
+
subject.set
|
50
|
+
subject.should be_set
|
51
|
+
subject.reset
|
52
|
+
subject.should_not be_set
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context '#wait' do
|
57
|
+
|
58
|
+
it 'returns immediately when the event has been set' do
|
59
|
+
subject.reset
|
60
|
+
@expected = false
|
61
|
+
subject.set
|
62
|
+
Thread.new{ subject.wait(1000); @expected = true}
|
63
|
+
sleep(1)
|
64
|
+
@expected.should be_true
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns true once the event is set' do
|
68
|
+
subject.set
|
69
|
+
subject.wait.should be_true
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'blocks indefinitely when the timer is nil' do
|
73
|
+
subject.reset
|
74
|
+
@expected = false
|
75
|
+
Thread.new{ subject.wait; @expected = true}
|
76
|
+
subject.set
|
77
|
+
sleep(1)
|
78
|
+
@expected.should be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'stops waiting when the timer expires' do
|
82
|
+
subject.reset
|
83
|
+
@expected = false
|
84
|
+
Thread.new{ subject.wait(0.5); @expected = true}
|
85
|
+
sleep(1)
|
86
|
+
@expected.should be_true
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns false when the timer expires' do
|
90
|
+
subject.reset
|
91
|
+
subject.wait(1).should be_false
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'triggers multiple waiting threads' do
|
95
|
+
subject.reset
|
96
|
+
@expected = []
|
97
|
+
5.times{ Thread.new{ subject.wait; @expected << Thread.current.object_id } }
|
98
|
+
subject.set
|
99
|
+
sleep(1)
|
100
|
+
@expected.length.should eq 5
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'behaves appropriately if wait begins while #set is processing' do
|
104
|
+
subject.reset
|
105
|
+
@expected = []
|
106
|
+
5.times{ Thread.new{ subject.wait(5) } }
|
107
|
+
subject.set
|
108
|
+
5.times{ Thread.new{ subject.wait; @expected << Thread.current.object_id } }
|
109
|
+
sleep(1)
|
110
|
+
@expected.length.should eq 5
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'thread_pool_shared'
|
3
|
+
|
4
|
+
module Functional
|
5
|
+
|
6
|
+
describe FixedThreadPool do
|
7
|
+
|
8
|
+
subject { FixedThreadPool.new(5) }
|
9
|
+
|
10
|
+
it_should_behave_like 'Thread Pool'
|
11
|
+
|
12
|
+
context '#initialize' do
|
13
|
+
|
14
|
+
it 'raises an exception when the pool size is less than one' do
|
15
|
+
lambda {
|
16
|
+
FixedThreadPool.new(0)
|
17
|
+
}.should raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'raises an exception when the pool size is greater than 1024' do
|
21
|
+
lambda {
|
22
|
+
FixedThreadPool.new(1025)
|
23
|
+
}.should raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'creates a thread pool of the given size' do
|
27
|
+
thread = mock('thread')
|
28
|
+
# add one for the garbage collector
|
29
|
+
Thread.should_receive(:new).exactly(5+1).times.and_return(thread)
|
30
|
+
pool = FixedThreadPool.new(5)
|
31
|
+
pool.size.should eq 5
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'aliases Functional#new_fixed_thread_pool' do
|
35
|
+
pool = Functional.new_fixed_thread_pool(5)
|
36
|
+
pool.should be_a(FixedThreadPool)
|
37
|
+
pool.size.should eq 5
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context '#kill' do
|
42
|
+
|
43
|
+
it 'kills all threads' do
|
44
|
+
Thread.should_receive(:kill).exactly(5).times
|
45
|
+
pool = FixedThreadPool.new(5)
|
46
|
+
pool.kill
|
47
|
+
sleep(0.1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context '#size' do
|
52
|
+
|
53
|
+
let(:pool_size) { 3 }
|
54
|
+
subject { FixedThreadPool.new(pool_size) }
|
55
|
+
|
56
|
+
it 'returns the size of the subject when running' do
|
57
|
+
subject.size.should eq pool_size
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns zero while shutting down' do
|
61
|
+
subject.post{ sleep(1) }
|
62
|
+
subject.shutdown
|
63
|
+
subject.size.should eq 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'returns zero once shut down' do
|
67
|
+
subject.shutdown
|
68
|
+
subject.size.should eq 0
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'exception handling' do
|
73
|
+
|
74
|
+
it 'restarts threads that experience exception' do
|
75
|
+
pool = FixedThreadPool.new(5)
|
76
|
+
3.times{ pool << proc{ raise StandardError } }
|
77
|
+
sleep(2)
|
78
|
+
pool.size.should eq 5
|
79
|
+
pool.status.should_not include(nil)
|
80
|
+
#pool.status.include?(nil).should be_false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|