concurrently 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +8 -3
  4. data/README.md +70 -60
  5. data/RELEASE_NOTES.md +16 -1
  6. data/Rakefile +98 -14
  7. data/concurrently.gemspec +16 -12
  8. data/ext/mruby/io.rb +1 -1
  9. data/guides/Overview.md +191 -66
  10. data/guides/Performance.md +300 -102
  11. data/guides/Troubleshooting.md +28 -28
  12. data/lib/Ruby/concurrently/proc/evaluation/error.rb +10 -0
  13. data/lib/all/concurrently/error.rb +0 -3
  14. data/lib/all/concurrently/evaluation.rb +8 -12
  15. data/lib/all/concurrently/event_loop.rb +1 -1
  16. data/lib/all/concurrently/event_loop/fiber.rb +3 -3
  17. data/lib/all/concurrently/event_loop/io_selector.rb +1 -1
  18. data/lib/all/concurrently/event_loop/run_queue.rb +29 -17
  19. data/lib/all/concurrently/proc.rb +13 -13
  20. data/lib/all/concurrently/proc/evaluation.rb +29 -29
  21. data/lib/all/concurrently/proc/evaluation/error.rb +13 -0
  22. data/lib/all/concurrently/proc/fiber.rb +3 -6
  23. data/lib/all/concurrently/version.rb +1 -1
  24. data/lib/all/io.rb +118 -41
  25. data/lib/all/kernel.rb +82 -29
  26. data/lib/mruby/concurrently/event_loop/io_selector.rb +46 -0
  27. data/lib/mruby/kernel.rb +1 -1
  28. data/mrbgem.rake +28 -17
  29. data/mruby_builds/build_config.rb +67 -0
  30. data/perf/Ruby/stage.rb +23 -0
  31. data/perf/benchmark_call_methods.rb +32 -0
  32. data/perf/benchmark_call_methods_waiting.rb +52 -0
  33. data/perf/benchmark_wait_methods.rb +38 -0
  34. data/perf/mruby/stage.rb +8 -0
  35. data/perf/profile_await_readable.rb +10 -0
  36. data/perf/{concurrent_proc_call.rb → profile_call.rb} +1 -5
  37. data/perf/{concurrent_proc_call_and_forget.rb → profile_call_and_forget.rb} +1 -5
  38. data/perf/{concurrent_proc_call_detached.rb → profile_call_detached.rb} +1 -5
  39. data/perf/{concurrent_proc_call_nonblock.rb → profile_call_nonblock.rb} +1 -5
  40. data/perf/profile_wait.rb +7 -0
  41. data/perf/stage.rb +47 -0
  42. data/perf/stage/benchmark.rb +47 -0
  43. data/perf/stage/benchmark/code_gen.rb +29 -0
  44. data/perf/stage/benchmark/code_gen/batch.rb +41 -0
  45. data/perf/stage/benchmark/code_gen/single.rb +38 -0
  46. metadata +27 -23
  47. data/ext/mruby/array.rb +0 -19
  48. data/lib/Ruby/concurrently/error.rb +0 -4
  49. data/perf/_shared/stage.rb +0 -33
  50. data/perf/concurrent_proc_calls.rb +0 -49
  51. data/perf/concurrent_proc_calls_awaiting.rb +0 -48
@@ -0,0 +1,13 @@
1
+ module Concurrently
2
+ class Proc::Evaluation
3
+ class Cancelled < Exception
4
+ # should not be rescued accidentally and therefore is an exception
5
+ end
6
+
7
+ module RescueableError
8
+ [ScriptError, StandardError, SystemStackError].each do |error_class|
9
+ append_features error_class
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,10 +1,6 @@
1
1
  module Concurrently
2
2
  # @private
3
3
  class Proc::Fiber < ::Fiber
4
- class Cancelled < Exception
5
- # should not be rescued accidentally and therefore is an exception
6
- end
7
-
8
4
  EMPTY_EVALUATION_BUCKET = [].freeze
9
5
 
10
6
  def initialize(fiber_pool)
@@ -43,12 +39,13 @@ module Concurrently
43
39
  result = proc.__proc_call__ *args
44
40
  (evaluation = evaluation_bucket[0]) and evaluation.conclude_to result
45
41
  result
46
- rescue Cancelled
42
+ rescue Proc::Evaluation::Cancelled
47
43
  # raised in Kernel#await_resume!
