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