ione 1.1.5 → 1.2.0.pre0
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/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
|