48
44
  :cancelled
49
- rescue *RESCUABLE_ERRORS => error
45
+ rescue Proc::Evaluation::RescueableError => error
50
46
  # Rescue all errors not critical for other concurrent evaluations
51
47
  # and don't let them leak to the loop to keep it up and running.
48
+ STDERR.puts error
52
49
  proc.trigger :error, error
53
50
  (evaluation = evaluation_bucket[0]) and evaluation.conclude_to error
54
51
  error
@@ -4,5 +4,5 @@
4
4
  # The namespace this library lives in
5
5
  module Concurrently
6
6
  # The version
7
- VERSION = "1.0.1"
7
+ VERSION = "1.1.0"
8
8
  end
data/lib/all/io.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  # @api public
2
- # @since 1.0.0
3
2
  #
4
3
  # Concurrently adds a few methods to `IO` which make them available
5
4
  # for every IO instance.
6
5
  class IO
7
- # Suspends the current evaluation until IO is readable. It can be used inside
8
- # and outside of concurrent procs.
6
+ # Suspends the current evaluation until IO is readable.
9
7
  #
10
8
  # While waiting, the code jumps to the event loop and executes other
11
- # concurrent procs that are ready to run in the meantime.
9
+ # evaluations that are ready to run in the meantime.
12
10
  #
13
11
  # @param [Hash] opts
14
12
  # @option opts [Numeric] :within maximum time to wait *(defaults to: Float::INFINITY)*
@@ -19,17 +17,17 @@ class IO
19
17
  # @raise [Concurrently::Evaluation::TimeoutError] if a given maximum waiting time
20
18
  # is exceeded and no custom timeout result is given.
21
19
  #
22
- # @example Waiting inside a concurrent proc
20
+ # @example Waiting inside a concurrent evaluation
23
21
  # # Control flow is indicated by (N)
24
22
  #
25
23
  # r,w = IO.pipe
26
24
  #
27
25
  # # (1)
28
- # wait_proc = concurrent_proc do
29
- # # (4)
30
- # r.await_readable
31
- # # (6)
32
- # r.read
26
+ # reader = concurrently do
27
+ # # (4)
28
+ # r.await_readable
29
+ # # (6)
30
+ # r.read
33
31
  # end
34
32
  #
35
33
  # # (2)
@@ -40,12 +38,12 @@ class IO
40
38
  # end
41
39
  #
42
40
  # # (3)
43
- # wait_proc.call # => 'Hey from the other proc!'
41
+ # reader.await_result # => 'Hey from the other proc!'
44
42
  # # (7)
45
43
  #
46
44
  # r.close
47
45
  #
48
- # @example Waiting outside a concurrent proc
46
+ # @example Waiting outside a concurrent evaluation
49
47
  # # Control flow is indicated by (N)
50
48
  #
51
49
  # r,w = IO.pipe
@@ -73,7 +71,9 @@ class IO
73
71
  # @example Waiting with a timeout and a timeout result
74
72
  # r,w = IO.pipe
75
73
  # r.await_readable(within: 0.1, timeout_result: false)
76
- # # => returns false after 0.1 second
74
+ # # => returns false after 0.1 seconds
75
+ #
76
+ # @since 1.0.0
77
77
  def await_readable(opts = {})
78
78
  io_selector = Concurrently::EventLoop.current.io_selector
79
79
  io_selector.await_reader(self, Concurrently::Evaluation.current)
@@ -82,7 +82,7 @@ class IO
82
82
  io_selector.cancel_reader(self)
83
83
  end
84
84
 
85
- # Reads from IO concurrently.
85
+ # Waits until successfully read from IO with blocking other evaluations.
86
86
  #
87
87
  # If IO is not readable right now it blocks the current concurrent evaluation
88
88
  # and tries again after it became readable.
@@ -91,9 +91,9 @@ class IO
91
91
  #
92
92
  # ```
93
93
  # begin
94
- # read_nonblock(maxlen, buf)
94
+ # io.read_nonblock(maxlen, outbuf)
95
95
  # rescue IO::WaitReadable
96
- # await_readable
96
+ # io.await_readable
97
97
  # retry
98
98
  # end
99
99
  # ```
@@ -103,33 +103,75 @@ class IO
103
103
  #
104
104
  # @example
105
105
  # r,w = IO.pipe
