async 2.24.0 → 2.26.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/agent.md +47 -0
- data/lib/async/barrier.md +0 -1
- data/lib/async/barrier.rb +30 -9
- data/lib/async/clock.rb +1 -1
- data/lib/async/condition.rb +2 -0
- data/lib/async/limited_queue.rb +7 -1
- data/lib/async/list.rb +9 -2
- data/lib/async/node.rb +1 -0
- data/lib/async/notification.rb +5 -3
- data/lib/async/queue.rb +51 -2
- data/lib/async/reactor.rb +2 -0
- data/lib/async/scheduler.rb +75 -7
- data/lib/async/task.rb +6 -1
- data/lib/async/variable.rb +1 -1
- data/lib/async/version.rb +2 -2
- data/lib/async/waiter.rb +3 -0
- 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 +18 -1
- data/releases.md +111 -2
- data.tar.gz.sig +0 -0
- metadata +18 -14
- metadata.gz.sig +0 -0
- data/lib/async/waiter.md +0 -50
- data/lib/async/worker_pool.rb +0 -182
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c593ddf06be9954e63b4f85eb8c0fe022be6cf1e241a37f86620a82fdf860b3
|
4
|
+
data.tar.gz: 023b1544a36d3bb8ff85f0b8cc3ccc0b4694ed3b0542ed7dafd02330f05ee663
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65c3fee1ef33362307c4c74dd2451384de1e1747041ffd68cec0049465a317dada93083198254b905e89758a112800894436fe4cc8a2c713b0c0f5de1a36402e
|
7
|
+
data.tar.gz: a90fa15643b4a491599e95429c0d79da4363d0778455a50893ee9d6f34217ebd442bf99085f981807e024dfec561f545ea86d74e06f2874b34c1bd191a1b79fa
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/agent.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Agent
|
2
|
+
|
3
|
+
## Context
|
4
|
+
|
5
|
+
This section provides links to documentation from installed packages. It is automatically generated and may be updated by running `bake agent:context:install`.
|
6
|
+
|
7
|
+
**Important:** Before performing any code, documentation, or analysis tasks, always read and apply the full content of any relevant documentation referenced in the following sections. These context files contain authoritative standards and best practices for documentation, code style, and project-specific workflows. **Do not proceed with any actions until you have read and incorporated the guidance from relevant context files.**
|
8
|
+
|
9
|
+
### agent-context
|
10
|
+
|
11
|
+
Install and manage context files from Ruby gems.
|
12
|
+
|
13
|
+
#### [Usage Guide](.context/agent-context/usage.md)
|
14
|
+
|
15
|
+
`agent-context` is a tool that helps you discover and install contextual information from Ruby gems for AI agents. Gems can provide additional documentation, examples, and guidance in a `context/` ...
|
16
|
+
|
17
|
+
### decode
|
18
|
+
|
19
|
+
Code analysis for documentation generation.
|
20
|
+
|
21
|
+
#### [Getting Started with Decode](.context/decode/getting-started.md)
|
22
|
+
|
23
|
+
The Decode gem provides programmatic access to Ruby code structure and metadata. It can parse Ruby files and extract definitions, comments, and documentation pragmas, enabling code analysis, docume...
|
24
|
+
|
25
|
+
#### [Documentation Coverage](.context/decode/coverage.md)
|
26
|
+
|
27
|
+
This guide explains how to test and monitor documentation coverage in your Ruby projects using the Decode gem's built-in bake tasks.
|
28
|
+
|
29
|
+
#### [Ruby Documentation](.context/decode/ruby-documentation.md)
|
30
|
+
|
31
|
+
This guide covers documentation practices and pragmas supported by the Decode gem for documenting Ruby code. These pragmas provide structured documentation that can be parsed and used to generate A...
|
32
|
+
|
33
|
+
### sus
|
34
|
+
|
35
|
+
A fast and scalable test runner.
|
36
|
+
|
37
|
+
#### [Using Sus Testing Framework](.context/sus/usage.md)
|
38
|
+
|
39
|
+
Sus is a modern Ruby testing framework that provides a clean, BDD-style syntax for writing tests. It's designed to be fast, simple, and expressive.
|
40
|
+
|
41
|
+
#### [Mocking](.context/sus/mocking.md)
|
42
|
+
|
43
|
+
There are two types of mocking in sus: `receive` and `mock`. The `receive` matcher is a subset of full mocking and is used to set expectations on method calls, while `mock` can be used to replace m...
|
44
|
+
|
45
|
+
#### [Shared Test Behaviors and Fixtures](.context/sus/shared.md)
|
46
|
+
|
47
|
+
Sus provides shared test contexts which can be used to define common behaviours or tests that can be reused across one or more test files.
|
data/lib/async/barrier.md
CHANGED
data/lib/async/barrier.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "list"
|
7
7
|
require_relative "task"
|
8
|
+
require_relative "queue"
|
8
9
|
|
9
10
|
module Async
|
10
11
|
# A general purpose synchronisation primitive, which allows one task to wait for a number of other tasks to complete. It can be used in conjunction with {Semaphore}.
|
@@ -16,6 +17,7 @@ module Async
|
|
16
17
|
# @public Since *Async v1*.
|
17
18
|
def initialize(parent: nil)
|
18
19
|
@tasks = List.new
|
20
|
+
@finished = Queue.new
|
19
21
|
|
20
22
|
@parent = parent
|
21
23
|
end
|
@@ -41,11 +43,15 @@ module Async
|
|
41
43
|
# Execute a child task and add it to the barrier.
|
42
44
|
# @asynchronous Executes the given block concurrently.
|
43
45
|
def async(*arguments, parent: (@parent or Task.current), **options, &block)
|
44
|
-
|
46
|
+
waiting = nil
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
parent.async(*arguments, **options) do |task, *arguments|
|
49
|
+
waiting = TaskNode.new(task)
|
50
|
+
@tasks.append(waiting)
|
51
|
+
block.call(task, *arguments)
|
52
|
+
ensure
|
53
|
+
@finished.signal(waiting)
|
54
|
+
end
|
49
55
|
end
|
50
56
|
|
51
57
|
# Whether there are any tasks being held by the barrier.
|
@@ -55,14 +61,27 @@ module Async
|
|
55
61
|
end
|
56
62
|
|
57
63
|
# Wait for all tasks to complete by invoking {Task#wait} on each waiting task, which may raise an error. As long as the task has completed, it will be removed from the barrier.
|
64
|
+
#
|
65
|
+
# @yields {|task| ...} If a block is given, the unwaited task is yielded. You must invoke {Task#wait} yourself. In addition, you may `break` if you have captured enough results.
|
66
|
+
#
|
58
67
|
# @asynchronous Will wait for tasks to finish executing.
|
59
68
|
def wait
|
60
|
-
|
69
|
+
while !@tasks.empty?
|
70
|
+
# Wait for a task to finish (we get the task node):
|
71
|
+
return unless waiting = @finished.wait
|
72
|
+
|
73
|
+
# Remove the task as it is now finishing:
|
74
|
+
@tasks.remove?(waiting)
|
75
|
+
|
76
|
+
# Get the task:
|
61
77
|
task = waiting.task
|
62
|
-
|
78
|
+
|
79
|
+
# If a block is given, the user can implement their own behaviour:
|
80
|
+
if block_given?
|
81
|
+
yield task
|
82
|
+
else
|
83
|
+
# Wait for it to either complete or raise an error:
|
63
84
|
task.wait
|
64
|
-
ensure
|
65
|
-
@tasks.remove?(waiting) unless task.alive?
|
66
85
|
end
|
67
86
|
end
|
68
87
|
end
|
@@ -73,6 +92,8 @@ module Async
|
|
73
92
|
@tasks.each do |waiting|
|
74
93
|
waiting.task.stop
|
75
94
|
end
|
95
|
+
|
96
|
+
@finished.close
|
76
97
|
end
|
77
98
|
end
|
78
99
|
end
|
data/lib/async/clock.rb
CHANGED
data/lib/async/condition.rb
CHANGED
data/lib/async/limited_queue.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
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"
|
8
|
+
|
9
|
+
module Async
|
10
|
+
class LimitedQueue < Queue
|
11
|
+
singleton_class.remove_method(:new)
|
12
|
+
end
|
13
|
+
end
|
data/lib/async/list.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2022-
|
4
|
+
# Copyright, 2022-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
module Async
|
7
7
|
# A general doublely linked list. This is used internally by {Async::Barrier} and {Async::Condition} to manage child tasks.
|
@@ -18,6 +18,7 @@ module Async
|
|
18
18
|
sprintf("#<%s:0x%x size=%d>", self.class.name, object_id, @size)
|
19
19
|
end
|
20
20
|
|
21
|
+
# @returns [String] A short summary of the list.
|
21
22
|
alias inspect to_s
|
22
23
|
|
23
24
|
# Fast, safe, unbounded accumulation of children.
|
@@ -134,7 +135,7 @@ module Async
|
|
134
135
|
return removed(node)
|
135
136
|
end
|
136
137
|
|
137
|
-
# @returns [Boolean]
|
138
|
+
# @returns [Boolean] True if the list is empty.
|
138
139
|
def empty?
|
139
140
|
@size == 0
|
140
141
|
end
|
@@ -238,6 +239,12 @@ module Async
|
|
238
239
|
attr_accessor :head
|
239
240
|
attr_accessor :tail
|
240
241
|
|
242
|
+
# @returns [String] A string representation of the node.
|
243
|
+
def to_s
|
244
|
+
sprintf("#<%s:0x%x>", self.class.name, object_id)
|
245
|
+
end
|
246
|
+
|
247
|
+
# @returns [String] A string representation of the node.
|
241
248
|
alias inspect to_s
|
242
249
|
end
|
243
250
|
|
data/lib/async/node.rb
CHANGED
data/lib/async/notification.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
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
|
|
6
6
|
require_relative "condition"
|
7
7
|
|
@@ -10,12 +10,14 @@ module Async
|
|
10
10
|
# @public Since *Async v1*.
|
11
11
|
class Notification < Condition
|
12
12
|
# Signal to a given task that it should resume operations.
|
13
|
+
#
|
14
|
+
# @returns [Boolean] if a task was signalled.
|
13
15
|
def signal(value = nil, task: Task.current)
|
14
|
-
return if @waiting.empty?
|
16
|
+
return false if @waiting.empty?
|
15
17
|
|
16
18
|
Fiber.scheduler.push Signal.new(self.exchange, value)
|
17
19
|
|
18
|
-
return
|
20
|
+
return true
|
19
21
|
end
|
20
22
|
|
21
23
|
Signal = Struct.new(:waiting, :value) do
|
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
|
|
@@ -14,16 +15,31 @@ module Async
|
|
14
15
|
#
|
15
16
|
# @public Since *Async v1*.
|
16
17
|
class Queue
|
18
|
+
# An error raised when trying to enqueue items to a closed queue.
|
19
|
+
# @public Since *Async v2.24*.
|
20
|
+
class ClosedError < RuntimeError
|
21
|
+
end
|
22
|
+
|
17
23
|
# Create a new queue.
|
18
24
|
#
|
19
25
|
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
|
20
26
|
# @parameter available [Notification] The notification to use for signaling when items are available.
|
21
27
|
def initialize(parent: nil, available: Notification.new)
|
22
28
|
@items = []
|
29
|
+
@closed = false
|
23
30
|
@parent = parent
|
24
31
|
@available = available
|
25
32
|
end
|
26
33
|
|
34
|
+
# Close the queue, causing all waiting tasks to return `nil`. Any subsequent calls to {enqueue} will raise an exception.
|
35
|
+
def close
|
36
|
+
@closed = true
|
37
|
+
|
38
|
+
while @available.waiting?
|
39
|
+
@available.signal(nil)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
27
43
|
# @attribute [Array] The items in the queue.
|
28
44
|
attr :items
|
29
45
|
|
@@ -39,6 +55,10 @@ module Async
|
|
39
55
|
|
40
56
|
# Add an item to the queue.
|
41
57
|
def push(item)
|
58
|
+
if @closed
|
59
|
+
raise ClosedError, "Cannot push items to a closed queue."
|
60
|
+
end
|
61
|
+
|
42
62
|
@items << item
|
43
63
|
|
44
64
|
@available.signal unless self.empty?
|
@@ -51,6 +71,10 @@ module Async
|
|
51
71
|
|
52
72
|
# Add multiple items to the queue.
|
53
73
|
def enqueue(*items)
|
74
|
+
if @closed
|
75
|
+
raise ClosedError, "Cannot enqueue items to a closed queue."
|
76
|
+
end
|
77
|
+
|
54
78
|
@items.concat(items)
|
55
79
|
|
56
80
|
@available.signal unless self.empty?
|
@@ -59,6 +83,10 @@ module Async
|
|
59
83
|
# Remove and return the next item from the queue.
|
60
84
|
def dequeue
|
61
85
|
while @items.empty?
|
86
|
+
if @closed
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
|
62
90
|
@available.wait
|
63
91
|
end
|
64
92
|
|
@@ -105,6 +133,13 @@ module Async
|
|
105
133
|
# A queue which limits the number of items that can be enqueued.
|
106
134
|
# @public Since *Async v1*.
|
107
135
|
class LimitedQueue < Queue
|
136
|
+
# @private This exists purely for emitting a warning.
|
137
|
+
def self.new(...)
|
138
|
+
warn("`require 'async/limited_queue'` to use `Async::LimitedQueue`.", uplevel: 1, category: :deprecated) if $VERBOSE
|
139
|
+
|
140
|
+
super
|
141
|
+
end
|
142
|
+
|
108
143
|
# Create a new limited queue.
|
109
144
|
#
|
110
145
|
# @parameter limit [Integer] The maximum number of items that can be enqueued.
|
@@ -119,9 +154,19 @@ module Async
|
|
119
154
|
# @attribute [Integer] The maximum number of items that can be enqueued.
|
120
155
|
attr :limit
|
121
156
|
|
157
|
+
# Close the queue, causing all waiting tasks to return `nil`. Any subsequent calls to {enqueue} will raise an exception.
|
158
|
+
# Also signals all tasks waiting for the queue to be full.
|
159
|
+
def close
|
160
|
+
super
|
161
|
+
|
162
|
+
while @full.waiting?
|
163
|
+
@full.signal(nil)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
122
167
|
# @returns [Boolean] Whether trying to enqueue an item would block.
|
123
168
|
def limited?
|
124
|
-
@items.size >= @limit
|
169
|
+
!@closed && @items.size >= @limit
|
125
170
|
end
|
126
171
|
|
127
172
|
# Add an item to the queue.
|
@@ -148,6 +193,10 @@ module Async
|
|
148
193
|
@full.wait
|
149
194
|
end
|
150
195
|
|
196
|
+
if @closed
|
197
|
+
raise ClosedError, "Cannot enqueue items to a closed queue."
|
198
|
+
end
|
199
|
+
|
151
200
|
available = @limit - @items.size
|
152
201
|
@items.concat(items.shift(available))
|
153
202
|
|
data/lib/async/reactor.rb
CHANGED
data/lib/async/scheduler.rb
CHANGED
@@ -1,14 +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
11
|
require_relative "timeout"
|
11
|
-
require_relative "worker_pool"
|
12
12
|
|
13
13
|
require "io/event"
|
14
14
|
|
@@ -45,7 +45,29 @@ module Async
|
|
45
45
|
def self.supported?
|
46
46
|
true
|
47
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
|
48
64
|
|
65
|
+
if ::IO::Event.const_defined?(:WorkerPool)
|
66
|
+
WorkerPool = ::IO::Event::WorkerPool
|
67
|
+
else
|
68
|
+
WorkerPool = nil
|
69
|
+
end
|
70
|
+
|
49
71
|
# Create a new scheduler.
|
50
72
|
#
|
51
73
|
# @public Since *Async v1*.
|
@@ -65,14 +87,15 @@ module Async
|
|
65
87
|
@idle_time = 0.0
|
66
88
|
|
67
89
|
@timers = ::IO::Event::Timers.new
|
90
|
+
|
68
91
|
if worker_pool == true
|
69
|
-
@worker_pool = WorkerPool
|
92
|
+
@worker_pool = WorkerPool&.new
|
70
93
|
else
|
71
94
|
@worker_pool = worker_pool
|
72
95
|
end
|
73
|
-
|
96
|
+
|
74
97
|
if @worker_pool
|
75
|
-
self.singleton_class.prepend(
|
98
|
+
self.singleton_class.prepend(BlockingOperationWait)
|
76
99
|
end
|
77
100
|
end
|
78
101
|
|
@@ -234,7 +257,7 @@ module Async
|
|
234
257
|
# @parameter blocker [Object] The object that was blocking the fiber.
|
235
258
|
# @parameter fiber [Fiber] The fiber to unblock.
|
236
259
|
def unblock(blocker, fiber)
|
237
|
-
# $stderr.puts "unblock(#{blocker}, #{fiber})"
|
260
|
+
# Fiber.blocking{$stderr.puts "unblock(#{blocker}, #{fiber})"}
|
238
261
|
|
239
262
|
# This operation is protected by the GVL:
|
240
263
|
if selector = @selector
|
@@ -250,6 +273,8 @@ module Async
|
|
250
273
|
#
|
251
274
|
# @parameter duration [Numeric | Nil] The time in seconds to sleep, or if nil, indefinitely.
|
252
275
|
def kernel_sleep(duration = nil)
|
276
|
+
# Fiber.blocking{$stderr.puts "kernel_sleep(#{duration}, #{Fiber.current})"}
|
277
|
+
|
253
278
|
if duration
|
254
279
|
self.block(nil, duration)
|
255
280
|
else
|
@@ -348,6 +373,34 @@ module Async
|
|
348
373
|
end
|
349
374
|
end
|
350
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
|
+
|
351
404
|
# Wait for the specified process ID to exit.
|
352
405
|
#
|
353
406
|
# @public Since *Async v2*.
|
@@ -361,6 +414,19 @@ module Async
|
|
361
414
|
return @selector.process_wait(Fiber.current, pid, flags)
|
362
415
|
end
|
363
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
|
+
# Don't make unnecessary output, since we will propagate the exception:
|
424
|
+
Thread.current.report_on_exception = false
|
425
|
+
|
426
|
+
::IO.select(...)
|
427
|
+
end.value
|
428
|
+
end
|
429
|
+
|
364
430
|
# Run one iteration of the event loop.
|
365
431
|
#
|
366
432
|
# 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.
|
@@ -517,7 +583,7 @@ module Async
|
|
517
583
|
# @yields {|task| ...} Executed within the task.
|
518
584
|
# @returns [Task] The task that was scheduled into the reactor.
|
519
585
|
def async(*arguments, **options, &block)
|
520
|
-
|
586
|
+
warn("Async::Scheduler#async is deprecated. Use `run` or `Task#async` instead.", uplevel: 1, category: :deprecated) if $VERBOSE
|
521
587
|
|
522
588
|
Kernel.raise ClosedError if @selector.nil?
|
523
589
|
|
@@ -528,6 +594,8 @@ module Async
|
|
528
594
|
return task
|
529
595
|
end
|
530
596
|
|
597
|
+
# Create a new fiber and return it without starting execution.
|
598
|
+
# @returns [Fiber] The fiber that was created.
|
531
599
|
def fiber(...)
|
532
600
|
return async(...).fiber
|
533
601
|
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"
|
@@ -64,6 +65,8 @@ module Async
|
|
64
65
|
|
65
66
|
# @deprecated With no replacement.
|
66
67
|
def self.yield
|
68
|
+
warn("`Async::Task.yield` is deprecated with no replacement.", uplevel: 1, category: :deprecated) if $VERBOSE
|
69
|
+
|
67
70
|
Fiber.scheduler.transfer
|
68
71
|
end
|
69
72
|
|
@@ -133,6 +136,8 @@ module Async
|
|
133
136
|
|
134
137
|
# @deprecated Prefer {Kernel#sleep} except when compatibility with `stable-v1` is required.
|
135
138
|
def sleep(duration = nil)
|
139
|
+
Kernel.warn("`Async::Task#sleep` is deprecated, use `Kernel#sleep` instead.", uplevel: 1, category: :deprecated) if $VERBOSE
|
140
|
+
|
136
141
|
super
|
137
142
|
end
|
138
143
|
|
data/lib/async/variable.rb
CHANGED
data/lib/async/version.rb
CHANGED
data/lib/async/waiter.rb
CHANGED
@@ -6,12 +6,15 @@
|
|
6
6
|
|
7
7
|
module Async
|
8
8
|
# A composable synchronization primitive, which allows one task to wait for a number of other tasks to complete. It can be used in conjunction with {Semaphore} and/or {Barrier}.
|
9
|
+
# @deprecated `Async::Waiter` is deprecated, use `Async::Barrier` instead.
|
9
10
|
class Waiter
|
10
11
|
# Create a waiter instance.
|
11
12
|
#
|
12
13
|
# @parameter parent [Interface(:async) | Nil] The parent task to use for asynchronous operations.
|
13
14
|
# @parameter finished [Async::Condition] The condition to signal when a task completes.
|
14
15
|
def initialize(parent: nil, finished: Async::Condition.new)
|
16
|
+
warn("`Async::Waiter` is deprecated, use `Async::Barrier` instead.", uplevel: 1, category: :deprecated) if $VERBOSE
|
17
|
+
|
15
18
|
@finished = finished
|
16
19
|
@done = []
|
17
20
|
|
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,23 @@ 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.26.0
|
39
|
+
|
40
|
+
- `Async::Notification#signal` now returns `true` if a task was signaled, `false` otherwise, providing better feedback for notification operations.
|
41
|
+
- `require "async/limited_queue"` is required to use `Async::LimitedQueue` without a deprecation warning. `Async::LimitedQueue` is not deprecated, but it's usage via `async/queue` is deprecated.
|
42
|
+
- `Async::Task#sleep` is deprecated with no replacement.
|
43
|
+
- `Async::Task.yield` is deprecated with no replacement.
|
44
|
+
- `Async::Scheduler#async` is deprecated, use `Async{}`, `Sync{}` or `Async::Task#async` instead.
|
45
|
+
- Agent context is now available, via the [`agent-context` gem](https://github.com/ioquatix/agent-context).
|
46
|
+
- [`Async::Barrier` Improvements](https://socketry.github.io/async/releases/index#async::barrier-improvements)
|
47
|
+
- [Introduce `Async::Queue#close`](https://socketry.github.io/async/releases/index#introduce-async::queue#close)
|
48
|
+
|
49
|
+
### v2.25.0
|
50
|
+
|
51
|
+
- 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.
|
52
|
+
- [Use `IO::Event::WorkerPool` for Blocking Operations](https://socketry.github.io/async/releases/index#use-io::event::workerpool-for-blocking-operations)
|
53
|
+
- [Better handling of `IO#close` using `fiber_interrupt`](https://socketry.github.io/async/releases/index#better-handling-of-io#close-using-fiber_interrupt)
|
54
|
+
|
38
55
|
### v2.24.0
|
39
56
|
|
40
57
|
- Ruby v3.1 support is dropped.
|
@@ -56,7 +73,7 @@ Please see the [project releases](https://socketry.github.io/async/releases/inde
|
|
56
73
|
|
57
74
|
### v2.19.0
|
58
75
|
|
59
|
-
- [Async::Scheduler Debugging](https://socketry.github.io/async/releases/index#async::scheduler-debugging)
|
76
|
+
- [`Async::Scheduler` Debugging](https://socketry.github.io/async/releases/index#async::scheduler-debugging)
|
60
77
|
- [Console Shims](https://socketry.github.io/async/releases/index#console-shims)
|
61
78
|
|
62
79
|
### v2.18.0
|
data/releases.md
CHANGED
@@ -1,5 +1,114 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v2.26.0
|
4
|
+
|
5
|
+
- `Async::Notification#signal` now returns `true` if a task was signaled, `false` otherwise, providing better feedback for notification operations.
|
6
|
+
- `require "async/limited_queue"` is required to use `Async::LimitedQueue` without a deprecation warning. `Async::LimitedQueue` is not deprecated, but it's usage via `async/queue` is deprecated.
|
7
|
+
- `Async::Task#sleep` is deprecated with no replacement.
|
8
|
+
- `Async::Task.yield` is deprecated with no replacement.
|
9
|
+
- `Async::Scheduler#async` is deprecated, use `Async{}`, `Sync{}` or `Async::Task#async` instead.
|
10
|
+
- Agent context is now available, via the [`agent-context` gem](https://github.com/ioquatix/agent-context).
|
11
|
+
|
12
|
+
### `Async::Barrier` Improvements
|
13
|
+
|
14
|
+
`Async::Barrier` now provides more flexible and predictable behavior for waiting on task completion:
|
15
|
+
|
16
|
+
- **Completion-order waiting**: `barrier.wait` now processes tasks in the order they complete rather than the order they were created. This provides more predictable behavior when tasks have different execution times.
|
17
|
+
- **Block-based waiting**: `barrier.wait` now accepts an optional block that yields each task as it completes, allowing for custom handling of individual tasks:
|
18
|
+
|
19
|
+
<!-- end list -->
|
20
|
+
|
21
|
+
``` ruby
|
22
|
+
barrier = Async::Barrier.new
|
23
|
+
|
24
|
+
# Start several tasks
|
25
|
+
3.times do |i|
|
26
|
+
barrier.async do |task|
|
27
|
+
sleep(rand * 0.1) # Random completion time
|
28
|
+
"result_#{i}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Wait for all tasks, processing them as they complete
|
33
|
+
barrier.wait do |task|
|
34
|
+
result = task.wait
|
35
|
+
puts "Task completed with: #{result}"
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
- **Partial completion support**: The new block-based interface allows you to wait for only the first N tasks to complete:
|
40
|
+
|
41
|
+
<!-- end list -->
|
42
|
+
|
43
|
+
``` ruby
|
44
|
+
# Wait for only the first 3 tasks to complete
|
45
|
+
count = 0
|
46
|
+
barrier.wait do |task|
|
47
|
+
task.wait
|
48
|
+
count += 1
|
49
|
+
break if count >= 3
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
This makes `Async::Barrier` a superset of `Async::Waiter` functionality, providing more flexible task coordination patterns, and therrefore, `Async::Waiter` is now deprecated.
|
54
|
+
|
55
|
+
### Introduce `Async::Queue#close`
|
56
|
+
|
57
|
+
`Async::Queue` and `Async::LimitedQueue` can now be closed, which provides better resource management and error handling:
|
58
|
+
|
59
|
+
- **New `close` method**: Both queue types now have a `close` method that prevents further items from being added and signals any waiting tasks.
|
60
|
+
- **Consistent error handling**: All queue modification methods (`push`, `enqueue`, `<<`) now raise `Async::Queue::ClosedError` when called on a closed queue.
|
61
|
+
- **Waiting task signaling**: When a queue is closed, any tasks waiting on `dequeue` (for regular queues) or `enqueue` (for limited queues) are properly signaled and can complete.
|
62
|
+
|
63
|
+
<!-- end list -->
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
queue = Async::Queue.new
|
67
|
+
|
68
|
+
# Start a task waiting for items:
|
69
|
+
waiting_task = Async do
|
70
|
+
queue.dequeue
|
71
|
+
end
|
72
|
+
|
73
|
+
# Close the queue - this signals the waiting task
|
74
|
+
queue.close
|
75
|
+
|
76
|
+
# These will raise Async::Queue::ClosedError
|
77
|
+
queue.push(:item) # => raises ClosedError
|
78
|
+
queue.enqueue(:item) # => raises ClosedError
|
79
|
+
queue << :item # => raises ClosedError
|
80
|
+
|
81
|
+
# Dequeue returns nil when closed and empty
|
82
|
+
queue.dequeue # => nil
|
83
|
+
```
|
84
|
+
|
85
|
+
## v2.25.0
|
86
|
+
|
87
|
+
- 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.
|
88
|
+
|
89
|
+
### Use `IO::Event::WorkerPool` for Blocking Operations
|
90
|
+
|
91
|
+
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.
|
92
|
+
|
93
|
+
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`).
|
94
|
+
|
95
|
+
### Better handling of `IO#close` using `fiber_interrupt`
|
96
|
+
|
97
|
+
`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).
|
98
|
+
|
99
|
+
``` ruby
|
100
|
+
r, w = IO.pipe
|
101
|
+
|
102
|
+
Async do
|
103
|
+
child = Async do
|
104
|
+
r.gets
|
105
|
+
end
|
106
|
+
|
107
|
+
r.close # This will interrupt the child fiber.
|
108
|
+
child.wait # This will raise an `IOError` because the IO was closed.
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
3
112
|
## v2.24.0
|
4
113
|
|
5
114
|
- Ruby v3.1 support is dropped.
|
@@ -7,7 +116,7 @@
|
|
7
116
|
|
8
117
|
### Flexible Timeouts
|
9
118
|
|
10
|
-
When
|
119
|
+
When `Async::Scheduler#with_timeout` is invoked with a block, it can receive a `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.
|
11
120
|
|
12
121
|
``` ruby
|
13
122
|
Async do
|
@@ -81,7 +190,7 @@ To take advantage of this feature, you will need to introduce your own `config/t
|
|
81
190
|
|
82
191
|
## v2.19.0
|
83
192
|
|
84
|
-
### Async::Scheduler Debugging
|
193
|
+
### `Async::Scheduler` Debugging
|
85
194
|
|
86
195
|
Occasionally on issues, I encounter people asking for help and I need more information. Pressing Ctrl-C to exit a hung program is common, but it usually doesn't provide enough information to diagnose the problem. Setting the `CONSOLE_LEVEL=debug` environment variable will now print additional information about the scheduler when you interrupt it, including a backtrace of the current tasks.
|
87
196
|
|
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.26.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -10,24 +10,29 @@ authors:
|
|
10
10
|
- Olle Jonsson
|
11
11
|
- Patrik Wenger
|
12
12
|
- Devin Christensen
|
13
|
+
- Shopify Inc.
|
13
14
|
- Emil Tin
|
14
15
|
- Jamie McCarthy
|
15
16
|
- Kent Gruber
|
17
|
+
- Alan Wu
|
16
18
|
- Brian Morearty
|
17
19
|
- Colin Kelley
|
18
20
|
- Dimitar Peychinov
|
19
21
|
- Gert Goet
|
22
|
+
- Jahfer Husain
|
20
23
|
- Jiang Jinyang
|
21
24
|
- Julien Portalier
|
22
25
|
- Jun Jiang
|
23
26
|
- Ken Muryoi
|
24
27
|
- Leon Löchner
|
28
|
+
- Mark Montroy
|
25
29
|
- Masafumi Okura
|
26
30
|
- Masayuki Yamamoto
|
27
31
|
- Math Ieu
|
28
32
|
- Ryan Musgrave
|
29
33
|
- Salim Semaoune
|
30
34
|
- Shannon Skipper
|
35
|
+
- Shigeru Nakajima
|
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,46 +103,47 @@ dependencies:
|
|
98
103
|
requirements:
|
99
104
|
- - "~>"
|
100
105
|
- !ruby/object:Gem::Version
|
101
|
-
version: '1.
|
106
|
+
version: '1.12'
|
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.12'
|
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: []
|
140
145
|
files:
|
146
|
+
- agent.md
|
141
147
|
- lib/async.rb
|
142
148
|
- lib/async/barrier.md
|
143
149
|
- lib/async/barrier.rb
|
@@ -160,9 +166,7 @@ files:
|
|
160
166
|
- lib/async/timeout.rb
|
161
167
|
- lib/async/variable.rb
|
162
168
|
- lib/async/version.rb
|
163
|
-
- lib/async/waiter.md
|
164
169
|
- lib/async/waiter.rb
|
165
|
-
- lib/async/worker_pool.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/waiter.md
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
A synchronization primitive, which allows you to wait for tasks to complete in order of completion. This is useful for implementing a task pool, where you want to wait for the first task to complete, and then cancel the rest.
|
2
|
-
|
3
|
-
If you try to wait for more things than you have added, you will deadlock.
|
4
|
-
|
5
|
-
## Example
|
6
|
-
|
7
|
-
~~~ ruby
|
8
|
-
require 'async'
|
9
|
-
require 'async/semaphore'
|
10
|
-
require 'async/barrier'
|
11
|
-
require 'async/waiter'
|
12
|
-
|
13
|
-
Sync do
|
14
|
-
barrier = Async::Barrier.new
|
15
|
-
waiter = Async::Waiter.new(parent: barrier)
|
16
|
-
semaphore = Async::Semaphore.new(2, parent: waiter)
|
17
|
-
|
18
|
-
# Sleep sort the numbers:
|
19
|
-
generator = Async do
|
20
|
-
while true
|
21
|
-
semaphore.async do |task|
|
22
|
-
number = rand(1..10)
|
23
|
-
sleep(number)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
numbers = []
|
29
|
-
|
30
|
-
4.times do
|
31
|
-
# Wait for all the numbers to be sorted:
|
32
|
-
numbers << waiter.wait
|
33
|
-
end
|
34
|
-
|
35
|
-
# Don't generate any more numbers:
|
36
|
-
generator.stop
|
37
|
-
|
38
|
-
# Stop all tasks which we don't care about:
|
39
|
-
barrier.stop
|
40
|
-
|
41
|
-
Console.info("Smallest", numbers)
|
42
|
-
end
|
43
|
-
~~~
|
44
|
-
|
45
|
-
### Output
|
46
|
-
|
47
|
-
~~~
|
48
|
-
0.0s info: Smallest
|
49
|
-
| [3, 3, 1, 2]
|
50
|
-
~~~
|
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.21* 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
|