concurrent-ruby 0.0.1 → 0.1.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 +223 -3
- data/lib/concurrent.rb +19 -0
- data/lib/concurrent/agent.rb +130 -0
- data/lib/concurrent/cached_thread_pool.rb +122 -0
- data/lib/concurrent/defer.rb +69 -0
- data/lib/concurrent/event.rb +60 -0
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -0
- data/lib/concurrent/fixed_thread_pool.rb +89 -0
- data/lib/concurrent/future.rb +42 -0
- data/lib/concurrent/global_thread_pool.rb +3 -0
- data/lib/concurrent/goroutine.rb +25 -0
- data/lib/concurrent/obligation.rb +121 -0
- data/lib/concurrent/promise.rb +194 -0
- data/lib/concurrent/thread_pool.rb +61 -0
- data/lib/concurrent/version.rb +1 -1
- data/lib/concurrent_ruby.rb +1 -0
- data/md/agent.md +123 -0
- data/md/defer.md +174 -0
- data/md/event.md +32 -0
- data/md/future.md +83 -0
- data/md/goroutine.md +52 -0
- data/md/obligation.md +32 -0
- data/md/promise.md +225 -0
- data/md/thread_pool.md +197 -0
- data/spec/concurrent/agent_spec.rb +405 -0
- data/spec/concurrent/cached_thread_pool_spec.rb +112 -0
- data/spec/concurrent/defer_spec.rb +199 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +246 -0
- data/spec/concurrent/event_spec.rb +134 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +84 -0
- data/spec/concurrent/future_spec.rb +115 -0
- data/spec/concurrent/goroutine_spec.rb +52 -0
- data/spec/concurrent/obligation_shared.rb +121 -0
- data/spec/concurrent/promise_spec.rb +310 -0
- data/spec/concurrent/thread_pool_shared.rb +209 -0
- data/spec/spec_helper.rb +2 -0
- metadata +61 -4
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
describe Defer do
|
6
|
+
|
7
|
+
context '#initialize' do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
$GLOBAL_THREAD_POOL = FixedThreadPool.new(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'raises an exception if no block or operation given' do
|
14
|
+
lambda {
|
15
|
+
Defer.new
|
16
|
+
}.should raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'raises an exception if both a block and an operation given' do
|
20
|
+
lambda {
|
21
|
+
operation = proc{ nil }
|
22
|
+
Defer.new(operation, nil, nil){ nil }
|
23
|
+
}.should raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'starts the thread if an operation is given' do
|
27
|
+
$GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
|
28
|
+
operation = proc{ nil }
|
29
|
+
Defer.new(operation, nil, nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not start the thread if neither a callback or errorback is given' do
|
33
|
+
$GLOBAL_THREAD_POOL.should_not_receive(:post)
|
34
|
+
Defer.new(nil, nil, nil){ nil }
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'aliases Kernel#defer' do
|
38
|
+
defer{ nil }.should be_a(Defer)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '#then' do
|
43
|
+
|
44
|
+
it 'raises an exception if no block given' do
|
45
|
+
lambda {
|
46
|
+
Defer.new{ nil }.then
|
47
|
+
}.should raise_error(ArgumentError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'raises an exception if called twice' do
|
51
|
+
lambda {
|
52
|
+
Defer.new{ nil }.then{|result| nil }.then{|result| nil }
|
53
|
+
}.should raise_error(IllegalMethodCallError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'raises an exception if an operation was provided at construction' do
|
57
|
+
lambda {
|
58
|
+
operation = proc{ nil }
|
59
|
+
Defer.new(operation, nil, nil).then{|result| nil }
|
60
|
+
}.should raise_error(IllegalMethodCallError)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'raises an exception if a callback was provided at construction' do
|
64
|
+
lambda {
|
65
|
+
callback = proc{|result|nil }
|
66
|
+
Defer.new(nil, callback, nil){ nil }.then{|result| nil }
|
67
|
+
}.should raise_error(IllegalMethodCallError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'returns self' do
|
71
|
+
deferred = Defer.new{ nil }
|
72
|
+
deferred.then{|result| nil }.should eq deferred
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context '#rescue' do
|
77
|
+
|
78
|
+
it 'raises an exception if no block given' do
|
79
|
+
lambda {
|
80
|
+
Defer.new{ nil }.rescue
|
81
|
+
}.should raise_error(ArgumentError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'raises an exception if called twice' do
|
85
|
+
lambda {
|
86
|
+
Defer.new{ nil }.rescue{ nil }.rescue{ nil }
|
87
|
+
}.should raise_error(IllegalMethodCallError)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'raises an exception if an operation was provided at construction' do
|
91
|
+
lambda {
|
92
|
+
operation = proc{ nil }
|
93
|
+
Defer.new(operation, nil, nil).rescue{|ex| nil }
|
94
|
+
}.should raise_error(IllegalMethodCallError)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'raises an exception if an errorback was provided at construction' do
|
98
|
+
lambda {
|
99
|
+
errorback = proc{|ex| nil }
|
100
|
+
Defer.new(nil, nil, errorback){ nil }.rescue{|ex| nil }
|
101
|
+
}.should raise_error(IllegalMethodCallError)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns self' do
|
105
|
+
deferred = Defer.new{ nil }
|
106
|
+
deferred.rescue{|ex| nil }.should eq deferred
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'aliases #catch' do
|
110
|
+
lambda {
|
111
|
+
Defer.new{ nil }.catch{|ex| nil }
|
112
|
+
}.should_not raise_error
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'aliases #on_error' do
|
116
|
+
lambda {
|
117
|
+
Defer.new{ nil }.on_error{|ex| nil }
|
118
|
+
}.should_not raise_error
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context '#go' do
|
123
|
+
|
124
|
+
it 'starts the thread if not started' do
|
125
|
+
deferred = Defer.new{ nil }
|
126
|
+
$GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
|
127
|
+
deferred.go
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'does nothing if called more than once' do
|
131
|
+
deferred = Defer.new{ nil }
|
132
|
+
deferred.go
|
133
|
+
$GLOBAL_THREAD_POOL.should_not_receive(:post)
|
134
|
+
deferred.go
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'does nothing if thread started at construction' do
|
138
|
+
operation = proc{ nil }
|
139
|
+
callback = proc{|result| nil }
|
140
|
+
errorback = proc{|ex| nil }
|
141
|
+
deferred = Defer.new(operation, callback, errorback)
|
142
|
+
$GLOBAL_THREAD_POOL.should_not_receive(:post)
|
143
|
+
deferred.go
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'fulfillment' do
|
148
|
+
|
149
|
+
it 'runs the operation' do
|
150
|
+
@expected = false
|
151
|
+
Defer.new{ @expected = true }.go
|
152
|
+
sleep(0.1)
|
153
|
+
@expected.should be_true
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'calls the callback when the operation is successful' do
|
157
|
+
@expected = false
|
158
|
+
Defer.new{ true }.then{|result| @expected = true }.go
|
159
|
+
sleep(0.1)
|
160
|
+
@expected.should be_true
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'passes the result of the block to the callback' do
|
164
|
+
@expected = false
|
165
|
+
Defer.new{ 'w00t' }.then{|result| @expected = result }.go
|
166
|
+
sleep(0.1)
|
167
|
+
@expected.should eq 'w00t'
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'does not call the errorback when the operation is successful' do
|
171
|
+
@expected = true
|
172
|
+
Defer.new{ nil }.rescue{|ex| @expected = false }.go
|
173
|
+
sleep(0.1)
|
174
|
+
@expected.should be_true
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'calls the errorback if the operation throws an exception' do
|
178
|
+
@expected = false
|
179
|
+
Defer.new{ raise StandardError }.rescue{|ex| @expected = true }.go
|
180
|
+
sleep(0.1)
|
181
|
+
@expected.should be_true
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'passes the exception object to the errorback' do
|
185
|
+
@expected = nil
|
186
|
+
Defer.new{ raise StandardError }.rescue{|ex| @expected = ex }.go
|
187
|
+
sleep(0.1)
|
188
|
+
@expected.should be_a(StandardError)
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'does not call the callback when the operation fails' do
|
192
|
+
@expected = true
|
193
|
+
Defer.new{ raise StandardError }.then{|result| @expected = false }.go
|
194
|
+
sleep(0.1)
|
195
|
+
@expected.should be_true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'concurrent/agent'
|
4
|
+
require 'concurrent/future'
|
5
|
+
require 'concurrent/promise'
|
6
|
+
|
7
|
+
module Concurrent
|
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,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Concurrent
|
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{ subject.wait; @expected = true }
|
35
|
+
sleep(0.1)
|
36
|
+
subject.set
|
37
|
+
sleep(0.1)
|
38
|
+
@expected.should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'sets the state to set' do
|
42
|
+
subject.set
|
43
|
+
subject.should be_set
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context '#reset' do
|
48
|
+
|
49
|
+
it 'sets the state to unset' do
|
50
|
+
subject.set
|
51
|
+
subject.should be_set
|
52
|
+
subject.reset
|
53
|
+
subject.should_not be_set
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context '#pulse' do
|
58
|
+
|
59
|
+
it 'triggers the event' do
|
60
|
+
subject.reset
|
61
|
+
@expected = false
|
62
|
+
Thread.new{ subject.wait; @expected = true }
|
63
|
+
sleep(0.1)
|
64
|
+
subject.pulse
|
65
|
+
sleep(0.1)
|
66
|
+
@expected.should be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'sets the state to unset' do
|
70
|
+
subject.pulse
|
71
|
+
sleep(0.1)
|
72
|
+
subject.should_not be_set
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context '#wait' do
|
77
|
+
|
78
|
+
it 'returns immediately when the event has been set' do
|
79
|
+
subject.reset
|
80
|
+
@expected = false
|
81
|
+
subject.set
|
82
|
+
Thread.new{ subject.wait(1000); @expected = true}
|
83
|
+
sleep(1)
|
84
|
+
@expected.should be_true
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns true once the event is set' do
|
88
|
+
subject.set
|
89
|
+
subject.wait.should be_true
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'blocks indefinitely when the timer is nil' do
|
93
|
+
subject.reset
|
94
|
+
@expected = false
|
95
|
+
Thread.new{ subject.wait; @expected = true}
|
96
|
+
subject.set
|
97
|
+
sleep(1)
|
98
|
+
@expected.should be_true
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'stops waiting when the timer expires' do
|
102
|
+
subject.reset
|
103
|
+
@expected = false
|
104
|
+
Thread.new{ subject.wait(0.5); @expected = true}
|
105
|
+
sleep(1)
|
106
|
+
@expected.should be_true
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns false when the timer expires' do
|
110
|
+
subject.reset
|
111
|
+
subject.wait(1).should be_false
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'triggers multiple waiting threads' do
|
115
|
+
subject.reset
|
116
|
+
@expected = []
|
117
|
+
5.times{ Thread.new{ subject.wait; @expected << Thread.current.object_id } }
|
118
|
+
subject.set
|
119
|
+
sleep(1)
|
120
|
+
@expected.length.should eq 5
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'behaves appropriately if wait begins while #set is processing' do
|
124
|
+
subject.reset
|
125
|
+
@expected = []
|
126
|
+
5.times{ Thread.new{ subject.wait(5) } }
|
127
|
+
subject.set
|
128
|
+
5.times{ Thread.new{ subject.wait; @expected << Thread.current.object_id } }
|
129
|
+
sleep(1)
|
130
|
+
@expected.length.should eq 5
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|