async 2.26.0 → 2.27.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/condition.rb +1 -1
- data/lib/async/list.rb +7 -6
- data/lib/async/node.rb +1 -0
- data/lib/async/queue.rb +1 -0
- data/lib/async/reactor.rb +1 -1
- data/lib/async/scheduler.rb +5 -5
- data/lib/async/stop.rb +82 -0
- data/lib/async/task.rb +15 -31
- data/lib/async/version.rb +1 -1
- data/lib/async/waiter.rb +1 -1
- data/readme.md +5 -4
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +5 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c9d36d758f8a197b7c00d3d2e4d83d280cfd8ddf4fb60bba1d7b9e95b64ec47
|
4
|
+
data.tar.gz: c0c19dcc509563b18a9385c1ff07a982054e6e984c9ed9c1fd715ee027c74f09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b936e5c17d8e9e2eec7f3d9b3d2d4b00b5a16359cfb267a036f72fb254b53aeb234f8b9368ba1cd2ba41010438dd8da120621005bc1791fc554ee62647e46ed1
|
7
|
+
data.tar.gz: 808b0ce51e2bfa4a28d01126d59627409e6a25b37ee8e9e1f8146c138fcef995ab693e699f9c3ece326e234100a397c19e917a55c9d1e34dd797d26531e411f5
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/condition.rb
CHANGED
data/lib/async/list.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2022-2025, by Samuel Williams.
|
5
|
+
# Copyright, 2025, by Shopify Inc.
|
5
6
|
|
6
7
|
module Async
|
7
8
|
# A general doublely linked list. This is used internally by {Async::Barrier} and {Async::Condition} to manage child tasks.
|
@@ -144,26 +145,26 @@ module Async
|
|
144
145
|
# previous = self
|
145
146
|
# current = @tail
|
146
147
|
# found = node.equal?(self)
|
147
|
-
|
148
|
+
|
148
149
|
# while true
|
149
150
|
# break if current.equal?(self)
|
150
|
-
|
151
|
+
|
151
152
|
# if current.head != previous
|
152
153
|
# raise "Invalid previous linked list node!"
|
153
154
|
# end
|
154
|
-
|
155
|
+
|
155
156
|
# if current.is_a?(List) and !current.equal?(self)
|
156
157
|
# raise "Invalid list in list node!"
|
157
158
|
# end
|
158
|
-
|
159
|
+
|
159
160
|
# if node
|
160
161
|
# found ||= current.equal?(node)
|
161
162
|
# end
|
162
|
-
|
163
|
+
|
163
164
|
# previous = current
|
164
165
|
# current = current.tail
|
165
166
|
# end
|
166
|
-
|
167
|
+
|
167
168
|
# if node and !found
|
168
169
|
# raise "Node not found in list!"
|
169
170
|
# end
|
data/lib/async/node.rb
CHANGED
data/lib/async/queue.rb
CHANGED
data/lib/async/reactor.rb
CHANGED
data/lib/async/scheduler.rb
CHANGED
@@ -45,7 +45,7 @@ module Async
|
|
45
45
|
def self.supported?
|
46
46
|
true
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
# Used to augment the scheduler to add support for blocking operations.
|
50
50
|
module BlockingOperationWait
|
51
51
|
# Wait for the given work to be executed.
|
@@ -59,7 +59,7 @@ module Async
|
|
59
59
|
@worker_pool.call(work)
|
60
60
|
end
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
private_constant :BlockingOperationWait
|
64
64
|
|
65
65
|
if ::IO::Event.const_defined?(:WorkerPool)
|
@@ -67,7 +67,7 @@ module Async
|
|
67
67
|
else
|
68
68
|
WorkerPool = nil
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
# Create a new scheduler.
|
72
72
|
#
|
73
73
|
# @public Since *Async v1*.
|
@@ -93,7 +93,7 @@ module Async
|
|
93
93
|
else
|
94
94
|
@worker_pool = worker_pool
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
if @worker_pool
|
98
98
|
self.singleton_class.prepend(BlockingOperationWait)
|
99
99
|
end
|
@@ -120,7 +120,7 @@ module Async
|
|
120
120
|
return @busy_time / total_time
|
121
121
|
end
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
# Invoked when the fiber scheduler is being closed.
|
125
125
|
#
|
126
126
|
# Executes the run loop until all tasks are finished, then closes the scheduler.
|
data/lib/async/stop.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require "fiber"
|
7
|
+
require "console"
|
8
|
+
|
9
|
+
module Async
|
10
|
+
# Raised when a task is explicitly stopped.
|
11
|
+
class Stop < Exception
|
12
|
+
# Represents the source of the stop operation.
|
13
|
+
class Cause < Exception
|
14
|
+
if RUBY_VERSION >= "3.4"
|
15
|
+
# @returns [Array(Thread::Backtrace::Location)] The backtrace of the caller.
|
16
|
+
def self.backtrace
|
17
|
+
caller_locations(2..-1)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
# @returns [Array(String)] The backtrace of the caller.
|
21
|
+
def self.backtrace
|
22
|
+
caller(2..-1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create a new cause of the stop operation, with the given message.
|
27
|
+
#
|
28
|
+
# @parameter message [String] The error message.
|
29
|
+
# @returns [Cause] The cause of the stop operation.
|
30
|
+
def self.for(message = "Task was stopped")
|
31
|
+
instance = self.new(message)
|
32
|
+
instance.set_backtrace(self.backtrace)
|
33
|
+
return instance
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if RUBY_VERSION < "3.5"
|
38
|
+
# Create a new stop operation.
|
39
|
+
#
|
40
|
+
# This is a compatibility method for Ruby versions before 3.5 where cause is not propagated correctly when using {Fiber#raise}
|
41
|
+
#
|
42
|
+
# @parameter message [String | Hash] The error message or a hash containing the cause.
|
43
|
+
def initialize(message = "Task was stopped")
|
44
|
+
if message.is_a?(Hash)
|
45
|
+
@cause = message[:cause]
|
46
|
+
message = "Task was stopped"
|
47
|
+
end
|
48
|
+
|
49
|
+
super(message)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @returns [Exception] The cause of the stop operation.
|
53
|
+
#
|
54
|
+
# This is a compatibility method for Ruby versions before 3.5 where cause is not propagated correctly when using {Fiber#raise}, we explicitly capture the cause here.
|
55
|
+
def cause
|
56
|
+
super || @cause
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Used to defer stopping the current task until later.
|
61
|
+
class Later
|
62
|
+
# Create a new stop later operation.
|
63
|
+
#
|
64
|
+
# @parameter task [Task] The task to stop later.
|
65
|
+
# @parameter cause [Exception] The cause of the stop operation.
|
66
|
+
def initialize(task, cause = nil)
|
67
|
+
@task = task
|
68
|
+
@cause = cause
|
69
|
+
end
|
70
|
+
|
71
|
+
# @returns [Boolean] Whether the task is alive.
|
72
|
+
def alive?
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
# Transfer control to the operation - this will stop the task.
|
77
|
+
def transfer
|
78
|
+
@task.stop(false, cause: @cause)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/async/task.rb
CHANGED
@@ -13,33 +13,11 @@ require "console"
|
|
13
13
|
|
14
14
|
require_relative "node"
|
15
15
|
require_relative "condition"
|
16
|
+
require_relative "stop"
|
16
17
|
|
17
18
|
Fiber.attr_accessor :async_task
|
18
19
|
|
19
20
|
module Async
|
20
|
-
# Raised when a task is explicitly stopped.
|
21
|
-
class Stop < Exception
|
22
|
-
# Used to defer stopping the current task until later.
|
23
|
-
class Later
|
24
|
-
# Create a new stop later operation.
|
25
|
-
#
|
26
|
-
# @parameter task [Task] The task to stop later.
|
27
|
-
def initialize(task)
|
28
|
-
@task = task
|
29
|
-
end
|
30
|
-
|
31
|
-
# @returns [Boolean] Whether the task is alive.
|
32
|
-
def alive?
|
33
|
-
true
|
34
|
-
end
|
35
|
-
|
36
|
-
# Transfer control to the operation - this will stop the task.
|
37
|
-
def transfer
|
38
|
-
@task.stop
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
21
|
# Raised if a timeout occurs on a specific Fiber. Handled gracefully by `Task`.
|
44
22
|
# @public Since *Async v1*.
|
45
23
|
class TimeoutError < StandardError
|
@@ -271,7 +249,13 @@ module Async
|
|
271
249
|
# If `later` is false, it means that `stop` has been invoked directly. When `later` is true, it means that `stop` is invoked by `stop_children` or some other indirect mechanism. In that case, if we encounter the "current" fiber, we can't stop it right away, as it's currently performing `#stop`. Stopping it immediately would interrupt the current stop traversal, so we need to schedule the stop to occur later.
|
272
250
|
#
|
273
251
|
# @parameter later [Boolean] Whether to stop the task later, or immediately.
|
274
|
-
|
252
|
+
# @parameter cause [Exception] The cause of the stop operation.
|
253
|
+
def stop(later = false, cause: $!)
|
254
|
+
# If no cause is given, we generate one from the current call stack:
|
255
|
+
unless cause
|
256
|
+
cause = Stop::Cause.for("Stopping task!")
|
257
|
+
end
|
258
|
+
|
275
259
|
if self.stopped?
|
276
260
|
# If the task is already stopped, a `stop` state transition re-enters the same state which is a no-op. However, we will also attempt to stop any running children too. This can happen if the children did not stop correctly the first time around. Doing this should probably be considered a bug, but it's better to be safe than sorry.
|
277
261
|
return stopped!
|
@@ -285,7 +269,7 @@ module Async
|
|
285
269
|
# If we are deferring stop...
|
286
270
|
if @defer_stop == false
|
287
271
|
# Don't stop now... but update the state so we know we need to stop later.
|
288
|
-
@defer_stop =
|
272
|
+
@defer_stop = cause
|
289
273
|
return false
|
290
274
|
end
|
291
275
|
|
@@ -293,19 +277,19 @@ module Async
|
|
293
277
|
# If the fiber is current, and later is `true`, we need to schedule the fiber to be stopped later, as it's currently invoking `stop`:
|
294
278
|
if later
|
295
279
|
# If the fiber is the current fiber and we want to stop it later, schedule it:
|
296
|
-
Fiber.scheduler.push(Stop::Later.new(self))
|
280
|
+
Fiber.scheduler.push(Stop::Later.new(self, cause))
|
297
281
|
else
|
298
282
|
# Otherwise, raise the exception directly:
|
299
|
-
raise Stop, "Stopping current task!"
|
283
|
+
raise Stop, "Stopping current task!", cause: cause
|
300
284
|
end
|
301
285
|
else
|
302
286
|
# If the fiber is not curent, we can raise the exception directly:
|
303
287
|
begin
|
304
288
|
# There is a chance that this will stop the fiber that originally called stop. If that happens, the exception handling in `#stopped` will rescue the exception and re-raise it later.
|
305
|
-
Fiber.scheduler.raise(@fiber, Stop)
|
289
|
+
Fiber.scheduler.raise(@fiber, Stop, cause: cause)
|
306
290
|
rescue FiberError
|
307
291
|
# In some cases, this can cause a FiberError (it might be resumed already), so we schedule it to be stopped later:
|
308
|
-
Fiber.scheduler.push(Stop::Later.new(self))
|
292
|
+
Fiber.scheduler.push(Stop::Later.new(self, cause))
|
309
293
|
end
|
310
294
|
end
|
311
295
|
else
|
@@ -345,7 +329,7 @@ module Async
|
|
345
329
|
|
346
330
|
# If we were asked to stop, we should do so now:
|
347
331
|
if defer_stop
|
348
|
-
raise Stop, "Stopping current task (was deferred)!"
|
332
|
+
raise Stop, "Stopping current task (was deferred)!", cause: defer_stop
|
349
333
|
end
|
350
334
|
end
|
351
335
|
else
|
@@ -356,7 +340,7 @@ module Async
|
|
356
340
|
|
357
341
|
# @returns [Boolean] Whether stop has been deferred.
|
358
342
|
def stop_deferred?
|
359
|
-
|
343
|
+
!!@defer_stop
|
360
344
|
end
|
361
345
|
|
362
346
|
# Lookup the {Task} for the current fiber. Raise `RuntimeError` if none is available.
|
data/lib/async/version.rb
CHANGED
data/lib/async/waiter.rb
CHANGED
data/readme.md
CHANGED
@@ -35,6 +35,11 @@ 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.27.0
|
39
|
+
|
40
|
+
- `Async::Task#stop` supports an optional `cause:` argument (that defaults to `$!`), which allows you to specify the cause (exception) for stopping the task.
|
41
|
+
- Add thread-safety agent context.
|
42
|
+
|
38
43
|
### v2.26.0
|
39
44
|
|
40
45
|
- `Async::Notification#signal` now returns `true` if a task was signaled, `false` otherwise, providing better feedback for notification operations.
|
@@ -84,10 +89,6 @@ Please see the [project releases](https://socketry.github.io/async/releases/inde
|
|
84
89
|
|
85
90
|
- Introduce `Async::Queue#push` and `Async::Queue#pop` for compatibility with `::Queue`.
|
86
91
|
|
87
|
-
### v2.16.0
|
88
|
-
|
89
|
-
- [Better Handling of Async and Sync in Nested Fibers](https://socketry.github.io/async/releases/index#better-handling-of-async-and-sync-in-nested-fibers)
|
90
|
-
|
91
92
|
## See Also
|
92
93
|
|
93
94
|
- [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client/server.
|
data/releases.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v2.27.0
|
4
|
+
|
5
|
+
- `Async::Task#stop` supports an optional `cause:` argument (that defaults to `$!`), which allows you to specify the cause (exception) for stopping the task.
|
6
|
+
- Add thread-safety agent context.
|
7
|
+
|
3
8
|
## v2.26.0
|
4
9
|
|
5
10
|
- `Async::Notification#signal` now returns `true` if a task was signaled, `false` otherwise, providing better feedback for notification operations.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.27.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
|
+
- Shopify Inc.
|
8
9
|
- Bruno Sutic
|
9
10
|
- Jeremy Jung
|
10
11
|
- Olle Jonsson
|
11
12
|
- Patrik Wenger
|
12
13
|
- Devin Christensen
|
13
|
-
- Shopify Inc.
|
14
14
|
- Emil Tin
|
15
15
|
- Jamie McCarthy
|
16
16
|
- Kent Gruber
|
@@ -103,14 +103,14 @@ dependencies:
|
|
103
103
|
requirements:
|
104
104
|
- - "~>"
|
105
105
|
- !ruby/object:Gem::Version
|
106
|
-
version: '1.
|
106
|
+
version: '1.11'
|
107
107
|
type: :runtime
|
108
108
|
prerelease: false
|
109
109
|
version_requirements: !ruby/object:Gem::Requirement
|
110
110
|
requirements:
|
111
111
|
- - "~>"
|
112
112
|
- !ruby/object:Gem::Version
|
113
|
-
version: '1.
|
113
|
+
version: '1.11'
|
114
114
|
- !ruby/object:Gem::Dependency
|
115
115
|
name: metrics
|
116
116
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,6 +161,7 @@ files:
|
|
161
161
|
- lib/async/scheduler.rb
|
162
162
|
- lib/async/semaphore.md
|
163
163
|
- lib/async/semaphore.rb
|
164
|
+
- lib/async/stop.rb
|
164
165
|
- lib/async/task.md
|
165
166
|
- lib/async/task.rb
|
166
167
|
- lib/async/timeout.rb
|
metadata.gz.sig
CHANGED
Binary file
|