ione 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/ione/byte_buffer.rb +2 -0
- data/lib/ione/future.rb +91 -27
- data/lib/ione/io/acceptor.rb +27 -6
- data/lib/ione/io/base_connection.rb +25 -13
- data/lib/ione/io/connection.rb +3 -2
- data/lib/ione/io/io_reactor.rb +171 -50
- data/lib/ione/io/server_connection.rb +2 -1
- data/lib/ione/io/ssl_connection.rb +1 -1
- data/lib/ione/io/ssl_server_connection.rb +7 -4
- data/lib/ione/version.rb +1 -1
- data/spec/ione/future_spec.rb +112 -0
- data/spec/ione/io/connection_spec.rb +4 -4
- data/spec/ione/io/io_reactor_spec.rb +235 -18
- metadata +2 -2
data/spec/ione/future_spec.rb
CHANGED
@@ -822,6 +822,17 @@ module Ione
|
|
822
822
|
future.value.should == {'foo' => 'bar', 'qux' => 'baz', 'hello' => 'world'}
|
823
823
|
end
|
824
824
|
|
825
|
+
it 'accepts boolean accumulators' do
|
826
|
+
futures = [
|
827
|
+
Future.resolved([:foo]),
|
828
|
+
Future.resolved([]),
|
829
|
+
]
|
830
|
+
future = Future.reduce(futures, false) do |accumulator, value|
|
831
|
+
accumulator || value.empty?
|
832
|
+
end
|
833
|
+
future.value.should == true
|
834
|
+
end
|
835
|
+
|
825
836
|
it 'calls the block with the values in the order of the source futures' do
|
826
837
|
promises = [Promise.new, Promise.new, Promise.new, Promise.new, Promise.new]
|
827
838
|
futures = promises.map(&:future)
|
@@ -868,6 +879,18 @@ module Ione
|
|
868
879
|
future.should be_failed
|
869
880
|
end
|
870
881
|
|
882
|
+
it 'allows invocations to return nil' do
|
883
|
+
futures = [Future.resolved(1), Future.resolved(2), Future.resolved(3)]
|
884
|
+
future = Future.reduce(futures, []) do |accumulator, value|
|
885
|
+
if value == 2
|
886
|
+
nil
|
887
|
+
else
|
888
|
+
value
|
889
|
+
end
|
890
|
+
end
|
891
|
+
future.value.should eq(3)
|
892
|
+
end
|
893
|
+
|
871
894
|
context 'when the list of futures is empty' do
|
872
895
|
it 'returns a future that resolves to the initial value' do
|
873
896
|
Future.reduce([], :foo).value.should == :foo
|
@@ -938,6 +961,95 @@ module Ione
|
|
938
961
|
end
|
939
962
|
end
|
940
963
|
|
964
|
+
describe '.after' do
|
965
|
+
context 'returns a new future which' do
|
966
|
+
it 'is resolved when the source futures are resolved' do
|
967
|
+
p1 = Promise.new
|
968
|
+
p2 = Promise.new
|
969
|
+
f = Future.after(p1.future, p2.future)
|
970
|
+
p1.fulfill
|
971
|
+
f.should_not be_resolved
|
972
|
+
p2.fulfill
|
973
|
+
f.should be_resolved
|
974
|
+
end
|
975
|
+
|
976
|
+
it 'returns no value' do
|
977
|
+
p1 = Promise.new
|
978
|
+
p2 = Promise.new
|
979
|
+
p3 = Promise.new
|
980
|
+
f = Future.after(p1.future, p2.future, p3.future)
|
981
|
+
p2.fulfill(2)
|
982
|
+
p1.fulfill(1)
|
983
|
+
p3.fulfill(3)
|
984
|
+
f.value.should be_nil
|
985
|
+
end
|
986
|
+
|
987
|
+
it 'fails if any of the source futures fail' do
|
988
|
+
p1 = Promise.new
|
989
|
+
p2 = Promise.new
|
990
|
+
p3 = Promise.new
|
991
|
+
p4 = Promise.new
|
992
|
+
f = Future.after(p1.future, p2.future, p3.future, p4.future)
|
993
|
+
p2.fulfill
|
994
|
+
p1.fail(StandardError.new('hurgh'))
|
995
|
+
p3.fail(StandardError.new('murgasd'))
|
996
|
+
p4.fulfill
|
997
|
+
expect { f.value }.to raise_error(/hurgh|murgasd/)
|
998
|
+
f.should be_failed
|
999
|
+
end
|
1000
|
+
|
1001
|
+
it 'completes with nil when no futures are given' do
|
1002
|
+
Future.after.value.should be_nil
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
it 'completes with nil when an empty list is given' do
|
1006
|
+
Future.after([]).value.should be_nil
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
it 'completes with nil when an empty enumerable is given' do
|
1010
|
+
Future.after([].to_enum).value.should be_nil
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
it 'completes with nil when a single future is given' do
|
1014
|
+
f = Future.resolved(1)
|
1015
|
+
Future.after(f).value.should be_nil
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
it 'accepts a list of futures' do
|
1019
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
1020
|
+
futures = promises.map(&:future)
|
1021
|
+
f = Future.after(futures)
|
1022
|
+
promises.each(&:fulfill)
|
1023
|
+
f.value.should be_nil
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
it 'accepts an enumerable of futures' do
|
1027
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
1028
|
+
futures = promises.map(&:future).to_enum
|
1029
|
+
f = Future.after(futures)
|
1030
|
+
promises.each(&:fulfill)
|
1031
|
+
f.value.should be_nil
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
it 'accepts an enumerable of one future' do
|
1035
|
+
promises = [Promise.new]
|
1036
|
+
futures = promises.map(&:future).to_enum
|
1037
|
+
f = Future.after(futures)
|
1038
|
+
promises.each(&:fulfill)
|
1039
|
+
f.value.should be_nil
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
it 'accepts anything that implements #on_complete as futures' do
|
1043
|
+
ff1, ff2, ff3 = double, double, double
|
1044
|
+
ff1.stub(:on_complete) { |&listener| listener.call(1, nil) }
|
1045
|
+
ff2.stub(:on_complete) { |&listener| listener.call(2, nil) }
|
1046
|
+
ff3.stub(:on_complete) { |&listener| listener.call(3, nil) }
|
1047
|
+
future = Future.after(ff1, ff2, ff3)
|
1048
|
+
future.value.should be_nil
|
1049
|
+
end
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
|
941
1053
|
describe '.all' do
|
942
1054
|
context 'returns a new future which' do
|
943
1055
|
it 'is resolved when the source futures are resolved' do
|
@@ -312,15 +312,15 @@ module Ione
|
|
312
312
|
end
|
313
313
|
|
314
314
|
it 'includes the connection state' do
|
315
|
-
handler.to_s.should include('
|
315
|
+
handler.to_s.should include('CONNECTING')
|
316
316
|
socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
|
317
317
|
handler.connect
|
318
|
-
handler.to_s.should include('
|
318
|
+
handler.to_s.should include('CONNECTING')
|
319
319
|
socket.stub(:connect_nonblock)
|
320
320
|
handler.connect
|
321
|
-
handler.to_s.should include('
|
321
|
+
handler.to_s.should include('CONNECTED')
|
322
322
|
handler.close
|
323
|
-
handler.to_s.should include('
|
323
|
+
handler.to_s.should include('CLOSED')
|
324
324
|
end
|
325
325
|
end
|
326
326
|
end
|
@@ -59,12 +59,6 @@ module Ione
|
|
59
59
|
reactor.should be_running
|
60
60
|
end
|
61
61
|
|
62
|
-
it 'cannot be started again once stopped' do
|
63
|
-
reactor.start.value
|
64
|
-
reactor.stop.value
|
65
|
-
expect { reactor.start }.to raise_error(ReactorError)
|
66
|
-
end
|
67
|
-
|
68
62
|
it 'calls the selector' do
|
69
63
|
called = false
|
70
64
|
selector.handler { called = true; [[], [], []] }
|
@@ -73,6 +67,95 @@ module Ione
|
|
73
67
|
reactor.stop.value
|
74
68
|
called.should be_true, 'expected the selector to have been called'
|
75
69
|
end
|
70
|
+
|
71
|
+
context 'when stopping' do
|
72
|
+
it 'waits for the reactor to stop, then starts it again' do
|
73
|
+
barrier = Queue.new
|
74
|
+
selector.handler do
|
75
|
+
barrier.pop
|
76
|
+
[[], [], []]
|
77
|
+
end
|
78
|
+
reactor.start.value
|
79
|
+
stopped_future = reactor.stop
|
80
|
+
restarted_future = reactor.start
|
81
|
+
sequence = []
|
82
|
+
stopped_future.on_complete { sequence << :stopped }
|
83
|
+
restarted_future.on_complete { sequence << :restarted }
|
84
|
+
barrier.push(nil)
|
85
|
+
stopped_future.value
|
86
|
+
restarted_future.value
|
87
|
+
begin
|
88
|
+
sequence.should == [:stopped, :restarted]
|
89
|
+
ensure
|
90
|
+
reactor.stop
|
91
|
+
barrier.push(nil) while reactor.running?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'restarts the reactor even when restarted before a failed stop' do
|
96
|
+
barrier = Queue.new
|
97
|
+
selector.handler do
|
98
|
+
if barrier.pop == :fail
|
99
|
+
raise 'Blurgh'
|
100
|
+
else
|
101
|
+
[[], [], []]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
reactor.start.value
|
105
|
+
stopped_future = reactor.stop
|
106
|
+
restarted_future = reactor.start
|
107
|
+
crashed = false
|
108
|
+
restarted = false
|
109
|
+
stopped_future.on_failure { crashed = true }
|
110
|
+
restarted_future.on_complete { restarted = true }
|
111
|
+
barrier.push(:fail)
|
112
|
+
stopped_future.value rescue nil
|
113
|
+
restarted_future.value
|
114
|
+
begin
|
115
|
+
crashed.should be_true
|
116
|
+
restarted.should be_true
|
117
|
+
ensure
|
118
|
+
reactor.stop
|
119
|
+
barrier.push(nil) while reactor.running?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when stopped' do
|
125
|
+
before do
|
126
|
+
reactor.start.value
|
127
|
+
reactor.stop.value
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'starts the reactor again' do
|
131
|
+
reactor.start.value
|
132
|
+
reactor.should be_running
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'when already started' do
|
137
|
+
it 'is not started again' do
|
138
|
+
calls = 0
|
139
|
+
lock = Mutex.new
|
140
|
+
ticks = Queue.new
|
141
|
+
barrier = Queue.new
|
142
|
+
selector.handler do
|
143
|
+
ticks.push(:tick)
|
144
|
+
barrier.pop
|
145
|
+
[[], [], []]
|
146
|
+
end
|
147
|
+
reactor.start.value
|
148
|
+
reactor.start.value
|
149
|
+
reactor.start.value
|
150
|
+
begin
|
151
|
+
ticks.pop.should_not be_nil
|
152
|
+
ticks.size.should be_zero
|
153
|
+
ensure
|
154
|
+
reactor.stop
|
155
|
+
barrier.push(nil) while reactor.running?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
76
159
|
end
|
77
160
|
|
78
161
|
describe '#stop' do
|
@@ -114,6 +197,20 @@ module Ione
|
|
114
197
|
active_timer1.should be_failed
|
115
198
|
active_timer2.should be_failed
|
116
199
|
end
|
200
|
+
|
201
|
+
context 'when not started' do
|
202
|
+
it 'does nothing' do
|
203
|
+
reactor = described_class.new(selector: selector, clock: clock)
|
204
|
+
expect { reactor.stop.value }.to_not raise_error
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'when already stopped' do
|
209
|
+
it 'does nothing' do
|
210
|
+
reactor.stop.value
|
211
|
+
expect { reactor.stop.value }.to_not raise_error
|
212
|
+
end
|
213
|
+
end
|
117
214
|
end
|
118
215
|
|
119
216
|
describe '#on_error' do
|
@@ -145,6 +242,36 @@ module Ione
|
|
145
242
|
await { called }
|
146
243
|
called.should be_true, 'expected all close listeners to have been called'
|
147
244
|
end
|
245
|
+
|
246
|
+
it 'calls all listeners when the reactor crashes after being restarted' do
|
247
|
+
calls = []
|
248
|
+
barrier = Queue.new
|
249
|
+
selector.handler { barrier.pop; raise 'Blurgh' }
|
250
|
+
reactor.on_error { calls << :pre_started }
|
251
|
+
reactor.start
|
252
|
+
reactor.on_error { calls << :post_started }
|
253
|
+
barrier.push(nil)
|
254
|
+
await { !reactor.running? }
|
255
|
+
reactor.on_error { calls << :pre_restarted }
|
256
|
+
calls.should == [
|
257
|
+
:pre_started,
|
258
|
+
:post_started,
|
259
|
+
:pre_restarted,
|
260
|
+
]
|
261
|
+
reactor.start
|
262
|
+
reactor.on_error { calls << :post_restarted }
|
263
|
+
barrier.push(nil)
|
264
|
+
await { !reactor.running? }
|
265
|
+
calls.should == [
|
266
|
+
:pre_started,
|
267
|
+
:post_started,
|
268
|
+
:pre_restarted,
|
269
|
+
:pre_started,
|
270
|
+
:post_started,
|
271
|
+
:pre_restarted,
|
272
|
+
:post_restarted,
|
273
|
+
]
|
274
|
+
end
|
148
275
|
end
|
149
276
|
|
150
277
|
describe '#connect' do
|
@@ -199,6 +326,24 @@ module Ione
|
|
199
326
|
f = reactor.connect('example.com', 9999, ssl: ssl_context)
|
200
327
|
expect { f.value }.to raise_error
|
201
328
|
end
|
329
|
+
|
330
|
+
context 'when called before the reactor is started' do
|
331
|
+
it 'waits for the reactor to start' do
|
332
|
+
f = reactor.connect('example.com', 9999)
|
333
|
+
reactor.start.value
|
334
|
+
f.value
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
context 'when called after the reactor has stopped' do
|
339
|
+
it 'waits for the reactor to be restarted' do
|
340
|
+
reactor.start.value
|
341
|
+
reactor.stop.value
|
342
|
+
f = reactor.connect('example.com', 9999)
|
343
|
+
reactor.start.value
|
344
|
+
f.value
|
345
|
+
end
|
346
|
+
end
|
202
347
|
end
|
203
348
|
|
204
349
|
describe '#bind' do
|
@@ -252,22 +397,72 @@ module Ione
|
|
252
397
|
acceptor = reactor.bind(ENV['SERVER_HOST'], port, ssl: ssl_context).value
|
253
398
|
acceptor.should be_an(SslAcceptor)
|
254
399
|
end
|
400
|
+
|
401
|
+
context 'when called before the reactor is started' do
|
402
|
+
it 'waits for the reactor to start' do
|
403
|
+
f = reactor.bind(ENV['SERVER_HOST'], port, 5)
|
404
|
+
reactor.start.value
|
405
|
+
f.value
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
context 'when called after the reactor has stopped' do
|
410
|
+
it 'waits for the reactor to be restarted' do
|
411
|
+
reactor.start.value
|
412
|
+
reactor.stop.value
|
413
|
+
f = reactor.bind(ENV['SERVER_HOST'], port, 5)
|
414
|
+
reactor.start.value
|
415
|
+
f.value
|
416
|
+
end
|
417
|
+
end
|
255
418
|
end
|
256
419
|
|
257
420
|
describe '#schedule_timer' do
|
258
|
-
|
259
|
-
|
421
|
+
context 'when the reactor is running' do
|
422
|
+
before do
|
423
|
+
reactor.start.value
|
424
|
+
end
|
425
|
+
|
426
|
+
after do
|
427
|
+
reactor.stop.value
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'returns a future that is resolved after the specified duration' do
|
431
|
+
clock.stub(:now).and_return(1)
|
432
|
+
f = reactor.schedule_timer(0.1)
|
433
|
+
clock.stub(:now).and_return(1.1)
|
434
|
+
await { f.resolved? }
|
435
|
+
end
|
260
436
|
end
|
261
437
|
|
262
|
-
|
263
|
-
|
438
|
+
context 'when called before the reactor is started' do
|
439
|
+
after do
|
440
|
+
reactor.stop.value if reactor.running?
|
441
|
+
end
|
442
|
+
|
443
|
+
it 'waits for the reactor to start' do
|
444
|
+
clock.stub(:now).and_return(1)
|
445
|
+
f = reactor.schedule_timer(0.1)
|
446
|
+
clock.stub(:now).and_return(2)
|
447
|
+
reactor.start.value
|
448
|
+
f.value
|
449
|
+
end
|
264
450
|
end
|
265
451
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
452
|
+
context 'when called after the reactor has stopped' do
|
453
|
+
after do
|
454
|
+
reactor.stop.value if reactor.running?
|
455
|
+
end
|
456
|
+
|
457
|
+
it 'waits for the reactor to be restarted' do
|
458
|
+
reactor.start.value
|
459
|
+
reactor.stop.value
|
460
|
+
clock.stub(:now).and_return(1)
|
461
|
+
f = reactor.schedule_timer(0.1)
|
462
|
+
clock.stub(:now).and_return(2)
|
463
|
+
reactor.start.value
|
464
|
+
f.value
|
465
|
+
end
|
271
466
|
end
|
272
467
|
end
|
273
468
|
|
@@ -322,6 +517,29 @@ module Ione
|
|
322
517
|
it 'does nothing when given nil' do
|
323
518
|
reactor.cancel_timer(nil)
|
324
519
|
end
|
520
|
+
|
521
|
+
context 'when called before the reactor is started' do
|
522
|
+
it 'removes the timer before the reactor starts' do
|
523
|
+
clock.stub(:now).and_return(1)
|
524
|
+
f = reactor.schedule_timer(0.1)
|
525
|
+
reactor.cancel_timer(f)
|
526
|
+
clock.stub(:now).and_return(2)
|
527
|
+
f.should be_failed
|
528
|
+
reactor.start.value
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
context 'when called after the reactor has stopped' do
|
533
|
+
it 'removes the timer before the reactor is started again' do
|
534
|
+
reactor.start.value
|
535
|
+
reactor.stop.value
|
536
|
+
clock.stub(:now).and_return(1)
|
537
|
+
f = reactor.schedule_timer(0.1)
|
538
|
+
reactor.cancel_timer(f)
|
539
|
+
f.should be_failed
|
540
|
+
reactor.start.value
|
541
|
+
end
|
542
|
+
end
|
325
543
|
end
|
326
544
|
|
327
545
|
describe '#to_s' do
|
@@ -330,9 +548,8 @@ module Ione
|
|
330
548
|
reactor.to_s.should include('Ione::Io::IoReactor')
|
331
549
|
end
|
332
550
|
|
333
|
-
it 'includes
|
334
|
-
reactor.to_s.should include('
|
335
|
-
reactor.to_s.should include('#<Ione::Io::Unblocker>')
|
551
|
+
it 'includes the state' do
|
552
|
+
reactor.to_s.should include('PENDING')
|
336
553
|
end
|
337
554
|
end
|
338
555
|
end
|