106
- # w.concurrently_write "Hello!"
107
- # r.concurrently_read 1024 # => "Hello!"
106
+ # w.write "Hello!"
107
+ # r.await_read 1024 # => "Hello!"
108
108
  #
109
- # @overload concurrently_read(maxlen)
109
+ # @overload await_read(maxlen)
110
110
  # Reads maxlen bytes from IO and returns it as new string
111
111
  #
112
112
  # @param [Integer] maxlen
113
113
  # @return [String] read string
114
114
  #
115
- # @overload concurrently_read(maxlen, outbuf)
115
+ # @overload await_read(maxlen, outbuf)
116
116
  # Reads maxlen bytes from IO and fills the given buffer with them.
117
117
  #
118
118
  # @param [Integer] maxlen
119
119
  # @param [String] outbuf
120
120
  # @return [outbuf] outbuf filled with read string
121
- def concurrently_read(maxlen, outbuf = nil)
121
+ #
122
+ # @since 1.1.0
123
+ def await_read(maxlen, outbuf = nil)
122
124
  read_nonblock(maxlen, outbuf)
123
125
  rescue IO::WaitReadable
124
126
  await_readable
125
127
  retry
126
128
  end
127
129
 
128
- # Suspends the current evaluation until IO is writable. It can be used inside
129
- # and outside of concurrent procs.
130
+ # Reads from IO concurrently.
131
+ #
132
+ # Reading is done in a concurrent evaluation in the background.
133
+ #
134
+ # This method is a shortcut for:
135
+ #
136
+ # ```
137
+ # concurrently{ io.await_read(maxlen, outbuf) }
138
+ # ```
139
+ #
140
+ # @see https://ruby-doc.org/core/IO.html#method-i-read_nonblock
141
+ # Ruby documentation for `IO#read_nonblock` for details about parameters and return values.
142
+ #
143
+ # @example
144
+ # r,w = IO.pipe
145
+ # w.write "Hello!"
146
+ # r.concurrently_read 1024 # => "Hello!"
147
+ #
148
+ # @overload concurrently_read(maxlen)
149
+ # Reads maxlen bytes from IO and returns it as new string
150
+ #
151
+ # @param [Integer] maxlen
152
+ # @return [String] read string
153
+ #
154
+ # @overload concurrently_read(maxlen, outbuf)
155
+ # Reads maxlen bytes from IO and fills the given buffer with them.
156
+ #
157
+ # @param [Integer] maxlen
158
+ # @param [String] outbuf
159
+ # @return [outbuf] outbuf filled with read string
160
+ #
161
+ # @since 1.0.0
162
+ def concurrently_read(maxlen, outbuf = nil)
163
+ READ_PROC.call_detached(self, maxlen, outbuf)
164
+ end
165
+
166
+ # @private
167
+ READ_PROC = Concurrently::Proc.new do |io, maxlen, outbuf|
168
+ io.await_read(maxlen, outbuf)
169
+ end
170
+
171
+ # Suspends the current evaluation until IO is writable.
130
172
  #
131
173
  # While waiting, the code jumps to the event loop and executes other
132
- # concurrent procs that are ready to run in the meantime.
174
+ # evaluations that are ready to run in the meantime.
133
175
  #
134
176
  # @param [Hash] opts
135
177
  # @option opts [Numeric] :within maximum time to wait *(defaults to: Float::INFINITY)*
@@ -140,7 +182,7 @@ class IO
140
182
  # @raise [Concurrently::Evaluation::TimeoutError] if a given maximum waiting time
141
183
  # is exceeded and no custom timeout result is given.
142
184
  #
143
- # @example Waiting inside a concurrent proc
185
+ # @example Waiting inside a evaluation
144
186
  # # Control flow is indicated by (N)
145
187
  #
146
188
  # r,w = IO.pipe
@@ -149,12 +191,12 @@ class IO
149
191
  # w.write 'x'*65536
150
192
  #
151
193
  # # (1)
152
- # wait_proc = concurrent_proc do
153
- # # (4)
154
- # w.await_writable
155
- # # (6)
156
- # w.write 'I can write again!'
157
- # :written
194
+ # writer = concurrently do
195
+ # # (4)
196
+ # w.await_writable
197
+ # # (6)
198
+ # w.write 'I can write again!'
199
+ # :written
158
200
  # end
159
201
  #
160
202
  # # (2)
