ione 1.2.0 → 1.2.1
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 +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
|