ione 1.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d01e0e0185b664af4fe4770c988a2560388ab05b
4
+ data.tar.gz: ce1e35186cc20d8732d0cecb1698720d2f3fb73b
5
+ SHA512:
6
+ metadata.gz: 2a147567529615c86aefe2e2e6cfb5e0e0c57d99efef0bfedd02b4b91dd7acc611d98c98dd694a8ce2c6d657431134b14b7125b1a17bac97598aa560dda8026a
7
+ data.tar.gz: 92bf693d6bba06dd05e6434484f50802a780819db5b3f1a23c5ed02a30bc183362e1fbe7aeda581adc1223e32084fba11a24a9e7a2decb547e9122e441bd0e2d
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ --no-private
2
+ --markup markdown
3
+ lib/**/*.rb
4
+ -- README
data/lib/ione.rb ADDED
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ module Ione
4
+ end
5
+
6
+ require 'ione/future'
7
+ require 'ione/byte_buffer'
8
+ require 'ione/io'
@@ -0,0 +1,279 @@
1
+ # encoding: utf-8
2
+
3
+ module Ione
4
+ # A byte buffer is a more efficient way of working with bytes than using
5
+ # a regular Ruby string. It also has convenient methods for reading integers
6
+ # shorts and single bytes that are faster than `String#unpack`.
7
+ #
8
+ # When you use a string as a buffer, by adding to the end and taking away
9
+ # from the beginning, Ruby will continue to grow the backing array of
10
+ # characters. This means that the longer you use the string the worse the
11
+ # performance will get and the more memory you waste.
12
+ #
13
+ # {ByteBuffer} solves the problem by using two strings: one is the read
14
+ # buffer and one is the write buffer. Writes go to the write buffer only,
15
+ # and reads read from the read buffer until it is empty, then a new write
16
+ # buffer is created and the old write buffer becomes the new read buffer.
17
+ class ByteBuffer
18
+ def initialize(initial_bytes='')
19
+ @read_buffer = ''
20
+ @write_buffer = ''
21
+ @offset = 0
22
+ @length = 0
23
+ append(initial_bytes) unless initial_bytes.empty?
24
+ end
25
+
26
+ # Returns the number of bytes in the buffer.
27
+ #
28
+ # The value is cached so this is a cheap operation.
29
+ attr_reader :length
30
+ alias_method :size, :length
31
+ alias_method :bytesize, :length
32
+
33
+ # Returns true when the number of bytes in the buffer is zero.
34
+ #
35
+ # The length is cached so this is a cheap operation.
36
+ def empty?
37
+ length == 0
38
+ end
39
+
40
+ # Append the bytes from a string or another byte buffer to this buffer.
41
+ #
42
+ # @note
43
+ # When the bytes are not in an ASCII compatible encoding they are copied
44
+ # and retagged as `Encoding::BINARY` before they are appended to the
45
+ # buffer – this is required to avoid Ruby retagging the whole buffer with
46
+ # the encoding of the new bytes. If you can, make sure that the data you
47
+ # append is ASCII compatible (i.e. responds true to `#ascii_only?`),
48
+ # otherwise you will pay a small penalty for each append due to the extra
49
+ # copy that has to be made.
50
+ #
51
+ # @param [String, Ione::ByteBuffer] bytes the bytes to append
52
+ # @return [Ione::ByteBuffer] itself
53
+ def append(bytes)
54
+ if bytes.is_a?(self.class)
55
+ bytes.append_to(self)
56
+ else
57
+ bytes = bytes.to_s
58
+ unless bytes.ascii_only?
59
+ bytes = bytes.dup.force_encoding(::Encoding::BINARY)
60
+ end
61
+ retag = @write_buffer.empty?
62
+ @write_buffer << bytes
63
+ @write_buffer.force_encoding(::Encoding::BINARY) if retag
64
+ @length += bytes.bytesize
65
+ end
66
+ self
67
+ end
68
+ alias_method :<<, :append
69
+
70
+ # Remove the first N bytes from the buffer.
71
+ #
72
+ # @param [Integer] n the number of bytes to remove from the buffer
73
+ # @return [Ione::ByteBuffer] itself
74
+ # @raise RangeError when there are not enough bytes in the buffer
75
+ def discard(n)
76
+ raise RangeError, "#{n} bytes to discard but only #{@length} available" if @length < n
77
+ @offset += n
78
+ @length -= n
79
+ self
80
+ end
81
+
82
+ # Remove and return the first N bytes from the buffer.
83
+ #
84
+ # @param [Integer] n the number of bytes to remove and return from the buffer
85
+ # @return [String] a string with the bytes, the string will be tagged
86
+ # with `Encoding::BINARY`.
87
+ # @raise RangeError when there are not enough bytes in the buffer
88
+ def read(n)
89
+ raise RangeError, "#{n} bytes required but only #{@length} available" if @length < n
90
+ if @offset >= @read_buffer.bytesize
91
+ swap_buffers
92
+ end
93
+ if @offset + n > @read_buffer.bytesize
94
+ s = read(@read_buffer.bytesize - @offset)
95
+ s << read(n - s.bytesize)
96
+ s
97
+ else
98
+ s = @read_buffer[@offset, n]
99
+ @offset += n
100
+ @length -= n
101
+ s
102
+ end
103
+ end
104
+
105
+ # Remove and return the first four bytes from the buffer and decode them as an unsigned integer.
106
+ #
107
+ # @return [Integer] the big-endian integer interpretation of the four bytes
108
+ # @raise RangeError when there are not enough bytes in the buffer
109
+ def read_int
110
+ raise RangeError, "4 bytes required to read an int, but only #{@length} available" if @length < 4
111
+ if @offset >= @read_buffer.bytesize
112
+ swap_buffers
113
+ end
114
+ if @read_buffer.bytesize >= @offset + 4
115
+ i0 = @read_buffer.getbyte(@offset + 0)
116
+ i1 = @read_buffer.getbyte(@offset + 1)
117
+ i2 = @read_buffer.getbyte(@offset + 2)
118
+ i3 = @read_buffer.getbyte(@offset + 3)
119
+ @offset += 4
120
+ @length -= 4
121
+ else
122
+ i0 = read_byte
123
+ i1 = read_byte
124
+ i2 = read_byte
125
+ i3 = read_byte
126
+ end
127
+ (i0 << 24) | (i1 << 16) | (i2 << 8) | i3
128
+ end
129
+
130
+ # Remove and return the first two bytes from the buffer and decode them as an unsigned integer.
131
+ #
132
+ # @return [Integer] the big-endian integer interpretation of the two bytes
133
+ # @raise RangeError when there are not enough bytes in the buffer
134
+ def read_short
135
+ raise RangeError, "2 bytes required to read a short, but only #{@length} available" if @length < 2
136
+ if @offset >= @read_buffer.bytesize
137
+ swap_buffers
138
+ end
139
+ if @read_buffer.bytesize >= @offset + 2
140
+ i0 = @read_buffer.getbyte(@offset + 0)
141
+ i1 = @read_buffer.getbyte(@offset + 1)
142
+ @offset += 2
143
+ @length -= 2
144
+ else
145
+ i0 = read_byte
146
+ i1 = read_byte
147
+ end
148
+ (i0 << 8) | i1
149
+ end
150
+
151
+ # Remove and return the first byte from the buffer and decode it as a signed or unsigned integer.
152
+ #
153
+ # @param [Boolean] signed whether or not to interpret the byte as a signed number of not
154
+ # @return [Integer] the integer interpretation of the byte
155
+ # @raise RangeError when the buffer is empty
156
+ def read_byte(signed=false)
157
+ raise RangeError, "No bytes available to read byte" if empty?
158
+ if @offset >= @read_buffer.bytesize
159
+ swap_buffers
160
+ end
161
+ b = @read_buffer.getbyte(@offset)
162
+ b = (b & 0x7f) - (b & 0x80) if signed
163
+ @offset += 1
164
+ @length -= 1
165
+ b
166
+ end
167
+
168
+ # Overwrite a portion of the buffer with new bytes.
169
+ #
170
+ # The number of bytes that will be replaced depend on the size of the
171
+ # replacement string. If you pass a five byte string the five bytes
172
+ # starting at the location will be replaced.
173
+ #
174
+ # When you pass more bytes than the size of the buffer after the location
175
+ # only as many as needed to replace the remaining bytes of the buffer will
176
+ # actually be used.
177
+ #
178
+ # Make sure that you get your location right, if you have discarded bytes
179
+ # from the buffer all of the offsets will have changed.
180
+ #
181
+ # @example replacing bytes in the middle of a buffer
182
+ # buffer = ByteBuffer.new("hello world!")
183
+ # bufferupdate(6, "fnord")
184
+ # buffer # => "hello fnord!"
185
+ #
186
+ # @example replacing bytes at the end of the buffer
187
+ # buffer = ByteBuffer.new("my name is Jim")
188
+ # buffer.update(11, "Sammy")
189
+ # buffer # => "my name is Sam"
190
+ #
191
+ # @param [Integer] location the starting location where the new bytes
192
+ # should be inserted
193
+ # @param [String] bytes the replacement bytes
194
+ # @return [Ione::ByteBuffer] itself
195
+ def update(location, bytes)
196
+ absolute_offset = @offset + location
197
+ bytes_length = bytes.bytesize
198
+ if absolute_offset >= @read_buffer.bytesize
199
+ @write_buffer[absolute_offset - @read_buffer.bytesize, bytes_length] = bytes
200
+ else
201
+ overflow = absolute_offset + bytes_length - @read_buffer.bytesize
202
+ read_buffer_portion = bytes_length - overflow
203
+ @read_buffer[absolute_offset, read_buffer_portion] = bytes[0, read_buffer_portion]
204
+ if overflow > 0
205
+ @write_buffer[0, overflow] = bytes[read_buffer_portion, bytes_length - 1]
206
+ end
207
+ end
208
+ self
209
+ end
210
+
211
+ # Return as much of the buffer as possible without having to concatenate
212
+ # or allocate any unnecessary strings.
213
+ #
214
+ # If the buffer is not empty this method will return something, but there
215
+ # are no guarantees as to how much it will return. It's primarily useful
216
+ # in situations where a loop wants to offer some bytes but can't be sure
217
+ # how many will be accepted — for example when writing to a socket.
218
+ #
219
+ # @example feeding bytes to a socket
220
+ # while true
221
+ # _, writables, _ = IO.select(nil, sockets)
222
+ # if writables
223
+ # writables.each do |io|
224
+ # n = io.write_nonblock(buffer.cheap_peek)
225
+ # buffer.discard(n)
226
+ # end
227
+ # end
228
+ #
229
+ # @return [String] some bytes from the start of the buffer
230
+ def cheap_peek
231
+ if @offset >= @read_buffer.bytesize
232
+ swap_buffers
233
+ end
234
+ @read_buffer[@offset, @read_buffer.bytesize - @offset]
235
+ end
236
+
237
+ def eql?(other)
238
+ self.to_str.eql?(other.to_str)
239
+ end
240
+ alias_method :==, :eql?
241
+
242
+ def hash
243
+ to_str.hash
244
+ end
245
+
246
+ def dup
247
+ self.class.new(to_str)
248
+ end
249
+
250
+ def to_str
251
+ (@read_buffer + @write_buffer)[@offset, @length]
252
+ end
253
+ alias_method :to_s, :to_str
254
+
255
+ def inspect
256
+ %(#<#{self.class.name}: #{to_str.inspect}>)
257
+ end
258
+
259
+ protected
260
+
261
+ def append_to(other)
262
+ other.raw_append(cheap_peek)
263
+ other.raw_append(@write_buffer) unless @write_buffer.empty?
264
+ end
265
+
266
+ def raw_append(bytes)
267
+ @write_buffer << bytes
268
+ @length += bytes.bytesize
269
+ end
270
+
271
+ private
272
+
273
+ def swap_buffers
274
+ @offset -= @read_buffer.bytesize
275
+ @read_buffer = @write_buffer
276
+ @write_buffer = ''
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,509 @@
1
+ # encoding: utf-8
2
+
3
+ require 'thread'
4
+
5
+
6
+ module Ione
7
+ FutureError = Class.new(StandardError)
8
+
9
+ # A promise of delivering a value some time in the future.
10
+ #
11
+ # A promise is the write end of a Promise/Future pair. It can be fulfilled
12
+ # with a value or failed with an error. The value can be read through the
13
+ # future returned by {#future}.
14
+ class Promise
15
+ attr_reader :future
16
+
17
+ def initialize
18
+ @future = CompletableFuture.new
19
+ end
20
+
21
+ # Fulfills the promise.
22
+ #
23
+ # This will resolve this promise's future, and trigger all listeners of that
24
+ # future. The value of the future will be the specified value, or nil if
25
+ # no value is specified.
26
+ #
27
+ # @param [Object] value the value of the future
28
+ def fulfill(value=nil)
29
+ @future.resolve(value)
30
+ end
31
+
32
+ # Fails the promise.
33
+ #
34
+ # This will fail this promise's future, and trigger all listeners of that
35
+ # future.
36
+ #
37
+ # @param [Error] error the error which prevented the promise to be fulfilled
38
+ def fail(error)
39
+ @future.fail(error)
40
+ end
41
+
42
+ # Observe a future and fulfill the promise with the future's value when the
43
+ # future resolves, or fail with the future's error when the future fails.
44
+ #
45
+ # @param [Ione::Future] future the future to observe
46
+ def observe(future)
47
+ future.on_value { |v| fulfill(v) }
48
+ future.on_failure { |e| fail(e) }
49
+ end
50
+
51
+ # Run the given block and fulfill this promise with its result. If the block
52
+ # raises an error, fail this promise with the error.
53
+ #
54
+ # All arguments given will be passed onto the block.
55
+ #
56
+ # @example
57
+ # promise.try { 3 + 4 }
58
+ # promise.future.value # => 7
59
+ #
60
+ # @example
61
+ # promise.try do
62
+ # do_something_that_will_raise_an_error
63
+ # end
64
+ # promise.future.value # => (raises error)
65
+ #
66
+ # @example
67
+ # promise.try('foo', 'bar', &proc_taking_two_arguments)
68
+ #
69
+ # @yieldparam [Array] ctx the arguments passed to {#try}
70
+ def try(*ctx)
71
+ fulfill(yield(*ctx))
72
+ rescue => e
73
+ fail(e)
74
+ end
75
+ end
76
+
77
+ module FutureFactories
78
+ # Combines multiple futures into a new future which resolves when all
79
+ # constituent futures complete, or fails when one or more of them fails.
80
+ #
81
+ # The value of the combined future is an array of the values of the
82
+ # constituent futures.
83
+ #
84
+ # @param [Array<Ione::Future>] futures the futures to combine
85
+ # @return [Ione::Future<Array>] an array of the values of the constituent
86
+ # futures
87
+ def all(*futures)
88
+ return resolved([]) if futures.empty?
89
+ CombinedFuture.new(futures)
90
+ end
91
+
92
+ # Returns a future which will be resolved with the value of the first
93
+ # (resolved) of the specified futures. If all of the futures fail, the
94
+ # returned future will also fail (with the error of the last failed future).
95
+ #
96
+ # @param [Array<Ione::Future>] futures the futures to monitor
97
+ # @return [Ione::Future] a future which represents the first completing future
98
+ def first(*futures)
99
+ return resolved if futures.empty?
100
+ FirstFuture.new(futures)
101
+ end
102
+
103
+ # Creates a new pre-resolved future.
104
+ #
105
+ # @param [Object, nil] value the value of the created future
106
+ # @return [Ione::Future] a resolved future
107
+ def resolved(value=nil)
108
+ ResolvedFuture.new(value)
109
+ end
110
+
111
+ # Creates a new pre-failed future.
112
+ #
113
+ # @param [Error] error the error of the created future
114
+ # @return [Ione::Future] a failed future
115
+ def failed(error)
116
+ FailedFuture.new(error)
117
+ end
118
+ end
119
+
120
+ module FutureCombinators
121
+ # Returns a new future representing a transformation of this future's value.
122
+ #
123
+ # @example
124
+ # future2 = future1.map { |value| value * 2 }
125
+ #
126
+ # @param [Object] value the value of this future (when no block is given)
127
+ # @yieldparam [Object] value the value of this future
128
+ # @yieldreturn [Object] the transformed value
129
+ # @return [Ione::Future] a new future representing the transformed value
130
+ def map(value=nil, &block)
131
+ CompletableFuture.new.tap do |f|
132
+ on_failure { |e| f.fail(e) }
133
+ on_value { |v| run(f, value, block, v) }
134
+ end
135
+ end
136
+
137
+ # Returns a new future representing a transformation of this future's value,
138
+ # but where the transformation itself may be asynchronous.
139
+ #
140
+ # @example
141
+ # future2 = future1.flat_map { |value| method_returning_a_future(value) }
142
+ #
143
+ # This method is useful when you want to chain asynchronous operations.
144
+ #
145
+ # @yieldparam [Object] value the value of this future
146
+ # @yieldreturn [Ione::Future] a future representing the transformed value
147
+ # @return [Ione::Future] a new future representing the transformed value
148
+ def flat_map(&block)
149
+ CompletableFuture.new.tap do |f|
150
+ on_failure { |e| f.fail(e) }
151
+ on_value { |v| chain(f, block, v) }
152
+ end
153
+ end
154
+
155
+ # Returns a new future which represents either the value of the original
156
+ # future, or the result of the given block, if the original future fails.
157
+ #
158
+ # This method is similar to{#map}, but is triggered by a failure. You can
159
+ # also think of it as a `rescue` block for asynchronous operations.
160
+ #
161
+ # If the block raises an error a failed future with that error will be
162
+ # returned (this can be used to transform an error into another error,
163
+ # instead of tranforming an error into a value).
164
+ #
165
+ # @example
166
+ # future2 = future1.recover { |error| 'foo' }
167
+ # future1.fail(error)
168
+ # future2.value # => 'foo'
169
+ #
170
+ # @param [Object] value the value when no block is given
171
+ # @yieldparam [Object] error the error from the original future
172
+ # @yieldreturn [Object] the value of the new future
173
+ # @return [Ione::Future] a new future representing a value recovered from the error
174
+ def recover(value=nil, &block)
175
+ CompletableFuture.new.tap do |f|
176
+ on_failure { |e| run(f, value, block, e) }
177
+ on_value { |v| f.resolve(v) }
178
+ end
179
+ end
180
+
181
+ # Returns a new future which represents either the value of the original
182
+ # future, or the value of the future returned by the given block.
183
+ #
184
+ # This is like {#recover} but for cases when the handling of an error is
185
+ # itself asynchronous. In other words, {#fallback} is to {#recover} what
186
+ # {#flat_map} is to {#map}.
187
+ #
188
+ # If the block raises an error a failed future with that error will be
189
+ # returned (this can be used to transform an error into another error,
190
+ # instead of tranforming an error into a value).
191
+ #
192
+ # @example
193
+ # result = http_get('/foo/bar').fallback do |error|
194
+ # http_get('/baz')
195
+ # end
196
+ # result.value # either the response to /foo/bar, or if that failed
197
+ # # the response to /baz
198
+ #
199
+ # @yieldparam [Object] error the error from the original future
200
+ # @yieldreturn [Object] the value of the new future
201
+ # @return [Ione::Future] a new future representing a value recovered from the
202
+ # error
203
+ def fallback(&block)
204
+ CompletableFuture.new.tap do |f|
205
+ on_failure { |e| chain(f, block, e) }
206
+ on_value { |v| f.resolve(v) }
207
+ end
208
+ end
209
+
210
+ private
211
+
212
+ def run(f, value, producer, arg)
213
+ value = producer ? producer.call(arg) : value
214
+ f.resolve(value)
215
+ rescue => e
216
+ f.fail(e)
217
+ end
218
+
219
+ def chain(f, constructor, arg)
220
+ ff = constructor.call(arg)
221
+ ff.on_failure { |e| f.fail(e) }
222
+ ff.on_value { |v| f.resolve(v) }
223
+ rescue => e
224
+ f.fail(e)
225
+ end
226
+ end
227
+
228
+ module FutureCallbacks
229
+ # Registers a listener that will be called when this future completes,
230
+ # i.e. resolves or fails. The listener will be called with the future as
231
+ # solve argument
232
+ #
233
+ # @yieldparam [Ione::Future] future the future
234
+ def on_complete(&listener)
235
+ run_immediately = false
236
+ @lock.synchronize do
237
+ if @resolved || @failed
238
+ run_immediately = true
239
+ else
240
+ @complete_listeners << listener
241
+ end
242
+ end
243
+ if run_immediately
244
+ listener.call(self) rescue nil
245
+ end
246
+ nil
247
+ end
248
+
249
+ # Registers a listener that will be called when this future becomes
250
+ # resolved. The listener will be called with the value of the future as
251
+ # sole argument.
252
+ #
253
+ # @yieldparam [Object] value the value of the resolved future
254
+ def on_value(&listener)
255
+ run_immediately = false
256
+ @lock.synchronize do
257
+ if @resolved
258
+ run_immediately = true
259
+ elsif !@failed
260
+ @value_listeners << listener
261
+ end
262
+ end
263
+ if run_immediately
264
+ listener.call(value) rescue nil
265
+ end
266
+ nil
267
+ end
268
+
269
+ # Registers a listener that will be called when this future fails. The
270
+ # lisener will be called with the error that failed the future as sole
271
+ # argument.
272
+ #
273
+ # @yieldparam [Error] error the error that failed the future
274
+ def on_failure(&listener)
275
+ run_immediately = false
276
+ @lock.synchronize do
277
+ if @failed
278
+ run_immediately = true
279
+ elsif !@resolved
280
+ @failure_listeners << listener
281
+ end
282
+ end
283
+ if run_immediately
284
+ listener.call(@error) rescue nil
285
+ end
286
+ nil
287
+ end
288
+ end
289
+
290
+ # A future represents the value of a process that may not yet have completed.
291
+ #
292
+ # @see Ione::Promise
293
+ class Future
294
+ extend FutureFactories
295
+ include FutureCombinators
296
+ include FutureCallbacks
297
+
298
+ def initialize
299
+ @lock = Mutex.new
300
+ @resolved = false
301
+ @failed = false
302
+ @failure_listeners = []
303
+ @value_listeners = []
304
+ @complete_listeners = []
305
+ end
306
+
307
+ # Returns the value of this future, blocking until it is available if
308
+ # necessary.
309
+ #
310
+ # If the future fails this method will raise the error that failed the
311
+ # future.
312
+ #
313
+ # @return [Object] the value of this future
314
+ def value
315
+ semaphore = nil
316
+ @lock.synchronize do
317
+ raise @error if @failed
318
+ return @value if @resolved
319
+ semaphore = Queue.new
320
+ u = proc { semaphore << :unblock }
321
+ @value_listeners << u
322
+ @failure_listeners << u
323
+ end
324
+ while true
325
+ @lock.synchronize do
326
+ raise @error if @failed
327
+ return @value if @resolved
328
+ end
329
+ semaphore.pop
330
+ end
331
+ end
332
+
333
+ # Returns true if this future is resolved or failed
334
+ def completed?
335
+ resolved? || failed?
336
+ end
337
+
338
+ # Returns true if this future is resolved
339
+ def resolved?
340
+ @lock.synchronize { @resolved }
341
+ end
342
+
343
+ # Returns true if this future has failed
344
+ def failed?
345
+ @lock.synchronize { @failed }
346
+ end
347
+ end
348
+
349
+ # @private
350
+ class CompletableFuture < Future
351
+ def resolve(v=nil)
352
+ value_listeners = nil
353
+ complete_listeners = nil
354
+ @lock.synchronize do
355
+ raise FutureError, 'Future already completed' if @resolved || @failed
356
+ @resolved = true
357
+ @value = v
358
+ value_listeners = @value_listeners
359
+ complete_listeners = @complete_listeners
360
+ @value_listeners = nil
361
+ @failure_listeners = nil
362
+ @complete_listeners = nil
363
+ end
364
+ value_listeners.each do |listener|
365
+ listener.call(v) rescue nil
366
+ end
367
+ complete_listeners.each do |listener|
368
+ listener.call(self) rescue nil
369
+ end
370
+ nil
371
+ end
372
+
373
+ def fail(error)
374
+ failure_listeners = nil
375
+ complete_listeners = nil
376
+ @lock.synchronize do
377
+ raise FutureError, 'Future already completed' if @failed || @resolved
378
+ @failed = true
379
+ @error = error
380
+ failure_listeners = @failure_listeners
381
+ complete_listeners = @complete_listeners
382
+ @value_listeners = nil
383
+ @failure_listeners = nil
384
+ @complete_listeners = nil
385
+ end
386
+ failure_listeners.each do |listener|
387
+ listener.call(error) rescue nil
388
+ end
389
+ complete_listeners.each do |listener|
390
+ listener.call(self) rescue nil
391
+ end
392
+ nil
393
+ end
394
+ end
395
+
396
+ # @private
397
+ class CombinedFuture < CompletableFuture
398
+ def initialize(futures)
399
+ super()
400
+ values = Array.new(futures.size)
401
+ remaining = futures.size
402
+ futures.each_with_index do |f, i|
403
+ f.on_value do |v|
404
+ @lock.synchronize do
405
+ values[i] = v
406
+ remaining -= 1
407
+ end
408
+ if remaining == 0
409
+ resolve(values)
410
+ end
411
+ end
412
+ f.on_failure do |e|
413
+ unless failed?
414
+ fail(e)
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ # @private
422
+ class FirstFuture < CompletableFuture
423
+ def initialize(futures)
424
+ super()
425
+ futures.each do |f|
426
+ f.on_value do |value|
427
+ resolve(value) unless completed?
428
+ end
429
+ f.on_failure do |e|
430
+ fail(e) if futures.all?(&:failed?)
431
+ end
432
+ end
433
+ end
434
+ end
435
+
436
+ # @private
437
+ class ResolvedFuture < Future
438
+ def initialize(value=nil)
439
+ @resolved = true
440
+ @failed = false
441
+ @value = value
442
+ @error = nil
443
+ end
444
+
445
+ def value
446
+ @value
447
+ end
448
+
449
+ def completed?
450
+ true
451
+ end
452
+
453
+ def resolved?
454
+ true
455
+ end
456
+
457
+ def failed?
458
+ false
459
+ end
460
+
461
+ def on_complete(&listener)
462
+ listener.call(self) rescue nil
463
+ end
464
+
465
+ def on_value(&listener)
466
+ listener.call(value) rescue nil
467
+ end
468
+
469
+ def on_failure
470
+ end
471
+ end
472
+
473
+ # @private
474
+ class FailedFuture < Future
475
+ def initialize(error)
476
+ @resolved = false
477
+ @failed = true
478
+ @value = nil
479
+ @error = error
480
+ end
481
+
482
+ def value
483
+ raise @error
484
+ end
485
+
486
+ def completed?
487
+ true
488
+ end
489
+
490
+ def resolved?
491
+ false
492
+ end
493
+
494
+ def failed?
495
+ true
496
+ end
497
+
498
+ def on_complete(&listener)
499
+ listener.call(self) rescue nil
500
+ end
501
+
502
+ def on_value
503
+ end
504
+
505
+ def on_failure(&listener)
506
+ listener.call(@error) rescue nil
507
+ end
508
+ end
509
+ end