concurrently 1.0.1 → 1.1.0

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