ione 1.1.5 → 1.2.0.pre0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ione/future.rb +320 -59
- data/lib/ione/io/base_connection.rb +2 -5
- data/lib/ione/io/connection.rb +1 -0
- data/lib/ione/io/io_reactor.rb +17 -29
- data/lib/ione/io/server_connection.rb +1 -0
- data/lib/ione/version.rb +1 -1
- data/spec/ione/future_spec.rb +357 -48
- data/spec/ione/io/io_reactor_spec.rb +14 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1ae6f32b5b3e404b07f6ec0a0b20856a0458719
|
4
|
+
data.tar.gz: 6a1e98dcd2cb657b2cbbe7dcc29514e38c672608
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b57e6559e0be8dca588b6f4b7d1cf069041848d368de377ad0c4db94f4863c0d0194d66843190fc18f1de2c92a0465429d32773d4994c8aee405a584aa167544
|
7
|
+
data.tar.gz: 8a30c9516df0d52376ebda8958446fdb35d4fb9adbe4028d180afb438f31061bb77ede9ecebde1e7a640ba19364a2a2a35acc691288585de35e2ea64453d5526
|
data/lib/ione/future.rb
CHANGED
@@ -44,8 +44,13 @@ module Ione
|
|
44
44
|
#
|
45
45
|
# @param [Ione::Future] future the future to observe
|
46
46
|
def observe(future)
|
47
|
-
future.
|
48
|
-
|
47
|
+
future.on_complete do |_, v, e|
|
48
|
+
if e
|
49
|
+
fail(e)
|
50
|
+
else
|
51
|
+
fulfill(v)
|
52
|
+
end
|
53
|
+
end
|
49
54
|
end
|
50
55
|
|
51
56
|
# Run the given block and fulfill this promise with its result. If the block
|
@@ -81,12 +86,14 @@ module Ione
|
|
81
86
|
# The value of the combined future is an array of the values of the
|
82
87
|
# constituent futures.
|
83
88
|
#
|
84
|
-
# @param [Array<Ione::Future>] futures the futures to combine
|
89
|
+
# @param [Array<Ione::Future>] futures the futures to combine (this argument
|
90
|
+
# can be a splatted array or a regular array passed as sole argument)
|
85
91
|
# @return [Ione::Future<Array>] an array of the values of the constituent
|
86
92
|
# futures
|
87
93
|
def all(*futures)
|
88
94
|
return resolved([]) if futures.empty?
|
89
|
-
|
95
|
+
futures = futures.size == 1 && futures.first.is_a?(Enumerable) ? futures.first : futures
|
96
|
+
return futures.first.map { |v| [v] } if futures.is_a?(Array) && futures.size == 1
|
90
97
|
CombinedFuture.new(futures)
|
91
98
|
end
|
92
99
|
|
@@ -94,14 +101,90 @@ module Ione
|
|
94
101
|
# (resolved) of the specified futures. If all of the futures fail, the
|
95
102
|
# returned future will also fail (with the error of the last failed future).
|
96
103
|
#
|
97
|
-
# @param [Array<Ione::Future>] futures the futures to monitor
|
104
|
+
# @param [Array<Ione::Future>] futures the futures to monitor (this argument
|
105
|
+
# can be a splatted array or a regular array passed as sole argument)
|
98
106
|
# @return [Ione::Future] a future which represents the first completing future
|
99
107
|
def first(*futures)
|
100
108
|
return resolved if futures.empty?
|
101
|
-
|
109
|
+
futures = futures.size == 1 && futures.first.is_a?(Enumerable) ? futures.first : futures
|
110
|
+
return futures.first if futures.is_a?(Array) && futures.size == 1
|
102
111
|
FirstFuture.new(futures)
|
103
112
|
end
|
104
113
|
|
114
|
+
# Takes calls the block once for each element in an array, expecting each
|
115
|
+
# invocation to return a future, and returns a future that resolves to
|
116
|
+
# an array of the values of those futures.
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# ids = [1, 2, 3]
|
120
|
+
# future = Future.traverse(ids) { |id| load_thing(id) }
|
121
|
+
# future.value # => [thing1, thing2, thing3]
|
122
|
+
#
|
123
|
+
# @param [Array<Object>] values an array whose elements will be passed to
|
124
|
+
# the block, one by one
|
125
|
+
# @yieldparam [Object] value each element from the array
|
126
|
+
# @yieldreturn [Ione::Future] a future
|
127
|
+
# @return [Ione::Future] a future that will resolve to an array of the values
|
128
|
+
# of the futures returned by the block
|
129
|
+
def traverse(values, &block)
|
130
|
+
all(*values.map(&block))
|
131
|
+
rescue => e
|
132
|
+
failed(e)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns a future that will resolve to a value which is the reduction of
|
136
|
+
# the values of a list of source futures.
|
137
|
+
#
|
138
|
+
# This is essentially a parallel, streaming version of {Enumerable#reduce},
|
139
|
+
# but for futures. Use this method for example when you want to do a number
|
140
|
+
# of asynchronous operations in parallel and then merge the results together
|
141
|
+
# when all are done.
|
142
|
+
#
|
143
|
+
# The block will not be called concurrently, which means that unless you're
|
144
|
+
# handling the initial value or other values in the scope of the block you
|
145
|
+
# don't need (and shouldn't) do any locking to ensure that the accumulator
|
146
|
+
# passed to the block is safe to modify. It is, of course, even better if
|
147
|
+
# you don't modify the accumulator, but return a new, immutable value on
|
148
|
+
# each invocation.
|
149
|
+
#
|
150
|
+
# @example Merging the results of multipe asynchronous calls
|
151
|
+
# futures = ... # a list of futures that will resolve to hashes
|
152
|
+
# merged_future = Future.reduce(futures, {}) do |accumulator, value|
|
153
|
+
# accumulator.merge(value)
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# @example Reducing with an associative and commutative function, like addition
|
157
|
+
# futures = ... # a list of futures that will resolve to numbers
|
158
|
+
# sum_future = Future.reduce(futures, 0, ordered: false) do |accumulator, value|
|
159
|
+
# accumulator + value
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# @param [Array<Ione::Future>] futures an array of futures whose values
|
163
|
+
# should be reduced
|
164
|
+
# @param [Object] initial_value the initial value of the accumulator
|
165
|
+
# @param [Hash] options
|
166
|
+
# @option options [Boolean] :ordered (true) whether or not to respect the
|
167
|
+
# order of the input when reducing – when true the block will be called
|
168
|
+
# with the values of the source futures in the order they have in the
|
169
|
+
# given list, when false the block will be called in the order that the
|
170
|
+
# futures resolve (which means that your reducer function needs to be
|
171
|
+
# associative and commutative).
|
172
|
+
# @yieldparam [Object] accumulator the value of the last invocation of the
|
173
|
+
# block, or the initial value if this is the first invocation
|
174
|
+
# @yieldparam [Object] value the value of one of the source futures
|
175
|
+
# @yieldreturn [Object] the value to pass as accumulator to the next
|
176
|
+
# invocation of the block
|
177
|
+
# @return [Ione::Future] a future that will resolve to the value returned
|
178
|
+
# from the last invocation of the block, or nil when the list of futures
|
179
|
+
# is empty.
|
180
|
+
def reduce(futures, initial_value=nil, options=nil, &reducer)
|
181
|
+
if options && options[:ordered] == false
|
182
|
+
UnorderedReducingFuture.new(futures, initial_value, reducer)
|
183
|
+
else
|
184
|
+
OrderedReducingFuture.new(futures, initial_value, reducer)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
105
188
|
# Creates a new pre-resolved future.
|
106
189
|
#
|
107
190
|
# @param [Object, nil] value the value of the created future
|
@@ -131,10 +214,19 @@ module Ione
|
|
131
214
|
# @yieldreturn [Object] the transformed value
|
132
215
|
# @return [Ione::Future] a new future representing the transformed value
|
133
216
|
def map(value=nil, &block)
|
134
|
-
CompletableFuture.new
|
135
|
-
|
136
|
-
|
217
|
+
f = CompletableFuture.new
|
218
|
+
on_complete do |_, v, e|
|
219
|
+
if e
|
220
|
+
f.fail(e)
|
221
|
+
else
|
222
|
+
begin
|
223
|
+
f.resolve(block ? block.call(v) : value)
|
224
|
+
rescue => e
|
225
|
+
f.fail(e)
|
226
|
+
end
|
227
|
+
end
|
137
228
|
end
|
229
|
+
f
|
138
230
|
end
|
139
231
|
|
140
232
|
# Returns a new future representing a transformation of this future's value,
|
@@ -149,16 +241,82 @@ module Ione
|
|
149
241
|
# @yieldreturn [Ione::Future] a future representing the transformed value
|
150
242
|
# @return [Ione::Future] a new future representing the transformed value
|
151
243
|
def flat_map(&block)
|
152
|
-
CompletableFuture.new
|
153
|
-
|
154
|
-
|
244
|
+
f = CompletableFuture.new
|
245
|
+
on_complete do |_, v, e|
|
246
|
+
if e
|
247
|
+
f.fail(e)
|
248
|
+
else
|
249
|
+
begin
|
250
|
+
ff = block.call(v)
|
251
|
+
ff.on_complete do |_, vv, ee|
|
252
|
+
if ee
|
253
|
+
f.fail(ee)
|
254
|
+
else
|
255
|
+
f.resolve(vv)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
rescue => e
|
259
|
+
f.fail(e)
|
260
|
+
end
|
261
|
+
end
|
155
262
|
end
|
263
|
+
f
|
264
|
+
end
|
265
|
+
|
266
|
+
# Returns a new future representing a transformation of this future's value,
|
267
|
+
# similarily to {#map}, but acts as {#flat_map} when the block returns a
|
268
|
+
# {Future}.
|
269
|
+
#
|
270
|
+
# This method is useful when you want to transform the value of a future,
|
271
|
+
# but whether or not it can be done synchronously or require an asynchronous
|
272
|
+
# operation depends on the value of the future.
|
273
|
+
#
|
274
|
+
# @example
|
275
|
+
# future1 = load_something
|
276
|
+
# future2 = future1.then do |result|
|
277
|
+
# if result.empty?
|
278
|
+
# # make a new async call to load fallback value
|
279
|
+
# load_something_else
|
280
|
+
# else
|
281
|
+
# result
|
282
|
+
# end
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# @yieldparam [Object] value the value of this future
|
286
|
+
# @yieldreturn [Object, Ione::Future] the transformed value, or a future
|
287
|
+
# that will resolve to the transformed value.
|
288
|
+
# @return [Ione::Future] a new future representing the transformed value
|
289
|
+
def then(&block)
|
290
|
+
f = CompletableFuture.new
|
291
|
+
on_complete do |_, v, e|
|
292
|
+
if e
|
293
|
+
f.fail(e)
|
294
|
+
else
|
295
|
+
begin
|
296
|
+
fv = block.call(v)
|
297
|
+
if fv.respond_to?(:on_complete)
|
298
|
+
fv.on_complete do |_, vv, ee|
|
299
|
+
if ee
|
300
|
+
f.fail(ee)
|
301
|
+
else
|
302
|
+
f.resolve(vv)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
else
|
306
|
+
f.resolve(fv)
|
307
|
+
end
|
308
|
+
rescue => e
|
309
|
+
f.fail(e)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
f
|
156
314
|
end
|
157
315
|
|
158
316
|
# Returns a new future which represents either the value of the original
|
159
317
|
# future, or the result of the given block, if the original future fails.
|
160
318
|
#
|
161
|
-
# This method is similar to{#map}, but is triggered by a failure. You can
|
319
|
+
# This method is similar to {#map}, but is triggered by a failure. You can
|
162
320
|
# also think of it as a `rescue` block for asynchronous operations.
|
163
321
|
#
|
164
322
|
# If the block raises an error a failed future with that error will be
|
@@ -175,10 +333,19 @@ module Ione
|
|
175
333
|
# @yieldreturn [Object] the value of the new future
|
176
334
|
# @return [Ione::Future] a new future representing a value recovered from the error
|
177
335
|
def recover(value=nil, &block)
|
178
|
-
CompletableFuture.new
|
179
|
-
|
180
|
-
|
336
|
+
f = CompletableFuture.new
|
337
|
+
on_complete do |_, v, e|
|
338
|
+
if e
|
339
|
+
begin
|
340
|
+
f.resolve(block ? block.call(e) : value)
|
341
|
+
rescue => e
|
342
|
+
f.fail(e)
|
343
|
+
end
|
344
|
+
else
|
345
|
+
f.resolve(v)
|
346
|
+
end
|
181
347
|
end
|
348
|
+
f
|
182
349
|
end
|
183
350
|
|
184
351
|
# Returns a new future which represents either the value of the original
|
@@ -204,27 +371,26 @@ module Ione
|
|
204
371
|
# @return [Ione::Future] a new future representing a value recovered from the
|
205
372
|
# error
|
206
373
|
def fallback(&block)
|
207
|
-
CompletableFuture.new
|
208
|
-
|
209
|
-
|
374
|
+
f = CompletableFuture.new
|
375
|
+
on_complete do |_, v, e|
|
376
|
+
if e
|
377
|
+
begin
|
378
|
+
ff = block.call(e)
|
379
|
+
ff.on_complete do |_, vv, ee|
|
380
|
+
if ee
|
381
|
+
f.fail(ee)
|
382
|
+
else
|
383
|
+
f.resolve(vv)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
rescue => e
|
387
|
+
f.fail(e)
|
388
|
+
end
|
389
|
+
else
|
390
|
+
f.resolve(v)
|
391
|
+
end
|
210
392
|
end
|
211
|
-
|
212
|
-
|
213
|
-
private
|
214
|
-
|
215
|
-
def run(f, value, producer, arg)
|
216
|
-
value = producer ? producer.call(arg) : value
|
217
|
-
f.resolve(value)
|
218
|
-
rescue => e
|
219
|
-
f.fail(e)
|
220
|
-
end
|
221
|
-
|
222
|
-
def chain(f, constructor, arg)
|
223
|
-
ff = constructor.call(arg)
|
224
|
-
ff.on_failure { |e| f.fail(e) }
|
225
|
-
ff.on_value { |v| f.resolve(v) }
|
226
|
-
rescue => e
|
227
|
-
f.fail(e)
|
393
|
+
f
|
228
394
|
end
|
229
395
|
end
|
230
396
|
|
@@ -251,7 +417,7 @@ module Ione
|
|
251
417
|
end
|
252
418
|
end
|
253
419
|
if run_immediately
|
254
|
-
listener.call(self) rescue nil
|
420
|
+
listener.call(self, @value, @error) rescue nil
|
255
421
|
end
|
256
422
|
nil
|
257
423
|
end
|
@@ -417,7 +583,7 @@ module Ione
|
|
417
583
|
listener.call(v) rescue nil
|
418
584
|
end
|
419
585
|
complete_listeners.each do |listener|
|
420
|
-
listener.call(self) rescue nil
|
586
|
+
listener.call(self, v, nil) rescue nil
|
421
587
|
end
|
422
588
|
nil
|
423
589
|
end
|
@@ -442,7 +608,7 @@ module Ione
|
|
442
608
|
listener.call(error) rescue nil
|
443
609
|
end
|
444
610
|
complete_listeners.each do |listener|
|
445
|
-
listener.call(self) rescue nil
|
611
|
+
listener.call(self, nil, error) rescue nil
|
446
612
|
end
|
447
613
|
nil
|
448
614
|
end
|
@@ -452,40 +618,135 @@ module Ione
|
|
452
618
|
class CombinedFuture < CompletableFuture
|
453
619
|
def initialize(futures)
|
454
620
|
super()
|
455
|
-
|
456
|
-
|
621
|
+
remaining = futures.count
|
622
|
+
values = Array.new(remaining)
|
457
623
|
futures.each_with_index do |f, i|
|
458
|
-
f.
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
624
|
+
f.on_complete do |_, v, e|
|
625
|
+
unless failed?
|
626
|
+
if e
|
627
|
+
fail(e)
|
628
|
+
else
|
629
|
+
@lock.lock
|
630
|
+
begin
|
631
|
+
values[i] = v
|
632
|
+
remaining -= 1
|
633
|
+
ensure
|
634
|
+
@lock.unlock
|
635
|
+
end
|
636
|
+
if remaining == 0
|
637
|
+
resolve(values)
|
638
|
+
end
|
639
|
+
end
|
465
640
|
end
|
466
|
-
|
467
|
-
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
# @private
|
647
|
+
class ReducingFuture < CompletableFuture
|
648
|
+
def initialize(futures, initial_value, reducer)
|
649
|
+
super()
|
650
|
+
@futures = Array(futures)
|
651
|
+
@remaining = @futures.size
|
652
|
+
@initial_value = initial_value
|
653
|
+
@accumulator = initial_value
|
654
|
+
@reducer = reducer
|
655
|
+
end
|
656
|
+
|
657
|
+
private
|
658
|
+
|
659
|
+
def reduce_one(value)
|
660
|
+
unless failed?
|
661
|
+
@lock.lock
|
662
|
+
begin
|
663
|
+
if @accumulator
|
664
|
+
@accumulator = @reducer.call(@accumulator, value)
|
665
|
+
else
|
666
|
+
@accumulator = value
|
468
667
|
end
|
668
|
+
@remaining -= 1
|
669
|
+
rescue => e
|
670
|
+
@lock.unlock
|
671
|
+
fail(e)
|
672
|
+
else
|
673
|
+
@lock.unlock
|
469
674
|
end
|
470
|
-
|
471
|
-
|
675
|
+
unless failed?
|
676
|
+
if @remaining == 0
|
677
|
+
resolve(@accumulator)
|
678
|
+
:done
|
679
|
+
else
|
680
|
+
:continue
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
# @private
|
688
|
+
class OrderedReducingFuture < ReducingFuture
|
689
|
+
def initialize(futures, initial_value, reducer)
|
690
|
+
super
|
691
|
+
if @remaining > 0
|
692
|
+
reduce_next(0)
|
693
|
+
else
|
694
|
+
resolve(@initial_value)
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
private
|
699
|
+
|
700
|
+
def reduce_next(i)
|
701
|
+
@futures[i].on_complete do |_, v, e|
|
702
|
+
unless failed?
|
703
|
+
if e
|
472
704
|
fail(e)
|
705
|
+
elsif reduce_one(v) == :continue
|
706
|
+
reduce_next(i + 1)
|
473
707
|
end
|
474
708
|
end
|
475
709
|
end
|
476
710
|
end
|
477
711
|
end
|
478
712
|
|
713
|
+
# @private
|
714
|
+
class UnorderedReducingFuture < ReducingFuture
|
715
|
+
def initialize(futures, initial_value, reducer)
|
716
|
+
super
|
717
|
+
if @remaining > 0
|
718
|
+
futures.each do |f|
|
719
|
+
f.on_complete do |_, v, e|
|
720
|
+
unless failed?
|
721
|
+
if e
|
722
|
+
fail(e)
|
723
|
+
else
|
724
|
+
reduce_one(v)
|
725
|
+
end
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
else
|
730
|
+
resolve(@initial_value)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
479
735
|
# @private
|
480
736
|
class FirstFuture < CompletableFuture
|
481
737
|
def initialize(futures)
|
482
738
|
super()
|
483
739
|
futures.each do |f|
|
484
|
-
f.
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
740
|
+
f.on_complete do |_, v, e|
|
741
|
+
unless completed?
|
742
|
+
if e
|
743
|
+
if futures.all?(&:failed?)
|
744
|
+
fail(e)
|
745
|
+
end
|
746
|
+
else
|
747
|
+
resolve(v)
|
748
|
+
end
|
749
|
+
end
|
489
750
|
end
|
490
751
|
end
|
491
752
|
end
|
@@ -516,7 +777,7 @@ module Ione
|
|
516
777
|
end
|
517
778
|
|
518
779
|
def on_complete(&listener)
|
519
|
-
listener.call(self) rescue nil
|
780
|
+
listener.call(self, @value, nil) rescue nil
|
520
781
|
end
|
521
782
|
|
522
783
|
def on_value(&listener)
|
@@ -554,7 +815,7 @@ module Ione
|
|
554
815
|
end
|
555
816
|
|
556
817
|
def on_complete(&listener)
|
557
|
-
listener.call(self) rescue nil
|
818
|
+
listener.call(self, nil, @error) rescue nil
|
558
819
|
end
|
559
820
|
|
560
821
|
def on_value
|
@@ -9,7 +9,6 @@ module Ione
|
|
9
9
|
@host = host
|
10
10
|
@port = port
|
11
11
|
@state = :connecting
|
12
|
-
@lock = Mutex.new
|
13
12
|
@closed_promise = Promise.new
|
14
13
|
end
|
15
14
|
|
@@ -17,10 +16,7 @@ module Ione
|
|
17
16
|
#
|
18
17
|
# @return [true, false] returns false if the connection was already closed
|
19
18
|
def close(cause=nil)
|
20
|
-
@
|
21
|
-
return false if @state == :closed
|
22
|
-
@state = :closed
|
23
|
-
end
|
19
|
+
return false if @state == :closed
|
24
20
|
if @io
|
25
21
|
begin
|
26
22
|
@io.close
|
@@ -29,6 +25,7 @@ module Ione
|
|
29
25
|
# nothing to do, the socket was most likely already closed
|
30
26
|
end
|
31
27
|
end
|
28
|
+
@state = :closed
|
32
29
|
if cause
|
33
30
|
@closed_promise.fail(cause)
|
34
31
|
else
|
data/lib/ione/io/connection.rb
CHANGED
data/lib/ione/io/io_reactor.rb
CHANGED
@@ -293,21 +293,13 @@ module Ione
|
|
293
293
|
end
|
294
294
|
|
295
295
|
def cancel_timer(timer_future)
|
296
|
-
timer = nil
|
297
296
|
@lock.lock
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
timer = timer_pair[1]
|
302
|
-
timer_pair[1] = nil
|
303
|
-
end
|
304
|
-
ensure
|
305
|
-
@lock.unlock
|
306
|
-
end
|
307
|
-
if timer
|
308
|
-
timer.fail(CancelledError.new)
|
309
|
-
end
|
297
|
+
timer_pair = @timers.find { |pair| (p = pair[1]) && p.future == timer_future }
|
298
|
+
@timers = @timers.reject { |pair| pair[1].nil? || pair == timer_pair }
|
299
|
+
timer_pair && timer_pair[1].fail(CancelledError.new)
|
310
300
|
nil
|
301
|
+
ensure
|
302
|
+
@lock.unlock
|
311
303
|
end
|
312
304
|
|
313
305
|
def close_sockets
|
@@ -349,25 +341,21 @@ module Ione
|
|
349
341
|
writables << s if s.connecting? || s.writable?
|
350
342
|
connecting << s if s.connecting?
|
351
343
|
end
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
344
|
+
begin
|
345
|
+
r, w, _ = @selector.select(readables, writables, nil, timeout)
|
346
|
+
connecting.each(&:connect)
|
347
|
+
r && r.each(&:read)
|
348
|
+
w && w.each(&:flush)
|
349
|
+
rescue IOError, Errno::EBADF
|
350
|
+
end
|
356
351
|
end
|
357
352
|
|
358
353
|
def check_timers!
|
359
|
-
|
360
|
-
|
361
|
-
if pair[1] && pair[0] <= now
|
362
|
-
|
363
|
-
|
364
|
-
begin
|
365
|
-
timer = pair[1]
|
366
|
-
pair[1] = nil
|
367
|
-
ensure
|
368
|
-
@lock.unlock
|
369
|
-
end
|
370
|
-
timer.fulfill
|
354
|
+
timers = @timers
|
355
|
+
timers.each do |pair|
|
356
|
+
if pair[1] && pair[0] <= @clock.now
|
357
|
+
pair[1].fulfill
|
358
|
+
pair[1] = nil
|
371
359
|
end
|
372
360
|
end
|
373
361
|
end
|
data/lib/ione/version.rb
CHANGED
data/spec/ione/future_spec.rb
CHANGED
@@ -194,21 +194,48 @@ module Ione
|
|
194
194
|
describe '#on_complete' do
|
195
195
|
context 'registers listeners and' do
|
196
196
|
it 'notifies all listeners when the promise is fulfilled' do
|
197
|
+
c1, c2 = false, false
|
198
|
+
future.on_complete { c1 = true }
|
199
|
+
future.on_complete { c2 = true }
|
200
|
+
promise.fulfill('bar')
|
201
|
+
c1.should be_true
|
202
|
+
c2.should be_true
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'passes the future as the first parameter to the block' do
|
206
|
+
f1, f2 = nil, nil
|
207
|
+
future.on_complete { |f| f1 = f }
|
208
|
+
future.on_complete { |f| f2 = f }
|
209
|
+
promise.fulfill('bar')
|
210
|
+
f1.should equal(future)
|
211
|
+
f2.should equal(future)
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'passes the value as the second parameter to the block' do
|
197
215
|
v1, v2 = nil, nil
|
198
|
-
future.on_complete { |
|
199
|
-
future.on_complete { |
|
216
|
+
future.on_complete { |_, v| v1 = v }
|
217
|
+
future.on_complete { |_, v| v2 = v }
|
200
218
|
promise.fulfill('bar')
|
201
219
|
v1.should == 'bar'
|
202
220
|
v2.should == 'bar'
|
203
221
|
end
|
204
222
|
|
205
223
|
it 'notifies all listeners when the promise fails' do
|
224
|
+
c1, c2 = nil, nil
|
225
|
+
future.on_complete { c1 = true }
|
226
|
+
future.on_complete { c2 = true }
|
227
|
+
future.fail(error)
|
228
|
+
c1.should be_true
|
229
|
+
c2.should be_true
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'passes the error as the third parameter' do
|
206
233
|
e1, e2 = nil, nil
|
207
|
-
future.on_complete { |
|
208
|
-
future.on_complete { |
|
234
|
+
future.on_complete { |_, _, e| e1 = e }
|
235
|
+
future.on_complete { |_, _, e| e2 = e }
|
209
236
|
future.fail(error)
|
210
|
-
e1.
|
211
|
-
e2.
|
237
|
+
e1.should equal(error)
|
238
|
+
e2.should equal(error)
|
212
239
|
end
|
213
240
|
|
214
241
|
it 'notifies all listeners when the promise is fulfilled, even when one raises an error' do
|
@@ -228,8 +255,21 @@ module Ione
|
|
228
255
|
end
|
229
256
|
|
230
257
|
it 'notifies listeners registered after the promise was fulfilled' do
|
258
|
+
f, v, e = nil, nil, nil
|
231
259
|
promise.fulfill('bar')
|
232
|
-
|
260
|
+
future.on_complete { |ff, vv, ee| f = ff; v = vv; e = ee }
|
261
|
+
f.should equal(future)
|
262
|
+
v.should == 'bar'
|
263
|
+
e.should be_nil
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'notifies listeners registered after the promise failed' do
|
267
|
+
f, v, e = nil, nil, nil
|
268
|
+
promise.fail(StandardError.new('bork'))
|
269
|
+
future.on_complete { |ff, vv, ee| f = ff; v = vv; e = ee }
|
270
|
+
f.should equal(future)
|
271
|
+
v.should be_nil
|
272
|
+
e.message.should == 'bork'
|
233
273
|
end
|
234
274
|
|
235
275
|
it 'notifies listeners registered after the promise failed' do
|
@@ -366,7 +406,7 @@ module Ione
|
|
366
406
|
future.value.should == 'bar'
|
367
407
|
end
|
368
408
|
|
369
|
-
it 'blocks on #value until
|
409
|
+
it 'blocks on #value until completed, when value is nil' do
|
370
410
|
d = delayed(promise) do |p|
|
371
411
|
p.fulfill
|
372
412
|
end
|
@@ -382,7 +422,7 @@ module Ione
|
|
382
422
|
expect { future.value }.to raise_error('bork')
|
383
423
|
end
|
384
424
|
|
385
|
-
it 'allows multiple threads to block on #value until
|
425
|
+
it 'allows multiple threads to block on #value until completed' do
|
386
426
|
listeners = Array.new(10) do
|
387
427
|
async(future) do |f|
|
388
428
|
f.value
|
@@ -396,7 +436,7 @@ module Ione
|
|
396
436
|
|
397
437
|
describe '#map' do
|
398
438
|
context 'returns a new future that' do
|
399
|
-
it 'will be
|
439
|
+
it 'will be resolved with the result of the given block' do
|
400
440
|
mapped_value = nil
|
401
441
|
p = Promise.new
|
402
442
|
f = p.future.map { |v| v * 2 }
|
@@ -405,7 +445,7 @@ module Ione
|
|
405
445
|
mapped_value.should == 3 * 2
|
406
446
|
end
|
407
447
|
|
408
|
-
it 'will be
|
448
|
+
it 'will be resolved with the specified value' do
|
409
449
|
mapped_value = nil
|
410
450
|
p = Promise.new
|
411
451
|
f = p.future.map(7)
|
@@ -414,7 +454,7 @@ module Ione
|
|
414
454
|
mapped_value.should == 7
|
415
455
|
end
|
416
456
|
|
417
|
-
it 'will be
|
457
|
+
it 'will be resolved with the result of the given block, even if a value is specified' do
|
418
458
|
mapped_value = nil
|
419
459
|
p = Promise.new
|
420
460
|
f = p.future.map(7) { |v| v * 2 }
|
@@ -423,7 +463,7 @@ module Ione
|
|
423
463
|
mapped_value.should == 3 * 2
|
424
464
|
end
|
425
465
|
|
426
|
-
it 'will be
|
466
|
+
it 'will be resolved with nil when neither value nor block is specified' do
|
427
467
|
mapped_value = 3
|
428
468
|
p = Promise.new
|
429
469
|
f = p.future.map
|
@@ -454,45 +494,97 @@ module Ione
|
|
454
494
|
end
|
455
495
|
|
456
496
|
describe '#flat_map' do
|
457
|
-
|
497
|
+
context 'returns a future that' do
|
498
|
+
it 'passes the value of the source future to the block, and resolves to the value of the future returned by the block' do
|
499
|
+
p = Promise.new
|
500
|
+
f = p.future.flat_map { |v| Future.resolved(v * 2) }
|
501
|
+
p.fulfill(3)
|
502
|
+
f.value.should == 3 * 2
|
503
|
+
end
|
504
|
+
|
505
|
+
it 'fails when the block raises an error' do
|
506
|
+
p = Promise.new
|
507
|
+
f = p.future.flat_map { |v| raise 'Hurgh' }
|
508
|
+
p.fulfill(3)
|
509
|
+
expect { f.value }.to raise_error('Hurgh')
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
it 'accepts anything that implements #on_complete as a chained future' do
|
514
|
+
fake_future = double(:fake_future)
|
515
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(nil, :foobar) }
|
458
516
|
p = Promise.new
|
459
|
-
f = p.future.flat_map {
|
460
|
-
p.fulfill
|
461
|
-
f.value.should ==
|
517
|
+
f = p.future.flat_map { fake_future }
|
518
|
+
p.fulfill
|
519
|
+
f.value.should == :foobar
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
describe '#then' do
|
524
|
+
context 'when the block returns a future' do
|
525
|
+
it 'works like #flat_map' do
|
526
|
+
p = Promise.new
|
527
|
+
f = p.future.then { |v| Future.resolved(v * 2) }
|
528
|
+
p.fulfill(3)
|
529
|
+
f.value.should == 3 * 2
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
context 'when the block returns something that quacks like a future' do
|
534
|
+
it 'works like #flat_map' do
|
535
|
+
fake_future = double(:fake_future)
|
536
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(nil, :foobar) }
|
537
|
+
p = Promise.new
|
538
|
+
f = p.future.then { |v| fake_future }
|
539
|
+
p.fulfill
|
540
|
+
f.value.should == :foobar
|
541
|
+
end
|
462
542
|
end
|
463
543
|
|
464
|
-
|
544
|
+
context 'when the block returns something that does not quack like a future' do
|
545
|
+
it 'works like #map' do
|
546
|
+
p = Promise.new
|
547
|
+
f = p.future.then { |v| v * 2 }
|
548
|
+
p.fulfill(3)
|
549
|
+
f.value.should == 3 * 2
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
it 'returns a failed future when the block raises an error' do
|
465
554
|
p = Promise.new
|
466
|
-
f = p.future.
|
467
|
-
|
468
|
-
|
555
|
+
f = p.future.then { |v| raise 'blurgh' }
|
556
|
+
d = delayed do
|
557
|
+
p.fulfill
|
558
|
+
end
|
559
|
+
d.value
|
560
|
+
expect { f.value }.to raise_error('blurgh')
|
469
561
|
end
|
470
562
|
end
|
471
563
|
|
472
564
|
describe '#recover' do
|
473
565
|
context 'returns a new future that' do
|
474
|
-
it '
|
566
|
+
it 'resolves to a value created by the block when the source future fails' do
|
475
567
|
p = Promise.new
|
476
568
|
f = p.future.recover { 'foo' }
|
477
569
|
p.fail(error)
|
478
570
|
f.value.should == 'foo'
|
479
571
|
end
|
480
572
|
|
481
|
-
it '
|
573
|
+
it 'resolves to a specfied value when the source future fails' do
|
482
574
|
p = Promise.new
|
483
575
|
f = p.future.recover('bar')
|
484
576
|
p.fail(error)
|
485
577
|
f.value.should == 'bar'
|
486
578
|
end
|
487
579
|
|
488
|
-
it '
|
580
|
+
it 'resovles to a value created by the block even when a value is specified when the source future fails' do
|
489
581
|
p = Promise.new
|
490
582
|
f = p.future.recover('bar') { 'foo' }
|
491
583
|
p.fail(error)
|
492
584
|
f.value.should == 'foo'
|
493
585
|
end
|
494
586
|
|
495
|
-
it '
|
587
|
+
it 'resolves to nil value when no value nor block is specified and the source future fails' do
|
496
588
|
p = Promise.new
|
497
589
|
f = p.future.recover
|
498
590
|
p.fail(error)
|
@@ -506,7 +598,7 @@ module Ione
|
|
506
598
|
f.value.should == error.message
|
507
599
|
end
|
508
600
|
|
509
|
-
it '
|
601
|
+
it 'resolves to the value of the source future when the source future is resolved' do
|
510
602
|
p = Promise.new
|
511
603
|
f = p.future.recover { 'foo' }
|
512
604
|
p.fulfill('bar')
|
@@ -567,6 +659,187 @@ module Ione
|
|
567
659
|
p1.fail(StandardError.new('fnork'))
|
568
660
|
expect { f.value }.to raise_error('bork')
|
569
661
|
end
|
662
|
+
|
663
|
+
it 'accepts anything that implements #on_complete as a fallback future' do
|
664
|
+
fake_future = double(:fake_future)
|
665
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(nil, 'foo') }
|
666
|
+
p = Promise.new
|
667
|
+
f = p.future.fallback { fake_future }
|
668
|
+
p.fail(error)
|
669
|
+
f.value.should == 'foo'
|
670
|
+
end
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
describe '.traverse' do
|
675
|
+
it 'combines Array#map and Future.all' do
|
676
|
+
future = Future.traverse([1, 2, 3]) do |element|
|
677
|
+
Future.resolved(element * 2)
|
678
|
+
end
|
679
|
+
future.value.should == [2, 4, 6]
|
680
|
+
end
|
681
|
+
|
682
|
+
it 'fails if any of the source futures fail' do
|
683
|
+
future = Future.traverse([1, 2, 3]) do |element|
|
684
|
+
if element == 2
|
685
|
+
Future.failed(StandardError.new('BORK'))
|
686
|
+
else
|
687
|
+
Future.resolved(element * 2)
|
688
|
+
end
|
689
|
+
end
|
690
|
+
future.should be_failed
|
691
|
+
end
|
692
|
+
|
693
|
+
it 'fails if any of the block invocations fail' do
|
694
|
+
future = Future.traverse([1, 2, 3]) do |element|
|
695
|
+
if element == 2
|
696
|
+
raise 'BORK'
|
697
|
+
else
|
698
|
+
Future.resolved(element * 2)
|
699
|
+
end
|
700
|
+
end
|
701
|
+
future.should be_failed
|
702
|
+
end
|
703
|
+
|
704
|
+
it 'accepts anything that implements #on_complete as futures' do
|
705
|
+
fake_future = double(:fake_future)
|
706
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(nil, :foobar) }
|
707
|
+
future = Future.traverse([1, 2, 3]) { fake_future }
|
708
|
+
future.value.should == [:foobar, :foobar, :foobar]
|
709
|
+
end
|
710
|
+
|
711
|
+
it 'accepts an enumerable of values' do
|
712
|
+
future = Future.traverse([1, 2, 3].to_enum) { |v| Future.resolved(v * 2) }
|
713
|
+
future.value.should == [2, 4, 6]
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
describe '.reduce' do
|
718
|
+
it 'returns a future which represents the value of reducing the values of the inputs' do
|
719
|
+
futures = [
|
720
|
+
Future.resolved({'foo' => 'bar'}),
|
721
|
+
Future.resolved({'qux' => 'baz'}),
|
722
|
+
Future.resolved({'hello' => 'world'})
|
723
|
+
]
|
724
|
+
future = Future.reduce(futures, {}) do |accumulator, value|
|
725
|
+
accumulator.merge(value)
|
726
|
+
end
|
727
|
+
future.value.should == {'foo' => 'bar', 'qux' => 'baz', 'hello' => 'world'}
|
728
|
+
end
|
729
|
+
|
730
|
+
it 'calls the block with the values in the order of the source futures' do
|
731
|
+
promises = [Promise.new, Promise.new, Promise.new, Promise.new, Promise.new]
|
732
|
+
futures = promises.map(&:future)
|
733
|
+
future = Future.reduce(futures, []) do |accumulator, value|
|
734
|
+
accumulator.push(value)
|
735
|
+
end
|
736
|
+
promises[1].fulfill(1)
|
737
|
+
promises[0].fulfill(0)
|
738
|
+
promises[2].fulfill(2)
|
739
|
+
promises[4].fulfill(4)
|
740
|
+
promises[3].fulfill(3)
|
741
|
+
future.value.should == [0, 1, 2, 3, 4]
|
742
|
+
end
|
743
|
+
|
744
|
+
it 'uses the first value as initial value when no intial value is given' do
|
745
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
746
|
+
futures = promises.map(&:future)
|
747
|
+
future = Future.reduce(futures) do |sum, n|
|
748
|
+
sum + n
|
749
|
+
end
|
750
|
+
promises[1].fulfill(2)
|
751
|
+
promises[0].fulfill(1)
|
752
|
+
promises[2].fulfill(3)
|
753
|
+
future.value.should == 6
|
754
|
+
end
|
755
|
+
|
756
|
+
it 'fails if any of the source futures fail' do
|
757
|
+
futures = [Future.resolved(0), Future.failed(StandardError.new('BORK')), Future.resolved(2)]
|
758
|
+
future = Future.reduce(futures, []) do |accumulator, value|
|
759
|
+
accumulator.push(value)
|
760
|
+
end
|
761
|
+
future.should be_failed
|
762
|
+
end
|
763
|
+
|
764
|
+
it 'fails if any of the block invocations fail' do
|
765
|
+
futures = [Future.resolved(0), Future.resolved(1), Future.resolved(2)]
|
766
|
+
future = Future.reduce(futures, []) do |accumulator, value|
|
767
|
+
if value == 2
|
768
|
+
raise 'BORK'
|
769
|
+
else
|
770
|
+
accumulator.push(value)
|
771
|
+
end
|
772
|
+
end
|
773
|
+
future.should be_failed
|
774
|
+
end
|
775
|
+
|
776
|
+
context 'when the list of futures is empty' do
|
777
|
+
it 'returns a future that resolves to the initial value' do
|
778
|
+
Future.reduce([], :foo).value.should == :foo
|
779
|
+
end
|
780
|
+
|
781
|
+
it 'returns a future that resolves to nil there is also no initial value' do
|
782
|
+
Future.reduce([]).value.should be_nil
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
it 'accepts anything that implements #on_complete as futures' do
|
787
|
+
ff1, ff2, ff3 = double, double, double
|
788
|
+
ff1.stub(:on_complete) { |&listener| listener.call(nil, 1) }
|
789
|
+
ff2.stub(:on_complete) { |&listener| listener.call(nil, 2) }
|
790
|
+
ff3.stub(:on_complete) { |&listener| listener.call(nil, 3) }
|
791
|
+
future = Future.reduce([ff1, ff2, ff3], 0) { |sum, n| sum + n }
|
792
|
+
future.value.should == 6
|
793
|
+
end
|
794
|
+
|
795
|
+
it 'accepts an enumerable of futures' do
|
796
|
+
futures = [Future.resolved(1), Future.resolved(2), Future.resolved(3)].to_enum
|
797
|
+
future = Future.reduce(futures, 0) { |sum, n| sum + n }
|
798
|
+
future.value.should == 6
|
799
|
+
end
|
800
|
+
|
801
|
+
context 'when the :ordered option is false' do
|
802
|
+
it 'calls the block with the values in the order of completion, when the :ordered option is false' do
|
803
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
804
|
+
futures = promises.map(&:future)
|
805
|
+
future = Future.reduce(futures, [], ordered: false) do |accumulator, value|
|
806
|
+
accumulator.push(value)
|
807
|
+
end
|
808
|
+
promises[1].fulfill(1)
|
809
|
+
promises[0].fulfill(0)
|
810
|
+
promises[2].fulfill(2)
|
811
|
+
future.value.should == [1, 0, 2]
|
812
|
+
end
|
813
|
+
|
814
|
+
it 'fails if any of the source futures fail' do
|
815
|
+
futures = [Future.resolved(0), Future.failed(StandardError.new('BORK')), Future.resolved(2)]
|
816
|
+
future = Future.reduce(futures, [], ordered: false) do |accumulator, value|
|
817
|
+
accumulator.push(value)
|
818
|
+
end
|
819
|
+
future.should be_failed
|
820
|
+
end
|
821
|
+
|
822
|
+
it 'fails if any of the block invocations fail' do
|
823
|
+
futures = [Future.resolved(0), Future.resolved(1), Future.resolved(2)]
|
824
|
+
future = Future.reduce(futures, [], ordered: false) do |accumulator, value|
|
825
|
+
if value == 1
|
826
|
+
raise 'BORK'
|
827
|
+
else
|
828
|
+
accumulator.push(value)
|
829
|
+
end
|
830
|
+
end
|
831
|
+
future.should be_failed
|
832
|
+
end
|
833
|
+
|
834
|
+
context 'when the list of futures is empty' do
|
835
|
+
it 'returns a future that resolves to the initial value' do
|
836
|
+
Future.reduce([], :foo, ordered: false).value.should == :foo
|
837
|
+
end
|
838
|
+
|
839
|
+
it 'returns a future that resolves to nil there is also no initial value' do
|
840
|
+
Future.reduce([], nil, ordered: false).value.should be_nil
|
841
|
+
end
|
842
|
+
end
|
570
843
|
end
|
571
844
|
end
|
572
845
|
|
@@ -582,22 +855,7 @@ module Ione
|
|
582
855
|
f.should be_resolved
|
583
856
|
end
|
584
857
|
|
585
|
-
it '
|
586
|
-
sequence = []
|
587
|
-
p1 = Promise.new
|
588
|
-
p2 = Promise.new
|
589
|
-
p3 = Promise.new
|
590
|
-
f = Future.all(p1.future, p2.future, p3.future)
|
591
|
-
p1.future.on_value { sequence << 1 }
|
592
|
-
p2.future.on_value { sequence << 2 }
|
593
|
-
p3.future.on_value { sequence << 3 }
|
594
|
-
p2.fulfill
|
595
|
-
p1.fulfill
|
596
|
-
p3.fulfill
|
597
|
-
sequence.should == [2, 1, 3]
|
598
|
-
end
|
599
|
-
|
600
|
-
it 'returns an array of the values of the source futures, in order ' do
|
858
|
+
it 'returns an array of the values of the source futures, in order' do
|
601
859
|
p1 = Promise.new
|
602
860
|
p2 = Promise.new
|
603
861
|
p3 = Promise.new
|
@@ -630,6 +888,31 @@ module Ione
|
|
630
888
|
f = Future.resolved(1)
|
631
889
|
Future.all(f).value.should == [1]
|
632
890
|
end
|
891
|
+
|
892
|
+
it 'accepts a list of futures' do
|
893
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
894
|
+
futures = promises.map(&:future)
|
895
|
+
f = Future.all(futures)
|
896
|
+
promises.each(&:fulfill)
|
897
|
+
f.value.should have(3).items
|
898
|
+
end
|
899
|
+
|
900
|
+
it 'accepts an enumerable of futures' do
|
901
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
902
|
+
futures = promises.map(&:future).to_enum
|
903
|
+
f = Future.all(futures)
|
904
|
+
promises.each(&:fulfill)
|
905
|
+
f.value.should have(3).items
|
906
|
+
end
|
907
|
+
|
908
|
+
it 'accepts anything that implements #on_complete as futures' do
|
909
|
+
ff1, ff2, ff3 = double, double, double
|
910
|
+
ff1.stub(:on_complete) { |&listener| listener.call(nil, 1) }
|
911
|
+
ff2.stub(:on_complete) { |&listener| listener.call(nil, 2) }
|
912
|
+
ff3.stub(:on_complete) { |&listener| listener.call(nil, 3) }
|
913
|
+
future = Future.all(ff1, ff2, ff3)
|
914
|
+
future.value.should == [1, 2, 3]
|
915
|
+
end
|
633
916
|
end
|
634
917
|
end
|
635
918
|
|
@@ -704,6 +987,30 @@ module Ione
|
|
704
987
|
it 'completes with the value of the given future, when only one is given' do
|
705
988
|
Future.first(Future.resolved('foo')).value.should == 'foo'
|
706
989
|
end
|
990
|
+
|
991
|
+
it 'accepts a list of futures' do
|
992
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
993
|
+
futures = promises.map(&:future)
|
994
|
+
f = Future.first(futures)
|
995
|
+
promises.each(&:fulfill)
|
996
|
+
f.should be_resolved
|
997
|
+
end
|
998
|
+
|
999
|
+
it 'accepts an enumerable of futures' do
|
1000
|
+
promises = [Promise.new, Promise.new, Promise.new]
|
1001
|
+
futures = promises.map(&:future).to_enum
|
1002
|
+
f = Future.first(futures)
|
1003
|
+
promises.each(&:fulfill)
|
1004
|
+
f.should be_resolved
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
it 'accepts anything that implements #on_complete as futures' do
|
1008
|
+
ff1, ff2 = double, double
|
1009
|
+
ff1.stub(:on_complete) { |&listener| listener.call(nil, 1) }
|
1010
|
+
ff2.stub(:on_complete) { |&listener| listener.call(nil, 2) }
|
1011
|
+
future = Future.first(ff1, ff2)
|
1012
|
+
future.value.should == 1
|
1013
|
+
end
|
707
1014
|
end
|
708
1015
|
end
|
709
1016
|
|
@@ -732,9 +1039,10 @@ module Ione
|
|
732
1039
|
end
|
733
1040
|
|
734
1041
|
it 'calls its complete callbacks immediately' do
|
735
|
-
f = nil
|
736
|
-
future.on_complete { |ff| f = ff }
|
737
|
-
f.
|
1042
|
+
f, v = nil, nil
|
1043
|
+
future.on_complete { |ff, vv| f = ff; v = vv }
|
1044
|
+
f.should equal(future)
|
1045
|
+
v.should == 'hello world'
|
738
1046
|
end
|
739
1047
|
|
740
1048
|
it 'does not block on #value' do
|
@@ -772,9 +1080,10 @@ module Ione
|
|
772
1080
|
end
|
773
1081
|
|
774
1082
|
it 'calls its complete callbacks immediately' do
|
775
|
-
f = nil
|
776
|
-
future.on_complete { |ff| f = ff }
|
777
|
-
f.
|
1083
|
+
f, e = nil, nil
|
1084
|
+
future.on_complete { |ff, _, ee| f = ff; e = ee }
|
1085
|
+
f.should equal(future)
|
1086
|
+
e.message.should == 'bork'
|
778
1087
|
end
|
779
1088
|
|
780
1089
|
it 'does not block on #value' do
|
@@ -340,6 +340,20 @@ module Ione
|
|
340
340
|
loop_body.tick
|
341
341
|
end
|
342
342
|
|
343
|
+
it 'does nothing when IO.select raises Errno::EBADF' do
|
344
|
+
selector.should_receive(:select) do
|
345
|
+
raise Errno::EBADF
|
346
|
+
end
|
347
|
+
loop_body.tick
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'does nothing when IO.select raises IOError' do
|
351
|
+
selector.should_receive(:select) do
|
352
|
+
raise IOError
|
353
|
+
end
|
354
|
+
loop_body.tick
|
355
|
+
end
|
356
|
+
|
343
357
|
it 'calls #read on all readable sockets returned by the selector' do
|
344
358
|
socket.stub(:connected?).and_return(true)
|
345
359
|
socket.should_receive(:read)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ione
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0.pre0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Theo Hultberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Reactive programming framework for Ruby, painless evented IO, futures
|
14
14
|
and an efficient byte buffer
|
@@ -56,9 +56,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
56
|
version: 1.9.3
|
57
57
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 1.3.1
|
62
62
|
requirements: []
|
63
63
|
rubyforge_project:
|
64
64
|
rubygems_version: 2.2.2
|