ione 1.0.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 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