@@ -164,12 +206,12 @@ class IO
164
206
  # end
165
207
  #
166
208
  # # (3)
167
- # wait_proc.call # => :written
209
+ # writer.await_result # => :written
168
210
  # # (7)
169
211
  #
170
212
  # r.close; w.close
171
213
  #
172
- # @example Waiting outside a concurrent proc
214
+ # @example Waiting outside a evaluation
173
215
  # # Control flow is indicated by (N)
174
216
  #
175
217
  # r,w = IO.pipe
@@ -204,7 +246,9 @@ class IO
204
246
  # w.write 'x'*65536
205
247
  #
206
248
  # w.await_writable(within: 0.1, timeout_result: false)
207
- # # => returns false after 0.1 second
249
+ # # => returns false after 0.1 seconds
250
+ #
251
+ # @since 1.0.0
208
252
  def await_writable(opts = {})
209
253
  io_selector = Concurrently::EventLoop.current.io_selector
210
254
  io_selector.await_writer(self, Concurrently::Evaluation.current)
@@ -213,18 +257,18 @@ class IO
213
257
  io_selector.cancel_writer(self)
214
258
  end
215
259
 
216
- # Writes to IO concurrently.
260
+ # Waits until successfully written to IO with blocking other evaluations.
217
261
  #
218
- # If IO is not writable right now it blocks the current concurrent proc
262
+ # If IO is not writable right now it blocks the current evaluation
219
263
  # and tries again after it became writable.
220
264
  #
221
265
  # This methods is a shortcut for:
222
266
  #
223
267
  # ```
224
268
  # begin
225
- # write_nonblock(string)
269
+ # io.write_nonblock(string)
226
270
  # rescue IO::WaitWritable
227
- # await_writable
271
+ # io.await_writable
228
272
  # retry
229
273
  # end
230
274
  # ```
@@ -237,12 +281,45 @@ class IO
237
281
  #
238
282
  # @example
239
283
  # r,w = IO.pipe
240
- # w.concurrently_write "Hello!"
241
- # r.concurrently_read 1024 # => "Hello!"
242
- def concurrently_write(string)
284
+ # w.await_written "Hello!"
285
+ # r.read 1024 # => "Hello!"
286
+ #
287
+ # @since 1.1.0
288
+ def await_written(string)
243
289
  write_nonblock(string)
244
290
  rescue IO::WaitWritable
245
291
  await_writable
246
292
  retry
247
293
  end
294
+
295
+ # Writes to IO concurrently.
296
+ #
297
+ # Writing is done in a concurrent evaluation in the background.
298
+ #
299
+ # This method is a shortcut for:
300
+ #
301
+ # ```
302
+ # concurrently{ io.await_written(string) }
303
+ # ```
304
+ #
305
+ # @param [String] string to write
306
+ # @return [Integer] bytes written
307
+ #
308
+ # @see https://ruby-doc.org/core/IO.html#method-i-write_nonblock
309
+ # Ruby documentation for `IO#write_nonblock` for details about parameters and return values.
310
+ #
311
+ # @example
312
+ # r,w = IO.pipe
313
+ # w.concurrently_write "Hello!"
314
+ # r.read 1024 # => "Hello!"
315
+ #
316
+ # @since 1.0.0
317
+ def concurrently_write(string)
318
+ WRITE_PROC.call_detached(self, string)
319
+ end
320
+
321
+ # @private
322
+ WRITE_PROC = Concurrently::Proc.new do |io, string|
323
+ io.await_written(string)
324
+ end
248
325
  end
data/lib/all/kernel.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  # @api public
2
- # @since 1.0.0
3
2
  #
4
3
  # Concurrently adds a few methods to `Kernel` which makes them available
5
4
  # for every object.
@@ -8,7 +7,7 @@ module Kernel
8
7
  #
9
8
  # Executes code concurrently in the background.
10
9
  #
11
- # This is a shortcut for {Concurrently::Proc#call_and_forget}.
10
+ # This is a shortcut for {Concurrently::Proc#call_detached}.
12
11
  #
13
12
  # @return [nil]
14
13
  #
@@ -16,9 +15,11 @@ module Kernel
16
15
  # concurrently(a,b,c) do |a,b,c|
17
16
  # # ...
18
17
  # end
18
+ #
19
+ # @since 1.0.0
19
20
  private def concurrently(*args)
