Skip to content

Commit 195cf8c

Browse files
committed
Move Async::Stop to lib/async/stop.rb and implement compatibility for older Ruby versions.
1 parent 3789e09 commit 195cf8c

3 files changed

Lines changed: 83 additions & 57 deletions

File tree

lib/async/stop.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

lib/async/task.rb

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,65 +13,11 @@
1313

1414
require_relative "node"
1515
require_relative "condition"
16+
require_relative "stop"
1617

1718
Fiber.attr_accessor :async_task
1819

1920
module Async
20-
# Raised when a task is explicitly stopped.
21-
class Stop < Exception
22-
# Represents the source of the stop operation.
23-
class Cause < Exception
24-
if RUBY_VERSION >= "3.4"
25-
# @returns [Array(Thread::Backtrace::Location)] The backtrace of the caller.
26-
def self.backtrace
27-
caller_locations(2..-1)
28-
end
29-
else
30-
# @returns [Array(String)] The backtrace of the caller.
31-
def self.backtrace
32-
caller(2..-1)
33-
end
34-
end
35-
36-
# Create a new cause of the stop operation, with the given message.
37-
#
38-
# @parameter message [String] The error message.
39-
# @returns [Cause] The cause of the stop operation.
40-
def self.for(message = "Task was stopped")
41-
instance = self.new(message)
42-
instance.set_backtrace(self.backtrace)
43-
return instance
44-
end
45-
end
46-
47-
# Create a new stop operation.
48-
def initialize(message = "Task was stopped")
49-
super(message)
50-
end
51-
52-
# Used to defer stopping the current task until later.
53-
class Later
54-
# Create a new stop later operation.
55-
#
56-
# @parameter task [Task] The task to stop later.
57-
# @parameter cause [Exception] The cause of the stop operation.
58-
def initialize(task, cause = nil)
59-
@task = task
60-
@cause = cause
61-
end
62-
63-
# @returns [Boolean] Whether the task is alive.
64-
def alive?
65-
true
66-
end
67-
68-
# Transfer control to the operation - this will stop the task.
69-
def transfer
70-
@task.stop(false, cause: @cause)
71-
end
72-
end
73-
end
74-
7521
# Raised if a timeout occurs on a specific Fiber. Handled gracefully by `Task`.
7622
# @public Since *Async v1*.
7723
class TimeoutError < StandardError

test/async/task.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,8 +574,6 @@
574574
end
575575

576576
it "can stop a task from outside with a cause" do
577-
skip_unless_minimum_ruby_version("3.5")
578-
579577
error = nil
580578

581579
cause = RuntimeError.new("boom")

0 commit comments

Comments
 (0)