async 2.23.1 → 2.25.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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/clock.rb +1 -1
- data/lib/async/limited_queue.rb +1 -1
- data/lib/async/queue.rb +2 -1
- data/lib/async/scheduler.rb +80 -22
- data/lib/async/task.rb +2 -1
- data/lib/async/timeout.rb +88 -0
- data/lib/async/variable.rb +1 -1
- data/lib/async/version.rb +2 -2
- data/lib/metrics/provider/async/task.rb +1 -1
- data/lib/traces/provider/async/barrier.rb +1 -1
- data/lib/traces/provider/async/task.rb +1 -1
- data/license.md +6 -1
- data/readme.md +12 -0
- data/releases.md +60 -0
- data.tar.gz.sig +0 -0
- metadata +18 -14
- metadata.gz.sig +0 -0
- data/lib/async/worker_pool.rb +0 -182
- data/lib/async/wrapper.rb +0 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44c11aa8f74922b9bc2ccce792fd3c419af4a3a261f97f2f2202e3d75a5bf8f5
|
4
|
+
data.tar.gz: e2327b997b32a42e2ebfe9a4e4950d26a12ce698ce34a89d9fe1be94b0241b1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20961f535e5368753dffdb760d9bddf0fcf55e5910e5f6c71dba28ebb0405221d9328a8bfe5ef4e688ead71b4b5c4bde3949ce1edfa013b06af7db57adfe6d7f
|
7
|
+
data.tar.gz: d58c654852cd650978fa14fef7eaba00f4784137075960e5ad5b0b6b20ae3523912578b32b660f7d107df318cdd34945124e83231c4e574e336c5c4862a6c87c
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/clock.rb
CHANGED
data/lib/async/limited_queue.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright,
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
5
|
|
6
6
|
# The implementation lives in `queue.rb` but later we may move it here for better autoload/inference.
|
7
7
|
require_relative "queue"
|
data/lib/async/queue.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2018-
|
4
|
+
# Copyright, 2018-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by Ryan Musgrave.
|
6
6
|
# Copyright, 2020-2022, by Bruno Sutic.
|
7
|
+
# Copyright, 2025, by Jahfer Husain.
|
7
8
|
|
8
9
|
require_relative "notification"
|
9
10
|
|
data/lib/async/scheduler.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2020-
|
4
|
+
# Copyright, 2020-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2020, by Jun Jiang.
|
6
6
|
# Copyright, 2021, by Julien Portalier.
|
7
|
+
# Copyright, 2025, by Shopify Inc.
|
7
8
|
|
8
9
|
require_relative "clock"
|
9
10
|
require_relative "task"
|
10
|
-
require_relative "
|
11
|
+
require_relative "timeout"
|
11
12
|
|
12
13
|
require "io/event"
|
13
14
|
|
@@ -44,7 +45,29 @@ module Async
|
|
44
45
|
def self.supported?
|
45
46
|
true
|
46
47
|
end
|
48
|
+
|
49
|
+
# Used to augment the scheduler to add support for blocking operations.
|
50
|
+
module BlockingOperationWait
|
51
|
+
# Wait for the given work to be executed.
|
52
|
+
#
|
53
|
+
# @public Since *Async v2.21* and *Ruby v3.4*.
|
54
|
+
# @asynchronous May be non-blocking.
|
55
|
+
#
|
56
|
+
# @parameter work [Proc] The work to execute on a background thread.
|
57
|
+
# @returns [Object] The result of the work.
|
58
|
+
def blocking_operation_wait(work)
|
59
|
+
@worker_pool.call(work)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private_constant :BlockingOperationWait
|
47
64
|
|
65
|
+
if ::IO::Event.const_defined?(:WorkerPool)
|
66
|
+
WorkerPool = ::IO::Event::WorkerPool
|
67
|
+
else
|
68
|
+
WorkerPool = nil
|
69
|
+
end
|
70
|
+
|
48
71
|
# Create a new scheduler.
|
49
72
|
#
|
50
73
|
# @public Since *Async v1*.
|
@@ -64,14 +87,15 @@ module Async
|
|
64
87
|
@idle_time = 0.0
|
65
88
|
|
66
89
|
@timers = ::IO::Event::Timers.new
|
90
|
+
|
67
91
|
if worker_pool == true
|
68
|
-
@worker_pool = WorkerPool
|
92
|
+
@worker_pool = WorkerPool&.new
|
69
93
|
else
|
70
94
|
@worker_pool = worker_pool
|
71
95
|
end
|
72
|
-
|
96
|
+
|
73
97
|
if @worker_pool
|
74
|
-
self.singleton_class.prepend(
|
98
|
+
self.singleton_class.prepend(BlockingOperationWait)
|
75
99
|
end
|
76
100
|
end
|
77
101
|
|
@@ -233,7 +257,7 @@ module Async
|
|
233
257
|
# @parameter blocker [Object] The object that was blocking the fiber.
|
234
258
|
# @parameter fiber [Fiber] The fiber to unblock.
|
235
259
|
def unblock(blocker, fiber)
|
236
|
-
# $stderr.puts "unblock(#{blocker}, #{fiber})"
|
260
|
+
# Fiber.blocking{$stderr.puts "unblock(#{blocker}, #{fiber})"}
|
237
261
|
|
238
262
|
# This operation is protected by the GVL:
|
239
263
|
if selector = @selector
|
@@ -249,6 +273,8 @@ module Async
|
|
249
273
|
#
|
250
274
|
# @parameter duration [Numeric | Nil] The time in seconds to sleep, or if nil, indefinitely.
|
251
275
|
def kernel_sleep(duration = nil)
|
276
|
+
# Fiber.blocking{$stderr.puts "kernel_sleep(#{duration}, #{Fiber.current})"}
|
277
|
+
|
252
278
|
if duration
|
253
279
|
self.block(nil, duration)
|
254
280
|
else
|
@@ -269,16 +295,6 @@ module Async
|
|
269
295
|
::Resolv.getaddresses(hostname)
|
270
296
|
end
|
271
297
|
|
272
|
-
if IO.method_defined?(:timeout)
|
273
|
-
private def get_timeout(io)
|
274
|
-
io.timeout
|
275
|
-
end
|
276
|
-
else
|
277
|
-
private def get_timeout(io)
|
278
|
-
nil
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
298
|
# Wait for the specified IO to become ready for the specified events.
|
283
299
|
#
|
284
300
|
# @public Since *Async v2*.
|
@@ -295,7 +311,7 @@ module Async
|
|
295
311
|
timer = @timers.after(timeout) do
|
296
312
|
fiber.transfer
|
297
313
|
end
|
298
|
-
elsif timeout =
|
314
|
+
elsif timeout = io.timeout
|
299
315
|
# Otherwise, if we default to the io's timeout, we raise an exception:
|
300
316
|
timer = @timers.after(timeout) do
|
301
317
|
fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become ready!")
|
@@ -320,7 +336,7 @@ module Async
|
|
320
336
|
def io_read(io, buffer, length, offset = 0)
|
321
337
|
fiber = Fiber.current
|
322
338
|
|
323
|
-
if timeout =
|
339
|
+
if timeout = io.timeout
|
324
340
|
timer = @timers.after(timeout) do
|
325
341
|
fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become readable!")
|
326
342
|
end
|
@@ -344,7 +360,7 @@ module Async
|
|
344
360
|
def io_write(io, buffer, length, offset = 0)
|
345
361
|
fiber = Fiber.current
|
346
362
|
|
347
|
-
if timeout =
|
363
|
+
if timeout = io.timeout
|
348
364
|
timer = @timers.after(timeout) do
|
349
365
|
fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become writable!")
|
350
366
|
end
|
@@ -357,6 +373,34 @@ module Async
|
|
357
373
|
end
|
358
374
|
end
|
359
375
|
|
376
|
+
# Used to defer stopping the current task until later.
|
377
|
+
class FiberInterrupt
|
378
|
+
# Create a new stop later operation.
|
379
|
+
#
|
380
|
+
# @parameter task [Task] The task to stop later.
|
381
|
+
def initialize(fiber, exception)
|
382
|
+
@fiber = fiber
|
383
|
+
@exception = exception
|
384
|
+
end
|
385
|
+
|
386
|
+
# @returns [Boolean] Whether the task is alive.
|
387
|
+
def alive?
|
388
|
+
@fiber.alive?
|
389
|
+
end
|
390
|
+
|
391
|
+
# Transfer control to the operation - this will stop the task.
|
392
|
+
def transfer
|
393
|
+
# Fiber.blocking{$stderr.puts "FiberInterrupt#transfer(#{@fiber}, #{@exception})"}
|
394
|
+
@fiber.raise(@exception)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Raise an exception on the specified fiber, waking up the event loop if necessary.
|
399
|
+
def fiber_interrupt(fiber, exception)
|
400
|
+
# Fiber.blocking{$stderr.puts "fiber_interrupt(#{fiber}, #{exception})"}
|
401
|
+
unblock(nil, FiberInterrupt.new(fiber, exception))
|
402
|
+
end
|
403
|
+
|
360
404
|
# Wait for the specified process ID to exit.
|
361
405
|
#
|
362
406
|
# @public Since *Async v2*.
|
@@ -370,6 +414,16 @@ module Async
|
|
370
414
|
return @selector.process_wait(Fiber.current, pid, flags)
|
371
415
|
end
|
372
416
|
|
417
|
+
# Wait for the specified IOs to become ready for the specified events.
|
418
|
+
#
|
419
|
+
# @public Since *Async v2.25*.
|
420
|
+
# @asynchronous May be non-blocking.
|
421
|
+
def io_select(...)
|
422
|
+
Thread.new do
|
423
|
+
::IO.select(...)
|
424
|
+
end.value
|
425
|
+
end
|
426
|
+
|
373
427
|
# Run one iteration of the event loop.
|
374
428
|
#
|
375
429
|
# 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.
|
@@ -549,7 +603,7 @@ module Async
|
|
549
603
|
# @parameter duration [Numeric] The time in seconds, in which the task should complete.
|
550
604
|
# @parameter exception [Class] The exception class to raise.
|
551
605
|
# @parameter message [String] The message to pass to the exception.
|
552
|
-
# @yields {|
|
606
|
+
# @yields {|timeout| ...} The block to execute with a timeout.
|
553
607
|
def with_timeout(duration, exception = TimeoutError, message = "execution expired", &block)
|
554
608
|
fiber = Fiber.current
|
555
609
|
|
@@ -559,7 +613,11 @@ module Async
|
|
559
613
|
end
|
560
614
|
end
|
561
615
|
|
562
|
-
|
616
|
+
if block.arity.zero?
|
617
|
+
yield
|
618
|
+
else
|
619
|
+
yield Timeout.new(@timers, timer)
|
620
|
+
end
|
563
621
|
ensure
|
564
622
|
timer&.cancel!
|
565
623
|
end
|
@@ -574,7 +632,7 @@ module Async
|
|
574
632
|
# @parameter message [String] The message to pass to the exception.
|
575
633
|
# @yields {|duration| ...} The block to execute with a timeout.
|
576
634
|
def timeout_after(duration, exception, message, &block)
|
577
|
-
with_timeout(duration, exception, message) do
|
635
|
+
with_timeout(duration, exception, message) do
|
578
636
|
yield duration
|
579
637
|
end
|
580
638
|
end
|
data/lib/async/task.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2017-
|
4
|
+
# Copyright, 2017-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2017, by Kent Gruber.
|
6
6
|
# Copyright, 2017, by Devin Christensen.
|
7
7
|
# Copyright, 2020, by Patrik Wenger.
|
8
8
|
# Copyright, 2023, by Math Ieu.
|
9
|
+
# Copyright, 2025, by Shigeru Nakajima.
|
9
10
|
|
10
11
|
require "fiber"
|
11
12
|
require "console"
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
module Async
|
7
|
+
# Represents a flexible timeout that can be rescheduled or extended.
|
8
|
+
# @public Since *Async v2.24*.
|
9
|
+
class Timeout
|
10
|
+
# Initialize a new timeout.
|
11
|
+
def initialize(timers, handle)
|
12
|
+
@timers = timers
|
13
|
+
@handle = handle
|
14
|
+
end
|
15
|
+
|
16
|
+
# @returns [Numeric] The time remaining until the timeout occurs, in seconds.
|
17
|
+
def duration
|
18
|
+
@handle.time - @timers.now
|
19
|
+
end
|
20
|
+
|
21
|
+
# Update the duration of the timeout.
|
22
|
+
#
|
23
|
+
# The duration is relative to the current time, e.g. setting the duration to 5 means the timeout will occur in 5 seconds from now.
|
24
|
+
#
|
25
|
+
# @parameter value [Numeric] The new duration to assign to the timeout, in seconds.
|
26
|
+
def duration=(value)
|
27
|
+
self.reschedule(@timers.now + value)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adjust the timeout by the specified duration.
|
31
|
+
#
|
32
|
+
# The duration is relative to the timeout time, e.g. adjusting the timeout by 5 increases the current duration by 5 seconds.
|
33
|
+
#
|
34
|
+
# @parameter duration [Numeric] The duration to adjust the timeout by, in seconds.
|
35
|
+
# @returns [Numeric] The new time at which the timeout will occur.
|
36
|
+
def adjust(duration)
|
37
|
+
self.reschedule(time + duration)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @returns [Numeric] The time at which the timeout will occur, in seconds since {now}.
|
41
|
+
def time
|
42
|
+
@handle.time
|
43
|
+
end
|
44
|
+
|
45
|
+
# Assign a new time to the timeout, rescheduling it if necessary.
|
46
|
+
#
|
47
|
+
# @parameter value [Numeric] The new time to assign to the timeout.
|
48
|
+
# @returns [Numeric] The new time at which the timeout will occur.
|
49
|
+
def time=(value)
|
50
|
+
self.reschedule(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @returns [Numeric] The current time in the scheduler, relative to the time of this timeout, in seconds.
|
54
|
+
def now
|
55
|
+
@timers.now
|
56
|
+
end
|
57
|
+
|
58
|
+
# Cancel the timeout, preventing it from executing.
|
59
|
+
def cancel!
|
60
|
+
@handle.cancel!
|
61
|
+
end
|
62
|
+
|
63
|
+
# @returns [Boolean] Whether the timeout has been cancelled.
|
64
|
+
def cancelled?
|
65
|
+
@handle.cancelled?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Raised when attempting to reschedule a cancelled timeout.
|
69
|
+
class CancelledError < RuntimeError
|
70
|
+
end
|
71
|
+
|
72
|
+
# Reschedule the timeout to occur at the specified time.
|
73
|
+
#
|
74
|
+
# @parameter time [Numeric] The new time to schedule the timeout for.
|
75
|
+
# @returns [Numeric] The new time at which the timeout will occur.
|
76
|
+
private def reschedule(time)
|
77
|
+
if block = @handle&.block
|
78
|
+
@handle.cancel!
|
79
|
+
|
80
|
+
@handle = @timers.schedule(time, block)
|
81
|
+
|
82
|
+
return time
|
83
|
+
else
|
84
|
+
raise CancelledError, "Cannot reschedule a cancelled timeout!"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/async/variable.rb
CHANGED
data/lib/async/version.rb
CHANGED
data/license.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# MIT License
|
2
2
|
|
3
|
-
Copyright, 2017-
|
3
|
+
Copyright, 2017-2025, by Samuel Williams.
|
4
4
|
Copyright, 2017, by Kent Gruber.
|
5
5
|
Copyright, 2017, by Devin Christensen.
|
6
6
|
Copyright, 2018, by Sokolov Yura.
|
@@ -27,6 +27,11 @@ Copyright, 2023, by Emil Tin.
|
|
27
27
|
Copyright, 2023, by Gert Goet.
|
28
28
|
Copyright, 2024, by Dimitar Peychinov.
|
29
29
|
Copyright, 2024, by Jamie McCarthy.
|
30
|
+
Copyright, 2025, by Jahfer Husain.
|
31
|
+
Copyright, 2025, by Mark Montroy.
|
32
|
+
Copyright, 2025, by Shigeru Nakajima.
|
33
|
+
Copyright, 2025, by Alan Wu.
|
34
|
+
Copyright, 2025, by Shopify Inc.
|
30
35
|
|
31
36
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
32
37
|
of this software and associated documentation files (the "Software"), to deal
|
data/readme.md
CHANGED
@@ -35,6 +35,18 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
|
|
35
35
|
|
36
36
|
Please see the [project releases](https://socketry.github.io/async/releases/index) for all releases.
|
37
37
|
|
38
|
+
### v2.25.0
|
39
|
+
|
40
|
+
- Added support for `io_select` hook in the fiber scheduler, allowing non-blocking `IO.select` operations. This enables better integration with code that uses `IO.select` for multiplexing IO operations.
|
41
|
+
- [Use `IO::Event::WorkerPool` for Blocking Operations](https://socketry.github.io/async/releases/index#use-io::event::workerpool-for-blocking-operations)
|
42
|
+
- [Better handling of `IO#close` using `fiber_interrupt`](https://socketry.github.io/async/releases/index#better-handling-of-io#close-using-fiber_interrupt)
|
43
|
+
|
44
|
+
### v2.24.0
|
45
|
+
|
46
|
+
- Ruby v3.1 support is dropped.
|
47
|
+
- `Async::Wrapper` which was previously deprecated, is now removed.
|
48
|
+
- [Flexible Timeouts](https://socketry.github.io/async/releases/index#flexible-timeouts)
|
49
|
+
|
38
50
|
### v2.23.0
|
39
51
|
|
40
52
|
- Rename `ASYNC_SCHEDULER_DEFAULT_WORKER_POOL` to `ASYNC_SCHEDULER_WORKER_POOL`.
|
data/releases.md
CHANGED
@@ -1,5 +1,65 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v2.25.0
|
4
|
+
|
5
|
+
- Added support for `io_select` hook in the fiber scheduler, allowing non-blocking `IO.select` operations. This enables better integration with code that uses `IO.select` for multiplexing IO operations.
|
6
|
+
|
7
|
+
### Use `IO::Event::WorkerPool` for Blocking Operations
|
8
|
+
|
9
|
+
The `Async::WorkerPool` implementation has been removed in favor of using `IO::Event::WorkerPool` directly. This change simplifies the codebase by delegating worker pool functionality to the `io-event` gem, which provides a more efficient and well-tested implementation.
|
10
|
+
|
11
|
+
To enable the worker pool, you can set the `ASYNC_SCHEDULER_WORKER_POOL` environment variable to `true`. This will allow the scheduler to use a worker pool for blocking operations, which can help improve performance in applications that perform a lot of CPU-bound operations (e.g. `rb_nogvl`).
|
12
|
+
|
13
|
+
### Better handling of `IO#close` using `fiber_interrupt`
|
14
|
+
|
15
|
+
`IO#close` interrupts fibers that are waiting on the IO using the new `fiber_interrupt` hook introduced in Ruby 3.5/4.0. This means that if you close an IO while a fiber is waiting on it, the fiber will be interrupted and will raise an `IOError`. This is a change from previous versions of Ruby, where closing an IO would not interrupt fibers waiting on it, and would instead interrupt the entire event loop (essentially a bug).
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
r, w = IO.pipe
|
19
|
+
|
20
|
+
Async do
|
21
|
+
child = Async do
|
22
|
+
r.gets
|
23
|
+
end
|
24
|
+
|
25
|
+
r.close # This will interrupt the child fiber.
|
26
|
+
child.wait # This will raise an `IOError` because the IO was closed.
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
## v2.24.0
|
31
|
+
|
32
|
+
- Ruby v3.1 support is dropped.
|
33
|
+
- `Async::Wrapper` which was previously deprecated, is now removed.
|
34
|
+
|
35
|
+
### Flexible Timeouts
|
36
|
+
|
37
|
+
When {ruby Async::Scheduler\#with\_timeout} is invoked with a block, it can receive a {ruby Async::Timeout} instance. This allows you to adjust or cancel the timeout while the block is executing. This is useful for long-running tasks that may need to adjust their timeout based on external factors.
|
38
|
+
|
39
|
+
``` ruby
|
40
|
+
Async do
|
41
|
+
Async::Scheduler.with_timeout(5) do |timeout|
|
42
|
+
# Do some work that may take a while...
|
43
|
+
|
44
|
+
if some_condition
|
45
|
+
timeout.cancel! # Cancel the timeout
|
46
|
+
else
|
47
|
+
# Add 10 seconds to the current timeout:
|
48
|
+
timeout.adjust(10)
|
49
|
+
|
50
|
+
# Reduce the timeout by 10 seconds:
|
51
|
+
timeout.adjust(-10)
|
52
|
+
|
53
|
+
# Set the timeout to 10 seconds from now:
|
54
|
+
timeout.duration = 10
|
55
|
+
|
56
|
+
# Increase the current duration:
|
57
|
+
timeout.duration += 10
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
3
63
|
## v2.23.0
|
4
64
|
|
5
65
|
- Rename `ASYNC_SCHEDULER_DEFAULT_WORKER_POOL` to `ASYNC_SCHEDULER_WORKER_POOL`.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -13,21 +13,26 @@ authors:
|
|
13
13
|
- Emil Tin
|
14
14
|
- Jamie McCarthy
|
15
15
|
- Kent Gruber
|
16
|
+
- Alan Wu
|
16
17
|
- Brian Morearty
|
17
18
|
- Colin Kelley
|
18
19
|
- Dimitar Peychinov
|
19
20
|
- Gert Goet
|
21
|
+
- Jahfer Husain
|
20
22
|
- Jiang Jinyang
|
21
23
|
- Julien Portalier
|
22
24
|
- Jun Jiang
|
23
25
|
- Ken Muryoi
|
24
26
|
- Leon Löchner
|
27
|
+
- Mark Montroy
|
25
28
|
- Masafumi Okura
|
26
29
|
- Masayuki Yamamoto
|
27
30
|
- Math Ieu
|
28
31
|
- Ryan Musgrave
|
29
32
|
- Salim Semaoune
|
30
33
|
- Shannon Skipper
|
34
|
+
- Shigeru Nakajima
|
35
|
+
- Shopify Inc.
|
31
36
|
- Sokolov Yura
|
32
37
|
- Stefan Wrobel
|
33
38
|
- Trevor Turk
|
@@ -62,7 +67,7 @@ cert_chain:
|
|
62
67
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
63
68
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
64
69
|
-----END CERTIFICATE-----
|
65
|
-
date:
|
70
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
66
71
|
dependencies:
|
67
72
|
- !ruby/object:Gem::Dependency
|
68
73
|
name: console
|
@@ -98,42 +103,42 @@ dependencies:
|
|
98
103
|
requirements:
|
99
104
|
- - "~>"
|
100
105
|
- !ruby/object:Gem::Version
|
101
|
-
version: '1.
|
106
|
+
version: '1.11'
|
102
107
|
type: :runtime
|
103
108
|
prerelease: false
|
104
109
|
version_requirements: !ruby/object:Gem::Requirement
|
105
110
|
requirements:
|
106
111
|
- - "~>"
|
107
112
|
- !ruby/object:Gem::Version
|
108
|
-
version: '1.
|
113
|
+
version: '1.11'
|
109
114
|
- !ruby/object:Gem::Dependency
|
110
|
-
name:
|
115
|
+
name: metrics
|
111
116
|
requirement: !ruby/object:Gem::Requirement
|
112
117
|
requirements:
|
113
118
|
- - "~>"
|
114
119
|
- !ruby/object:Gem::Version
|
115
|
-
version: '0.
|
120
|
+
version: '0.12'
|
116
121
|
type: :runtime
|
117
122
|
prerelease: false
|
118
123
|
version_requirements: !ruby/object:Gem::Requirement
|
119
124
|
requirements:
|
120
125
|
- - "~>"
|
121
126
|
- !ruby/object:Gem::Version
|
122
|
-
version: '0.
|
127
|
+
version: '0.12'
|
123
128
|
- !ruby/object:Gem::Dependency
|
124
|
-
name:
|
129
|
+
name: traces
|
125
130
|
requirement: !ruby/object:Gem::Requirement
|
126
131
|
requirements:
|
127
132
|
- - "~>"
|
128
133
|
- !ruby/object:Gem::Version
|
129
|
-
version: '0.
|
134
|
+
version: '0.15'
|
130
135
|
type: :runtime
|
131
136
|
prerelease: false
|
132
137
|
version_requirements: !ruby/object:Gem::Requirement
|
133
138
|
requirements:
|
134
139
|
- - "~>"
|
135
140
|
- !ruby/object:Gem::Version
|
136
|
-
version: '0.
|
141
|
+
version: '0.15'
|
137
142
|
executables: []
|
138
143
|
extensions: []
|
139
144
|
extra_rdoc_files: []
|
@@ -157,12 +162,11 @@ files:
|
|
157
162
|
- lib/async/semaphore.rb
|
158
163
|
- lib/async/task.md
|
159
164
|
- lib/async/task.rb
|
165
|
+
- lib/async/timeout.rb
|
160
166
|
- lib/async/variable.rb
|
161
167
|
- lib/async/version.rb
|
162
168
|
- lib/async/waiter.md
|
163
169
|
- lib/async/waiter.rb
|
164
|
-
- lib/async/worker_pool.rb
|
165
|
-
- lib/async/wrapper.rb
|
166
170
|
- lib/kernel/async.rb
|
167
171
|
- lib/kernel/sync.rb
|
168
172
|
- lib/metrics/provider/async.rb
|
@@ -187,14 +191,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
187
191
|
requirements:
|
188
192
|
- - ">="
|
189
193
|
- !ruby/object:Gem::Version
|
190
|
-
version: '3.
|
194
|
+
version: '3.2'
|
191
195
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
196
|
requirements:
|
193
197
|
- - ">="
|
194
198
|
- !ruby/object:Gem::Version
|
195
199
|
version: '0'
|
196
200
|
requirements: []
|
197
|
-
rubygems_version: 3.6.
|
201
|
+
rubygems_version: 3.6.7
|
198
202
|
specification_version: 4
|
199
203
|
summary: A concurrency framework for Ruby.
|
200
204
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|
data/lib/async/worker_pool.rb
DELETED
@@ -1,182 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
5
|
-
|
6
|
-
require "etc"
|
7
|
-
|
8
|
-
module Async
|
9
|
-
# A simple work pool that offloads work to a background thread.
|
10
|
-
#
|
11
|
-
# @private
|
12
|
-
class WorkerPool
|
13
|
-
# Used to augment the scheduler to add support for blocking operations.
|
14
|
-
module BlockingOperationWait
|
15
|
-
# Wait for the given work to be executed.
|
16
|
-
#
|
17
|
-
# @public Since *Async v2.19* and *Ruby v3.4*.
|
18
|
-
# @asynchronous May be non-blocking.
|
19
|
-
#
|
20
|
-
# @parameter work [Proc] The work to execute on a background thread.
|
21
|
-
# @returns [Object] The result of the work.
|
22
|
-
def blocking_operation_wait(work)
|
23
|
-
@worker_pool.call(work)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Execute the given work in a background thread.
|
28
|
-
class Promise
|
29
|
-
# Create a new promise.
|
30
|
-
#
|
31
|
-
# @parameter work [Proc] The work to be done.
|
32
|
-
def initialize(work)
|
33
|
-
@work = work
|
34
|
-
@state = :pending
|
35
|
-
@value = nil
|
36
|
-
@guard = ::Mutex.new
|
37
|
-
@condition = ::ConditionVariable.new
|
38
|
-
@thread = nil
|
39
|
-
end
|
40
|
-
|
41
|
-
# Execute the work and resolve the promise.
|
42
|
-
def call
|
43
|
-
work = nil
|
44
|
-
|
45
|
-
@guard.synchronize do
|
46
|
-
@thread = ::Thread.current
|
47
|
-
|
48
|
-
return unless work = @work
|
49
|
-
end
|
50
|
-
|
51
|
-
resolve(work.call)
|
52
|
-
rescue Exception => error
|
53
|
-
reject(error)
|
54
|
-
end
|
55
|
-
|
56
|
-
private def resolve(value)
|
57
|
-
@guard.synchronize do
|
58
|
-
@work = nil
|
59
|
-
@thread = nil
|
60
|
-
@value = value
|
61
|
-
@state = :resolved
|
62
|
-
@condition.broadcast
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
private def reject(error)
|
67
|
-
@guard.synchronize do
|
68
|
-
@work = nil
|
69
|
-
@thread = nil
|
70
|
-
@value = error
|
71
|
-
@state = :failed
|
72
|
-
@condition.broadcast
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Cancel the work and raise an exception in the background thread.
|
77
|
-
def cancel
|
78
|
-
return unless @work
|
79
|
-
|
80
|
-
@guard.synchronize do
|
81
|
-
@work = nil
|
82
|
-
@state = :cancelled
|
83
|
-
@thread&.raise(Interrupt)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Wait for the work to be done.
|
88
|
-
#
|
89
|
-
# @returns [Object] The result of the work.
|
90
|
-
def wait
|
91
|
-
@guard.synchronize do
|
92
|
-
while @state == :pending
|
93
|
-
@condition.wait(@guard)
|
94
|
-
end
|
95
|
-
|
96
|
-
if @state == :failed
|
97
|
-
raise @value
|
98
|
-
else
|
99
|
-
return @value
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
# A background worker thread.
|
106
|
-
class Worker
|
107
|
-
# Create a new worker.
|
108
|
-
def initialize
|
109
|
-
@work = ::Thread::Queue.new
|
110
|
-
@thread = ::Thread.new(&method(:run))
|
111
|
-
end
|
112
|
-
|
113
|
-
# Execute work until the queue is closed.
|
114
|
-
def run
|
115
|
-
while work = @work.pop
|
116
|
-
work.call
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# Close the worker thread.
|
121
|
-
def close
|
122
|
-
if thread = @thread
|
123
|
-
@thread = nil
|
124
|
-
thread.kill
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
# Call the work and notify the scheduler when it is done.
|
129
|
-
def call(work)
|
130
|
-
promise = Promise.new(work)
|
131
|
-
|
132
|
-
@work.push(promise)
|
133
|
-
|
134
|
-
begin
|
135
|
-
return promise.wait
|
136
|
-
ensure
|
137
|
-
promise.cancel
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Create a new work pool.
|
143
|
-
#
|
144
|
-
# @parameter size [Integer] The number of threads to use.
|
145
|
-
def initialize(size: Etc.nprocessors)
|
146
|
-
@ready = ::Thread::Queue.new
|
147
|
-
|
148
|
-
size.times do
|
149
|
-
@ready.push(Worker.new)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# Close the work pool. Kills all outstanding work.
|
154
|
-
def close
|
155
|
-
if ready = @ready
|
156
|
-
@ready = nil
|
157
|
-
ready.close
|
158
|
-
|
159
|
-
while worker = ready.pop
|
160
|
-
worker.close
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
# Offload work to a thread.
|
166
|
-
#
|
167
|
-
# @parameter work [Proc] The work to be done.
|
168
|
-
def call(work)
|
169
|
-
if ready = @ready
|
170
|
-
worker = ready.pop
|
171
|
-
|
172
|
-
begin
|
173
|
-
worker.call(work)
|
174
|
-
ensure
|
175
|
-
ready.push(worker)
|
176
|
-
end
|
177
|
-
else
|
178
|
-
raise RuntimeError, "No worker available!"
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
data/lib/async/wrapper.rb
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2017-2024, by Samuel Williams.
|
5
|
-
# Copyright, 2017, by Kent Gruber.
|
6
|
-
|
7
|
-
warn "Async::Wrapper is deprecated and will be removed on 2025-03-31. Please use native interfaces instead.", uplevel: 1, category: :deprecated
|
8
|
-
|
9
|
-
module Async
|
10
|
-
# Represents an asynchronous IO within a reactor.
|
11
|
-
# @deprecated With no replacement. Prefer native interfaces.
|
12
|
-
class Wrapper
|
13
|
-
# An exception that occurs when the asynchronous operation was cancelled.
|
14
|
-
class Cancelled < StandardError
|
15
|
-
end
|
16
|
-
|
17
|
-
# @parameter io the native object to wrap.
|
18
|
-
# @parameter reactor [Reactor] the reactor that is managing this wrapper, or not specified, it's looked up by way of {Task.current}.
|
19
|
-
def initialize(io, reactor = nil)
|
20
|
-
@io = io
|
21
|
-
@reactor = reactor
|
22
|
-
|
23
|
-
@timeout = nil
|
24
|
-
end
|
25
|
-
|
26
|
-
attr_accessor :reactor
|
27
|
-
|
28
|
-
# Dup the underlying IO.
|
29
|
-
def dup
|
30
|
-
self.class.new(@io.dup)
|
31
|
-
end
|
32
|
-
|
33
|
-
# The underlying native `io`.
|
34
|
-
attr :io
|
35
|
-
|
36
|
-
# Wait for the io to become readable.
|
37
|
-
def wait_readable(timeout = @timeout)
|
38
|
-
@io.to_io.wait_readable(timeout) or raise TimeoutError
|
39
|
-
end
|
40
|
-
|
41
|
-
# Wait for the io to become writable.
|
42
|
-
def wait_priority(timeout = @timeout)
|
43
|
-
@io.to_io.wait_priority(timeout) or raise TimeoutError
|
44
|
-
end
|
45
|
-
|
46
|
-
# Wait for the io to become writable.
|
47
|
-
def wait_writable(timeout = @timeout)
|
48
|
-
@io.to_io.wait_writable(timeout) or raise TimeoutError
|
49
|
-
end
|
50
|
-
|
51
|
-
# Wait fo the io to become either readable or writable.
|
52
|
-
# @parameter duration [Float] timeout after the given duration if not `nil`.
|
53
|
-
def wait_any(timeout = @timeout)
|
54
|
-
@io.to_io.wait(::IO::READABLE|::IO::WRITABLE|::IO::PRIORITY, timeout) or raise TimeoutError
|
55
|
-
end
|
56
|
-
|
57
|
-
# Close the underlying IO.
|
58
|
-
def close
|
59
|
-
@io.close
|
60
|
-
end
|
61
|
-
|
62
|
-
# Whether the underlying IO is closed.
|
63
|
-
def closed?
|
64
|
-
@io.closed?
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|