20
21
  # Concurrently::Proc.new claims the method's block just like Proc.new does
21
- Concurrently::Proc.new.call_and_forget *args
22
+ Concurrently::Proc.new.call_detached *args
22
23
  end
23
24
 
24
25
  # @!method concurrent_proc(&block)
@@ -32,10 +33,12 @@ module Kernel
32
33
  #
33
34
  # @example
34
35
  # wait_proc = concurrent_proc do |seconds|
35
- # wait seconds
36
+ # wait seconds
36
37
  # end
37
38
  #
38
39
  # wait_proc.call 2 # waits 2 seconds and then resumes
40
+ #
41
+ # @since 1.0.0
39
42
  private def concurrent_proc(evaluation_class = Concurrently::Proc::Evaluation)
40
43
  # Concurrently::Proc.new claims the method's block just like Proc.new does
41
44
  Concurrently::Proc.new(evaluation_class)
@@ -45,8 +48,7 @@ module Kernel
45
48
  # This method needs to be complemented with a later call to
46
49
  # {Concurrently::Evaluation#resume!}.
47
50
  #
48
- # Suspends the current evaluation until it is resumed manually. It can be
49
- # used inside and outside of concurrent procs.
51
+ # Suspends the current evaluation until it is resumed manually.
50
52
  #
51
53
  # It needs to be complemented with a later call of {Concurrently::Evaluation#resume!}.
52
54
  #
@@ -60,15 +62,15 @@ module Kernel
60
62
  # @raise [Concurrently::Evaluation::TimeoutError] if a given maximum waiting time
61
63
  # is exceeded and no custom timeout result is given.
62
64
  #
63
- # @example Waiting inside a concurrent proc
65
+ # @example Waiting inside a concurrent evaluation
64
66
  # # Control flow is indicated by (N)
65
67
  #
66
68
  # # (1)
67
- # evaluation = concurrent_proc do
68
- # # (4)
69
- # await_resume!
70
- # # (7)
71
- # end.call_nonblock
69
+ # evaluation = concurrently do
70
+ # # (4)
71
+ # await_resume!
72
+ # # (7)
73
+ # end
72
74
  #
73
75
  # # (2)
74
76
  # concurrently do
@@ -82,7 +84,7 @@ module Kernel
82
84
  # evaluation.await_result # => :result
83
85
  # # (8)
84
86
  #
85
- # @example Waiting outside a concurrent proc
87
+ # @example Waiting outside a concurrent evaluation
86
88
  # # Control flow is indicated by (N)
87
89
  #
88
90
  # evaluation = Concurrently::Evaluation.current
@@ -105,7 +107,9 @@ module Kernel
105
107
  #
106
108
  # @example Waiting with a timeout and a timeout result
107
109
  # await_resume! within: 0.1, timeout_result: false
108
- # # => returns false after 0.1 second
110
+ # # => returns false after 0.1 seconds
111
+ #
112
+ # @since 1.0.0
109
113
  private def await_resume!(opts = {})
110
114
  event_loop = Concurrently::EventLoop.current
111
115
  run_queue = event_loop.run_queue
@@ -131,13 +135,7 @@ module Kernel
131
135
  # prematurely.
132
136
  if evaluation.fiber == result
133
137
  run_queue.cancel evaluation # in case the evaluation has already been scheduled to resume
134
-
135
- # Generally, throw-catch is faster than raise-rescue if the code needs to
136
- # play back the call stack, i.e. the throw resp. raise is invoked. If not
137
- # playing back the call stack, a begin block is faster than a catch
138
- # block. Since we won't jump out of the proc above most of the time, we
139
- # go with raise. It is rescued in the proc fiber.
140
- raise Concurrently::Proc::Fiber::Cancelled, '', []
138
+ raise Concurrently::Proc::Evaluation::Cancelled, ''
141
139
  elsif Concurrently::Evaluation::TimeoutError == result
142
140
  raise result, "evaluation timed out after #{seconds} second(s)"
143
141
  else
@@ -149,21 +147,20 @@ module Kernel
149
147
  end
150
148
  end
151
149
 
152
- # Suspends the current evaluation for the given number of seconds. It can be
153
- # used inside and outside of concurrent procs.
150
+ # Suspends the current evaluation for the given number of seconds.
154
151
  #
155
152
  # While waiting, the code jumps to the event loop and executes other
