async 2.12.1 → 2.21.3
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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/barrier.rb +5 -5
- data/lib/async/clock.rb +1 -1
- data/lib/async/condition.rb +11 -6
- data/lib/async/console.rb +42 -0
- data/lib/async/idler.rb +21 -1
- data/lib/async/limited_queue.rb +7 -0
- data/lib/async/list.rb +7 -4
- data/lib/async/node.rb +48 -6
- data/lib/async/notification.rb +5 -3
- data/lib/async/queue.rb +82 -21
- data/lib/async/reactor.rb +4 -2
- data/lib/async/scheduler.rb +210 -75
- data/lib/async/semaphore.rb +3 -3
- data/lib/async/task.rb +114 -55
- data/lib/async/variable.rb +26 -5
- data/lib/async/version.rb +1 -1
- data/lib/async/waiter.rb +8 -3
- data/lib/async/worker_pool.rb +182 -0
- data/lib/async/wrapper.rb +6 -2
- data/lib/async.rb +2 -1
- data/lib/kernel/async.rb +4 -2
- data/lib/kernel/sync.rb +12 -5
- data/lib/metrics/provider/async/task.rb +17 -0
- data/lib/metrics/provider/async.rb +6 -0
- data/lib/traces/provider/async/barrier.rb +17 -0
- data/lib/traces/provider/async/task.rb +40 -0
- data/lib/traces/provider/async.rb +7 -0
- data/license.md +2 -1
- data/readme.md +49 -29
- data/releases.md +97 -0
- data.tar.gz.sig +0 -0
- metadata +45 -24
- metadata.gz.sig +0 -0
data/lib/async/scheduler.rb
CHANGED
@@ -5,30 +5,44 @@
|
|
5
5
|
# Copyright, 2020, by Jun Jiang.
|
6
6
|
# Copyright, 2021, by Julien Portalier.
|
7
7
|
|
8
|
-
require_relative
|
9
|
-
require_relative
|
8
|
+
require_relative "clock"
|
9
|
+
require_relative "task"
|
10
|
+
require_relative "worker_pool"
|
10
11
|
|
11
|
-
require
|
12
|
+
require "io/event"
|
12
13
|
|
13
|
-
require
|
14
|
-
require
|
14
|
+
require "console"
|
15
|
+
require "resolv"
|
15
16
|
|
16
17
|
module Async
|
17
18
|
# Handles scheduling of fibers. Implements the fiber scheduler interface.
|
18
19
|
class Scheduler < Node
|
20
|
+
DEFAULT_WORKER_POOL = ENV.fetch("ASYNC_SCHEDULER_DEFAULT_WORKER_POOL", nil).then do |value|
|
21
|
+
value == "true" ? true : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
# Raised when an operation is attempted on a closed scheduler.
|
19
25
|
class ClosedError < RuntimeError
|
26
|
+
# Create a new error.
|
27
|
+
#
|
28
|
+
# @parameter message [String] The error message.
|
20
29
|
def initialize(message = "Scheduler is closed!")
|
21
30
|
super
|
22
31
|
end
|
23
32
|
end
|
24
33
|
|
25
34
|
# Whether the fiber scheduler is supported.
|
26
|
-
# @public Since
|
35
|
+
# @public Since *Async v1*.
|
27
36
|
def self.supported?
|
28
37
|
true
|
29
38
|
end
|
30
39
|
|
31
|
-
|
40
|
+
# Create a new scheduler.
|
41
|
+
#
|
42
|
+
# @public Since *Async v1*.
|
43
|
+
# @parameter parent [Node | Nil] The parent node to use for task hierarchy.
|
44
|
+
# @parameter selector [IO::Event::Selector] The selector to use for event handling.
|
45
|
+
def initialize(parent = nil, selector: nil, worker_pool: DEFAULT_WORKER_POOL)
|
32
46
|
super(parent)
|
33
47
|
|
34
48
|
@selector = selector || ::IO::Event::Selector.new(Fiber.current)
|
@@ -40,9 +54,19 @@ module Async
|
|
40
54
|
@idle_time = 0.0
|
41
55
|
|
42
56
|
@timers = ::IO::Event::Timers.new
|
57
|
+
if worker_pool == true
|
58
|
+
@worker_pool = WorkerPool.new
|
59
|
+
else
|
60
|
+
@worker_pool = worker_pool
|
61
|
+
end
|
62
|
+
|
63
|
+
if @worker_pool
|
64
|
+
self.singleton_class.prepend(WorkerPool::BlockingOperationWait)
|
65
|
+
end
|
43
66
|
end
|
44
67
|
|
45
68
|
# Compute the scheduler load according to the busy and idle times that are updated by the run loop.
|
69
|
+
#
|
46
70
|
# @returns [Float] The load of the scheduler. 0.0 means no load, 1.0 means fully loaded or over-loaded.
|
47
71
|
def load
|
48
72
|
total_time = @busy_time + @idle_time
|
@@ -62,52 +86,61 @@ module Async
|
|
62
86
|
return @busy_time / total_time
|
63
87
|
end
|
64
88
|
end
|
65
|
-
|
66
|
-
|
89
|
+
|
90
|
+
# Invoked when the fiber scheduler is being closed.
|
91
|
+
#
|
92
|
+
# Executes the run loop until all tasks are finished, then closes the scheduler.
|
93
|
+
def scheduler_close(error = $!)
|
67
94
|
# If the execution context (thread) was handling an exception, we want to exit as quickly as possible:
|
68
|
-
unless
|
95
|
+
unless error
|
69
96
|
self.run
|
70
97
|
end
|
71
98
|
ensure
|
72
99
|
self.close
|
73
100
|
end
|
74
101
|
|
75
|
-
# Terminate
|
102
|
+
# Terminate all child tasks.
|
76
103
|
def terminate
|
77
|
-
|
78
|
-
|
104
|
+
# If that doesn't work, take more serious action:
|
105
|
+
@children&.each do |child|
|
106
|
+
child.terminate
|
79
107
|
end
|
108
|
+
|
109
|
+
return @children.nil?
|
80
110
|
end
|
81
111
|
|
82
|
-
#
|
112
|
+
# Terminate all child tasks and close the scheduler.
|
113
|
+
# @public Since *Async v1*.
|
83
114
|
def close
|
84
|
-
|
85
|
-
|
86
|
-
|
115
|
+
self.run_loop do
|
116
|
+
until self.terminate
|
117
|
+
self.run_once!
|
118
|
+
end
|
87
119
|
end
|
88
120
|
|
89
121
|
Kernel.raise "Closing scheduler with blocked operations!" if @blocked > 0
|
90
|
-
|
91
|
-
# We depend on GVL for consistency:
|
92
|
-
# @guard.synchronize do
|
93
|
-
|
122
|
+
ensure
|
94
123
|
# We want `@selector = nil` to be a visible side effect from this point forward, specifically in `#interrupt` and `#unblock`. If the selector is closed, then we don't want to push any fibers to it.
|
95
124
|
selector = @selector
|
96
125
|
@selector = nil
|
97
126
|
|
98
127
|
selector&.close
|
99
128
|
|
100
|
-
|
129
|
+
worker_pool = @worker_pool
|
130
|
+
@worker_pool = nil
|
131
|
+
|
132
|
+
worker_pool&.close
|
101
133
|
|
102
134
|
consume
|
103
135
|
end
|
104
136
|
|
105
137
|
# @returns [Boolean] Whether the scheduler has been closed.
|
106
|
-
# @public Since
|
138
|
+
# @public Since *Async v1*.
|
107
139
|
def closed?
|
108
140
|
@selector.nil?
|
109
141
|
end
|
110
142
|
|
143
|
+
# @returns [String] A description of the scheduler.
|
111
144
|
def to_s
|
112
145
|
"\#<#{self.description} #{@children&.size || 0} children (#{stopped? ? 'stopped' : 'running'})>"
|
113
146
|
end
|
@@ -135,16 +168,31 @@ module Async
|
|
135
168
|
@selector.push(fiber)
|
136
169
|
end
|
137
170
|
|
138
|
-
|
139
|
-
|
171
|
+
# Raise an exception on a specified fiber with the given arguments.
|
172
|
+
#
|
173
|
+
# This internally schedules the current fiber to be ready, before raising the exception, so that it will later resume execution.
|
174
|
+
#
|
175
|
+
# @parameter fiber [Fiber] The fiber to raise the exception on.
|
176
|
+
# @parameter *arguments [Array] The arguments to pass to the fiber.
|
177
|
+
def raise(...)
|
178
|
+
@selector.raise(...)
|
140
179
|
end
|
141
180
|
|
181
|
+
# Resume execution of the specified fiber.
|
182
|
+
#
|
183
|
+
# @parameter fiber [Fiber] The fiber to resume.
|
184
|
+
# @parameter arguments [Array] The arguments to pass to the fiber.
|
142
185
|
def resume(fiber, *arguments)
|
143
186
|
@selector.resume(fiber, *arguments)
|
144
187
|
end
|
145
188
|
|
146
189
|
# Invoked when a fiber tries to perform a blocking operation which cannot continue. A corresponding call {unblock} must be performed to allow this fiber to continue.
|
190
|
+
#
|
191
|
+
# @public Since *Async v2*.
|
147
192
|
# @asynchronous May only be called on same thread as fiber scheduler.
|
193
|
+
#
|
194
|
+
# @parameter blocker [Object] The object that is blocking the fiber.
|
195
|
+
# @parameter timeout [Float | Nil] The maximum time to block, or if nil, indefinitely.
|
148
196
|
def block(blocker, timeout)
|
149
197
|
# $stderr.puts "block(#{blocker}, #{Fiber.current}, #{timeout})"
|
150
198
|
fiber = Fiber.current
|
@@ -167,7 +215,13 @@ module Async
|
|
167
215
|
timer&.cancel!
|
168
216
|
end
|
169
217
|
|
218
|
+
# Unblock a fiber that was previously blocked.
|
219
|
+
#
|
220
|
+
# @public Since *Async v2* and *Ruby v3.1*.
|
170
221
|
# @asynchronous May be called from any thread.
|
222
|
+
#
|
223
|
+
# @parameter blocker [Object] The object that was blocking the fiber.
|
224
|
+
# @parameter fiber [Fiber] The fiber to unblock.
|
171
225
|
def unblock(blocker, fiber)
|
172
226
|
# $stderr.puts "unblock(#{blocker}, #{fiber})"
|
173
227
|
|
@@ -178,7 +232,12 @@ module Async
|
|
178
232
|
end
|
179
233
|
end
|
180
234
|
|
181
|
-
#
|
235
|
+
# Sleep for the specified duration.
|
236
|
+
#
|
237
|
+
# @public Since *Async v2* and *Ruby v3.1*.
|
238
|
+
# @asynchronous May be non-blocking.
|
239
|
+
#
|
240
|
+
# @parameter duration [Numeric | Nil] The time in seconds to sleep, or if nil, indefinitely.
|
182
241
|
def kernel_sleep(duration = nil)
|
183
242
|
if duration
|
184
243
|
self.block(nil, duration)
|
@@ -187,7 +246,12 @@ module Async
|
|
187
246
|
end
|
188
247
|
end
|
189
248
|
|
190
|
-
#
|
249
|
+
# Resolve the address of the given hostname.
|
250
|
+
#
|
251
|
+
# @public Since *Async v2*.
|
252
|
+
# @asynchronous May be non-blocking.
|
253
|
+
#
|
254
|
+
# @parameter hostname [String] The hostname to resolve.
|
191
255
|
def address_resolve(hostname)
|
192
256
|
# On some platforms, hostnames may contain a device-specific suffix (e.g. %en0). We need to strip this before resolving.
|
193
257
|
# See <https://github.com/socketry/async/issues/180> for more details.
|
@@ -195,7 +259,6 @@ module Async
|
|
195
259
|
::Resolv.getaddresses(hostname)
|
196
260
|
end
|
197
261
|
|
198
|
-
|
199
262
|
if IO.method_defined?(:timeout)
|
200
263
|
private def get_timeout(io)
|
201
264
|
io.timeout
|
@@ -206,7 +269,14 @@ module Async
|
|
206
269
|
end
|
207
270
|
end
|
208
271
|
|
209
|
-
#
|
272
|
+
# Wait for the specified IO to become ready for the specified events.
|
273
|
+
#
|
274
|
+
# @public Since *Async v2*.
|
275
|
+
# @asynchronous May be non-blocking.
|
276
|
+
#
|
277
|
+
# @parameter io [IO] The IO object to wait on.
|
278
|
+
# @parameter events [Integer] The events to wait for, e.g. `IO::READABLE`, `IO::WRITABLE`, etc.
|
279
|
+
# @parameter timeout [Float | Nil] The maximum time to wait, or if nil, indefinitely.
|
210
280
|
def io_wait(io, events, timeout = nil)
|
211
281
|
fiber = Fiber.current
|
212
282
|
|
@@ -218,7 +288,7 @@ module Async
|
|
218
288
|
elsif timeout = get_timeout(io)
|
219
289
|
# Otherwise, if we default to the io's timeout, we raise an exception:
|
220
290
|
timer = @timers.after(timeout) do
|
221
|
-
fiber.raise(::IO::TimeoutError, "Timeout while waiting for IO to become ready!")
|
291
|
+
fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become ready!")
|
222
292
|
end
|
223
293
|
end
|
224
294
|
|
@@ -228,12 +298,21 @@ module Async
|
|
228
298
|
end
|
229
299
|
|
230
300
|
if ::IO::Event::Support.buffer?
|
301
|
+
# Read from the specified IO into the buffer.
|
302
|
+
#
|
303
|
+
# @public Since *Async v2* and Ruby with `IO::Buffer` support.
|
304
|
+
# @asynchronous May be non-blocking.
|
305
|
+
#
|
306
|
+
# @parameter io [IO] The IO object to read from.
|
307
|
+
# @parameter buffer [IO::Buffer] The buffer to read into.
|
308
|
+
# @parameter length [Integer] The minimum number of bytes to read.
|
309
|
+
# @parameter offset [Integer] The offset within the buffer to read into.
|
231
310
|
def io_read(io, buffer, length, offset = 0)
|
232
311
|
fiber = Fiber.current
|
233
312
|
|
234
313
|
if timeout = get_timeout(io)
|
235
314
|
timer = @timers.after(timeout) do
|
236
|
-
fiber.raise(::IO::TimeoutError, "Timeout while waiting for IO to become readable!")
|
315
|
+
fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become readable!")
|
237
316
|
end
|
238
317
|
end
|
239
318
|
|
@@ -243,12 +322,21 @@ module Async
|
|
243
322
|
end
|
244
323
|
|
245
324
|
if RUBY_ENGINE != "ruby" || RUBY_VERSION >= "3.3.1"
|
325
|
+
# Write the specified buffer to the IO.
|
326
|
+
#
|
327
|
+
# @public Since *Async v2* and *Ruby v3.3.1* with `IO::Buffer` support.
|
328
|
+
# @asynchronous May be non-blocking.
|
329
|
+
#
|
330
|
+
# @parameter io [IO] The IO object to write to.
|
331
|
+
# @parameter buffer [IO::Buffer] The buffer to write from.
|
332
|
+
# @parameter length [Integer] The minimum number of bytes to write.
|
333
|
+
# @parameter offset [Integer] The offset within the buffer to write from.
|
246
334
|
def io_write(io, buffer, length, offset = 0)
|
247
335
|
fiber = Fiber.current
|
248
336
|
|
249
337
|
if timeout = get_timeout(io)
|
250
338
|
timer = @timers.after(timeout) do
|
251
|
-
fiber.raise(::IO::TimeoutError, "Timeout while waiting for IO to become writable!")
|
339
|
+
fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become writable!")
|
252
340
|
end
|
253
341
|
end
|
254
342
|
|
@@ -260,6 +348,10 @@ module Async
|
|
260
348
|
end
|
261
349
|
|
262
350
|
# Wait for the specified process ID to exit.
|
351
|
+
#
|
352
|
+
# @public Since *Async v2*.
|
353
|
+
# @asynchronous May be non-blocking.
|
354
|
+
#
|
263
355
|
# @parameter pid [Integer] The process ID to wait for.
|
264
356
|
# @parameter flags [Integer] A bit-mask of flags suitable for `Process::Status.wait`.
|
265
357
|
# @returns [Process::Status] A process status instance.
|
@@ -268,28 +360,13 @@ module Async
|
|
268
360
|
return @selector.process_wait(Fiber.current, pid, flags)
|
269
361
|
end
|
270
362
|
|
271
|
-
# Run one iteration of the event loop.
|
272
|
-
# Does not handle interrupts.
|
273
|
-
# @parameter timeout [Float | Nil] The maximum timeout, or if nil, indefinite.
|
274
|
-
# @returns [Boolean] Whether there is more work to do.
|
275
|
-
def run_once(timeout = nil)
|
276
|
-
Kernel::raise "Running scheduler on non-blocking fiber!" unless Fiber.blocking?
|
277
|
-
|
278
|
-
# If we are finished, we stop the task tree and exit:
|
279
|
-
if self.finished?
|
280
|
-
return false
|
281
|
-
end
|
282
|
-
|
283
|
-
return run_once!(timeout)
|
284
|
-
end
|
285
|
-
|
286
363
|
# Run one iteration of the event loop.
|
287
364
|
#
|
288
365
|
# When terminating the event loop, we already know we are finished. So we don't need to check the task tree. This is a logical requirement because `run_once` ignores transient tasks. For example, a single top level transient task is not enough to keep the reactor running, but during termination we must still process it in order to terminate child tasks.
|
289
366
|
#
|
290
367
|
# @parameter timeout [Float | Nil] The maximum timeout, or if nil, indefinite.
|
291
368
|
# @returns [Boolean] Whether there is more work to do.
|
292
|
-
private def run_once!(timeout =
|
369
|
+
private def run_once!(timeout = nil)
|
293
370
|
start_time = Async::Clock.now
|
294
371
|
|
295
372
|
interval = @timers.wait_interval
|
@@ -326,7 +403,30 @@ module Async
|
|
326
403
|
return true
|
327
404
|
end
|
328
405
|
|
406
|
+
# Run one iteration of the event loop.
|
407
|
+
#
|
408
|
+
# @public Since *Async v1*.
|
409
|
+
# @asynchronous Must be invoked from blocking (root) fiber.
|
410
|
+
#
|
411
|
+
# @parameter timeout [Float | Nil] The maximum timeout, or if nil, indefinite.
|
412
|
+
# @returns [Boolean] Whether there is more work to do.
|
413
|
+
def run_once(timeout = nil)
|
414
|
+
Kernel.raise "Running scheduler on non-blocking fiber!" unless Fiber.blocking?
|
415
|
+
|
416
|
+
if self.finished?
|
417
|
+
self.stop
|
418
|
+
end
|
419
|
+
|
420
|
+
# If we are finished, we stop the task tree and exit:
|
421
|
+
if @children.nil?
|
422
|
+
return false
|
423
|
+
end
|
424
|
+
|
425
|
+
return run_once!(timeout)
|
426
|
+
end
|
427
|
+
|
329
428
|
# Checks and clears the interrupted state of the scheduler.
|
429
|
+
#
|
330
430
|
# @returns [Boolean] Whether the reactor has been interrupted.
|
331
431
|
private def interrupted?
|
332
432
|
if @interrupted
|
@@ -341,26 +441,34 @@ module Async
|
|
341
441
|
return false
|
342
442
|
end
|
343
443
|
|
344
|
-
#
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
444
|
+
# Stop all children, including transient children.
|
445
|
+
#
|
446
|
+
# @public Since *Async v1*.
|
447
|
+
def stop
|
448
|
+
@children&.each do |child|
|
449
|
+
child.stop
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
private def run_loop(&block)
|
349
454
|
interrupt = nil
|
350
455
|
|
351
456
|
begin
|
352
457
|
# In theory, we could use Exception here to be a little bit safer, but we've only shown the case for SignalException to be a problem, so let's not over-engineer this.
|
353
458
|
Thread.handle_interrupt(::SignalException => :never) do
|
354
|
-
|
355
|
-
# If we are interrupted, we need to exit:
|
356
|
-
break if self.interrupted?
|
357
|
-
|
459
|
+
until self.interrupted?
|
358
460
|
# If we are finished, we need to exit:
|
359
|
-
break unless
|
461
|
+
break unless yield
|
360
462
|
end
|
361
463
|
end
|
362
464
|
rescue Interrupt => interrupt
|
465
|
+
# If an interrupt did occur during an iteration of the event loop, we need to handle it. More specifically, `self.stop` is not safe to interrupt without potentially corrupting the task tree.
|
363
466
|
Thread.handle_interrupt(::SignalException => :never) do
|
467
|
+
Console.debug(self) do |buffer|
|
468
|
+
buffer.puts "Scheduler interrupted: #{interrupt.inspect}"
|
469
|
+
self.print_hierarchy(buffer)
|
470
|
+
end
|
471
|
+
|
364
472
|
self.stop
|
365
473
|
end
|
366
474
|
|
@@ -368,37 +476,48 @@ module Async
|
|
368
476
|
end
|
369
477
|
|
370
478
|
# If the event loop was interrupted, and we finished exiting normally (due to the interrupt), we need to re-raise the interrupt so that the caller can handle it too.
|
371
|
-
|
479
|
+
if interrupt
|
480
|
+
Kernel.raise(interrupt)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
# Run the reactor until all tasks are finished. Proxies arguments to {#async} immediately before entering the loop, if a block is provided.
|
485
|
+
#
|
486
|
+
# Forwards all parameters to {#async} if a block is given.
|
487
|
+
#
|
488
|
+
# @public Since *Async v1*.
|
489
|
+
#
|
490
|
+
# @yields {|task| ...} The top level task, if a block is given.
|
491
|
+
# @returns [Task] The initial task that was scheduled into the reactor.
|
492
|
+
def run(...)
|
493
|
+
Kernel.raise ClosedError if @selector.nil?
|
494
|
+
|
495
|
+
initial_task = self.async(...) if block_given?
|
496
|
+
|
497
|
+
self.run_loop do
|
498
|
+
run_once
|
499
|
+
end
|
372
500
|
|
373
501
|
return initial_task
|
374
|
-
ensure
|
375
|
-
Console.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."}
|
376
502
|
end
|
377
503
|
|
378
|
-
# Start an asynchronous task within the specified reactor. The task will be
|
379
|
-
# executed until the first blocking call, at which point it will yield and
|
380
|
-
# and this method will return.
|
504
|
+
# Start an asynchronous task within the specified reactor. The task will be executed until the first blocking call, at which point it will yield and and this method will return.
|
381
505
|
#
|
382
|
-
#
|
506
|
+
# @public Since *Async v1*.
|
507
|
+
# @asynchronous May context switch immediately to new task.
|
508
|
+
# @deprecated Use {#run} or {Task#async} instead.
|
383
509
|
#
|
384
510
|
# @yields {|task| ...} Executed within the task.
|
385
511
|
# @returns [Task] The task that was scheduled into the reactor.
|
386
|
-
# @deprecated With no replacement.
|
387
512
|
def async(*arguments, **options, &block)
|
388
|
-
|
513
|
+
# warn "Async::Scheduler#async is deprecated. Use `run` or `Task#async` instead.", uplevel: 1, category: :deprecated
|
514
|
+
|
515
|
+
Kernel.raise ClosedError if @selector.nil?
|
389
516
|
|
390
517
|
task = Task.new(Task.current? || self, **options, &block)
|
391
518
|
|
392
|
-
# I want to take a moment to explain the logic of this.
|
393
|
-
# When calling an async block, we deterministically execute it until the
|
394
|
-
# first blocking operation. We don't *have* to do this - we could schedule
|
395
|
-
# it for later execution, but it's useful to:
|
396
|
-
# - Fail at the point of the method call where possible.
|
397
|
-
# - Execute determinstically where possible.
|
398
|
-
# - Avoid scheduler overhead if no blocking operation is performed.
|
399
519
|
task.run(*arguments)
|
400
520
|
|
401
|
-
# Console.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
|
402
521
|
return task
|
403
522
|
end
|
404
523
|
|
@@ -407,7 +526,14 @@ module Async
|
|
407
526
|
end
|
408
527
|
|
409
528
|
# Invoke the block, but after the specified timeout, raise {TimeoutError} in any currenly blocking operation. If the block runs to completion before the timeout occurs or there are no non-blocking operations after the timeout expires, the code will complete without any exception.
|
529
|
+
#
|
530
|
+
# @public Since *Async v1*.
|
531
|
+
# @asynchronous May raise an exception at any interruption point (e.g. blocking operations).
|
532
|
+
#
|
410
533
|
# @parameter duration [Numeric] The time in seconds, in which the task should complete.
|
534
|
+
# @parameter exception [Class] The exception class to raise.
|
535
|
+
# @parameter message [String] The message to pass to the exception.
|
536
|
+
# @yields {|duration| ...} The block to execute with a timeout.
|
411
537
|
def with_timeout(duration, exception = TimeoutError, message = "execution expired", &block)
|
412
538
|
fiber = Fiber.current
|
413
539
|
|
@@ -422,6 +548,15 @@ module Async
|
|
422
548
|
timer&.cancel!
|
423
549
|
end
|
424
550
|
|
551
|
+
# Invoke the block, but after the specified timeout, raise the specified exception with the given message. If the block runs to completion before the timeout occurs or there are no non-blocking operations after the timeout expires, the code will complete without any exception.
|
552
|
+
#
|
553
|
+
# @public Since *Async v1* and *Ruby v3.1*. May be invoked from `Timeout.timeout`.
|
554
|
+
# @asynchronous May raise an exception at any interruption point (e.g. blocking operations).
|
555
|
+
#
|
556
|
+
# @parameter duration [Numeric] The time in seconds, in which the task should complete.
|
557
|
+
# @parameter exception [Class] The exception class to raise.
|
558
|
+
# @parameter message [String] The message to pass to the exception.
|
559
|
+
# @yields {|duration| ...} The block to execute with a timeout.
|
425
560
|
def timeout_after(duration, exception, message, &block)
|
426
561
|
with_timeout(duration, exception, message) do |timer|
|
427
562
|
yield duration
|
data/lib/async/semaphore.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2018-
|
4
|
+
# Copyright, 2018-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "list"
|
7
7
|
|
8
8
|
module Async
|
9
9
|
# A synchronization primitive, which limits access to a given resource.
|
10
|
-
# @public Since
|
10
|
+
# @public Since *Async v1*.
|
11
11
|
class Semaphore
|
12
12
|
# @parameter limit [Integer] The maximum number of times the semaphore can be acquired before it blocks.
|
13
13
|
# @parameter parent [Task | Semaphore | Nil] The parent for holding any children tasks.
|