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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c593ddf06be9954e63b4f85eb8c0fe022be6cf1e241a37f86620a82fdf860b3
4
- data.tar.gz: 023b1544a36d3bb8ff85f0b8cc3ccc0b4694ed3b0542ed7dafd02330f05ee663
3
+ metadata.gz: 4c9d36d758f8a197b7c00d3d2e4d83d280cfd8ddf4fb60bba1d7b9e95b64ec47
4
+ data.tar.gz: c0c19dcc509563b18a9385c1ff07a982054e6e984c9ed9c1fd715ee027c74f09
5
5
  SHA512:
6
- metadata.gz: 65c3fee1ef33362307c4c74dd2451384de1e1747041ffd68cec0049465a317dada93083198254b905e89758a112800894436fe4cc8a2c713b0c0f5de1a36402e
7
- data.tar.gz: a90fa15643b4a491599e95429c0d79da4363d0778455a50893ee9d6f34217ebd442bf99085f981807e024dfec561f545ea86d74e06f2874b34c1bd191a1b79fa
6
+ metadata.gz: b936e5c17d8e9e2eec7f3d9b3d2d4b00b5a16359cfb267a036f72fb254b53aeb234f8b9368ba1cd2ba41010438dd8da120621005bc1791fc554ee62647e46ed1
7
+ data.tar.gz: 808b0ce51e2bfa4a28d01126d59627409e6a25b37ee8e9e1f8146c138fcef995ab693e699f9c3ece326e234100a397c19e917a55c9d1e34dd797d26531e411f5
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2025, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
 
7
7
  require "fiber"
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
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
  # Copyright, 2022, by Shannon Skipper.
7
+ # Copyright, 2025, by Shopify Inc.
7
8
 
8
9
  require "fiber/annotation"
9
10
 
data/lib/async/queue.rb CHANGED
@@ -5,6 +5,7 @@
5
5
  # Copyright, 2019, by Ryan Musgrave.
6
6
  # Copyright, 2020-2022, by Bruno Sutic.
7
7
  # Copyright, 2025, by Jahfer Husain.
8
+ # Copyright, 2025, by Shopify Inc.
8
9
 
9
10
  require_relative "notification"
10
11
 
data/lib/async/reactor.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2025, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
  # Copyright, 2018, by Sokolov Yura.
7
7
 
@@ -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
- def stop(later = false)
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 = true
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
- @defer_stop
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
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2017-2025, by Samuel Williams.
5
5
 
6
6
  module Async
7
- VERSION = "2.26.0"
7
+ VERSION = "2.27.0"
8
8
  end
data/lib/async/waiter.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2024, by Samuel Williams.
4
+ # Copyright, 2022-2025, by Samuel Williams.
5
5
  # Copyright, 2024, by Patrik Wenger.
6
6
 
7
7
  module Async
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.26.0
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.12'
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.12'
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