156
- # concurrent procs that are ready to run in the meantime.
153
+ # evaluations that are ready to run in the meantime.
157
154
  #
158
155
  # @return [true]
159
156
  #
160
- # @example Waiting inside a concurrent proc
157
+ # @example Waiting inside a concurrent evaluation
161
158
  # # Control flow is indicated by (N)
162
159
  #
163
160
  # # (1)
164
- # wait_proc = concurrent_proc do |seconds|
161
+ # wait_eval = concurrently do
165
162
  # # (4)
166
- # wait seconds
163
+ # wait 1
167
164
  # # (6)
168
165
  # :waited
169
166
  # end
@@ -175,10 +172,10 @@ module Kernel
175
172
  # end
176
173
  #
177
174
  # # (3)
178
- # wait_proc.call 1 # => :waited
175
+ # wait_eval.await_result # => :waited
179
176
  # # (7)
180
177
  #
181
- # @example Waiting outside a concurrent proc
178
+ # @example Waiting outside a concurrent evaluation
182
179
  # # Control flow is indicated by (N)
183
180
  #
184
181
  # # (1)
@@ -190,6 +187,8 @@ module Kernel
190
187
  # # (2)
191
188
  # wait 1
192
189
  # # (4)
190
+ #
191
+ # @since 1.0.0
193
192
  private def wait(seconds)
194
193
  run_queue = Concurrently::EventLoop.current.run_queue
195
194
  evaluation = run_queue.current_evaluation
@@ -198,4 +197,58 @@ module Kernel
198
197
  ensure
199
198
  run_queue.cancel evaluation
200
199
  end
200
+
201
+ # Waits for the first in a list of evaluations to be concluded.
202
+ #
203
+ # @overload await_fastest(evaluation0, evaluation1, *more_evaluations, opts = {})
204
+ #
205
+ # @param [Concurrently::Proc::Evaluation] evaluation0
206
+ # @param [Concurrently::Proc::Evaluation] evaluation1
207
+ # @param [Concurrently::Proc::Evaluation] *more_evaluations
208
+ # @param [Hash] opts
209
+ # @option opts [Numeric] :within maximum time to wait *(defaults to: Float::INFINITY)*
210
+ # @option opts [Object] :timeout_result result to return in case of an exceeded
211
+ # waiting time *(defaults to raising {Concurrently::Evaluation::TimeoutError})*
212
+ #
213
+ # @return [Concurrently::Proc::Evaluation] the evaluation been concluded first
214
+ # @raise [Concurrently::Evaluation::TimeoutError] if a given maximum waiting time
215
+ # is exceeded and no custom timeout result is given.
216
+ #
217
+ # @example
218
+ # evaluation0 = concurrently{ wait 2 }
219
+ # evaluation1 = concurrently{ wait 1 }
220
+ #
221
+ # await_fastest(evaluation0, evaluation1) # => evaluation1
222
+ #
223
+ # @example Waiting with a timeout
224
+ # evaluation0 = concurrently{ wait 2 }
225
+ # evaluation1 = concurrently{ wait 1 }
226
+ #
227
+ # await_fastest(evaluation0, evaluation1, within: 0.1)
228
+ # # => raises a TimeoutError after 0.1 seconds
229
+ #
230
+ # @example Waiting with a timeout and a timeout result
231
+ # evaluation0 = concurrently{ wait 2 }
232
+ # evaluation1 = concurrently{ wait 1 }
233
+ #
234
+ # await_fastest(evaluation0, evaluation1, within: 0.1, timeout_result: false)
235
+ # # => returns false after 0.1 seconds
236
+ #
237
+ # @since 1.1.0
238
+ private def await_fastest(eval0, eval1, *evaluations)
239
+ opts = (evaluations.last.is_a? Hash) ? evaluations.pop : {}
240
+ evaluations.unshift eval0, eval1
241
+
242
+ if concluded = evaluations.find(&:concluded?)
243
+ concluded
244
+ else
245
+ begin
246
+ curr_eval = Concurrently::Evaluation.current
247
+ evaluations.each{ |e| e.instance_eval{ @awaiting_result.store curr_eval, self } }
248
+ await_resume! opts
249
+ ensure
250
+ evaluations.each{ |e| e.instance_eval{ @awaiting_result.delete curr_eval } }
251
+ end
252
+ end
253
+ end
201
254
  end