concurrently 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +8 -3
- data/README.md +70 -60
- data/RELEASE_NOTES.md +16 -1
- data/Rakefile +98 -14
- data/concurrently.gemspec +16 -12
- data/ext/mruby/io.rb +1 -1
- data/guides/Overview.md +191 -66
- data/guides/Performance.md +300 -102
- data/guides/Troubleshooting.md +28 -28
- data/lib/Ruby/concurrently/proc/evaluation/error.rb +10 -0
- data/lib/all/concurrently/error.rb +0 -3
- data/lib/all/concurrently/evaluation.rb +8 -12
- data/lib/all/concurrently/event_loop.rb +1 -1
- data/lib/all/concurrently/event_loop/fiber.rb +3 -3
- data/lib/all/concurrently/event_loop/io_selector.rb +1 -1
- data/lib/all/concurrently/event_loop/run_queue.rb +29 -17
- data/lib/all/concurrently/proc.rb +13 -13
- data/lib/all/concurrently/proc/evaluation.rb +29 -29
- data/lib/all/concurrently/proc/evaluation/error.rb +13 -0
- data/lib/all/concurrently/proc/fiber.rb +3 -6
- data/lib/all/concurrently/version.rb +1 -1
- data/lib/all/io.rb +118 -41
- data/lib/all/kernel.rb +82 -29
- data/lib/mruby/concurrently/event_loop/io_selector.rb +46 -0
- data/lib/mruby/kernel.rb +1 -1
- data/mrbgem.rake +28 -17
- data/mruby_builds/build_config.rb +67 -0
- data/perf/Ruby/stage.rb +23 -0
- data/perf/benchmark_call_methods.rb +32 -0
- data/perf/benchmark_call_methods_waiting.rb +52 -0
- data/perf/benchmark_wait_methods.rb +38 -0
- data/perf/mruby/stage.rb +8 -0
- data/perf/profile_await_readable.rb +10 -0
- data/perf/{concurrent_proc_call.rb → profile_call.rb} +1 -5
- data/perf/{concurrent_proc_call_and_forget.rb → profile_call_and_forget.rb} +1 -5
- data/perf/{concurrent_proc_call_detached.rb → profile_call_detached.rb} +1 -5
- data/perf/{concurrent_proc_call_nonblock.rb → profile_call_nonblock.rb} +1 -5
- data/perf/profile_wait.rb +7 -0
- data/perf/stage.rb +47 -0
- data/perf/stage/benchmark.rb +47 -0
- data/perf/stage/benchmark/code_gen.rb +29 -0
- data/perf/stage/benchmark/code_gen/batch.rb +41 -0
- data/perf/stage/benchmark/code_gen/single.rb +38 -0
- metadata +27 -23
- data/ext/mruby/array.rb +0 -19
- data/lib/Ruby/concurrently/error.rb +0 -4
- data/perf/_shared/stage.rb +0 -33
- data/perf/concurrent_proc_calls.rb +0 -49
- 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
|
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
|
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.
|
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
|
-
#
|
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
|
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
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
-
#
|
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,
|
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.
|
107
|
-
# r.
|
106
|
+
# w.write "Hello!"
|
107
|
+
# r.await_read 1024 # => "Hello!"
|
108
108
|
#
|
109
|
-
# @overload
|
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
|
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
|
-
|
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
|
-
#
|
129
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
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
|
-
#
|
209
|
+
# writer.await_result # => :written
|
168
210
|
# # (7)
|
169
211
|
#
|
170
212
|
# r.close; w.close
|
171
213
|
#
|
172
|
-
# @example Waiting outside a
|
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
|
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
|
-
#
|
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
|
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.
|
241
|
-
# r.
|
242
|
-
|
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#
|
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.
|
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
|
-
#
|
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.
|
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
|
65
|
+
# @example Waiting inside a concurrent evaluation
|
64
66
|
# # Control flow is indicated by (N)
|
65
67
|
#
|
66
68
|
# # (1)
|
67
|
-
# evaluation =
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
# end
|
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
|
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
|
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.
|
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
|
-
#
|
153
|
+
# evaluations that are ready to run in the meantime.
|
157
154
|
#
|
158
155
|
# @return [true]
|
159
156
|
#
|
160
|
-
# @example Waiting inside a concurrent
|
157
|
+
# @example Waiting inside a concurrent evaluation
|
161
158
|
# # Control flow is indicated by (N)
|
162
159
|
#
|
163
160
|
# # (1)
|
164
|
-
#
|
161
|
+
# wait_eval = concurrently do
|
165
162
|
# # (4)
|
166
|
-
# wait
|
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
|
-
#
|
175
|
+
# wait_eval.await_result # => :waited
|
179
176
|
# # (7)
|
180
177
|
#
|
181
|
-
# @example Waiting outside a concurrent
|
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
|