async 2.32.0 → 2.39.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/context/best-practices.md +53 -32
- data/context/debugging.md +3 -3
- data/context/getting-started.md +6 -6
- data/context/scheduler.md +4 -4
- data/context/tasks.md +41 -36
- data/context/thread-safety.md +267 -224
- data/lib/async/barrier.rb +41 -10
- data/lib/async/cancel.rb +80 -0
- data/lib/async/clock.rb +22 -1
- data/lib/async/deadline.rb +1 -0
- data/lib/async/error.rb +17 -0
- data/lib/async/fork_handler.rb +32 -0
- data/lib/async/idler.rb +27 -15
- data/lib/async/loop.rb +84 -0
- data/lib/async/node.rb +28 -9
- data/lib/async/promise.rb +112 -37
- data/lib/async/queue.rb +1 -1
- data/lib/async/scheduler.rb +40 -17
- data/lib/async/stop.rb +3 -75
- data/lib/async/task.rb +160 -91
- data/lib/async/version.rb +3 -2
- data/lib/async.rb +3 -5
- data/lib/kernel/barrier.rb +31 -0
- data/lib/kernel/sync.rb +1 -1
- data/lib/traces/provider/async/barrier.rb +1 -1
- data/license.md +4 -2
- data/readme.md +26 -32
- data/releases.md +104 -33
- data.tar.gz.sig +0 -0
- metadata +9 -3
- metadata.gz.sig +0 -0
- data/lib/async/task.md +0 -30
data/lib/async/barrier.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2019-
|
|
4
|
+
# Copyright, 2019-2026, by Samuel Williams.
|
|
5
|
+
# Copyright, 2026, by Tavian Barnes.
|
|
5
6
|
|
|
6
7
|
require_relative "list"
|
|
7
8
|
require_relative "task"
|
|
@@ -18,6 +19,7 @@ module Async
|
|
|
18
19
|
def initialize(parent: nil)
|
|
19
20
|
@tasks = List.new
|
|
20
21
|
@finished = Queue.new
|
|
22
|
+
@condition = Condition.new
|
|
21
23
|
|
|
22
24
|
@parent = parent
|
|
23
25
|
end
|
|
@@ -42,18 +44,32 @@ module Async
|
|
|
42
44
|
|
|
43
45
|
# Execute a child task and add it to the barrier.
|
|
44
46
|
# @asynchronous Executes the given block concurrently.
|
|
47
|
+
# @returns [Task] The task which was created to execute the block.
|
|
45
48
|
def async(*arguments, parent: (@parent or Task.current), **options, &block)
|
|
46
49
|
raise "Barrier is stopped!" if @finished.closed?
|
|
47
50
|
|
|
48
51
|
waiting = nil
|
|
49
52
|
|
|
50
|
-
parent.async(*arguments, **options) do |task, *arguments|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
task = parent.async(*arguments, **options) do |task, *arguments|
|
|
54
|
+
# Create a new list node for the task and add it to the list of waiting tasks:
|
|
55
|
+
node = TaskNode.new(task)
|
|
56
|
+
@tasks.append(node)
|
|
57
|
+
|
|
58
|
+
# Signal the outer async block that we have added the task to the list of waiting tasks, and that it can now wait for it to finish:
|
|
59
|
+
waiting = node
|
|
60
|
+
@condition.signal
|
|
61
|
+
|
|
62
|
+
# Invoke the block, which may raise an error. If it does, we will still signal that the task has finished:
|
|
53
63
|
block.call(task, *arguments)
|
|
54
64
|
ensure
|
|
55
|
-
|
|
65
|
+
# Signal that the task has finished, which will unblock the waiting task:
|
|
66
|
+
@finished.signal(node) unless @finished.closed?
|
|
56
67
|
end
|
|
68
|
+
|
|
69
|
+
# `parent.async` may yield before the child block executes, so we wait here until the child has appended itself to `@tasks`, ensuring `wait` cannot return early and miss tracking it:
|
|
70
|
+
@condition.wait while waiting.nil?
|
|
71
|
+
|
|
72
|
+
return task
|
|
57
73
|
end
|
|
58
74
|
|
|
59
75
|
# Whether there are any tasks being held by the barrier.
|
|
@@ -65,12 +81,17 @@ module Async
|
|
|
65
81
|
# 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.
|
|
66
82
|
#
|
|
67
83
|
# @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.
|
|
84
|
+
# @returns [Integer | Nil] The number of tasks which were waited for, or `nil` if there were no tasks to wait for.
|
|
68
85
|
#
|
|
69
86
|
# @asynchronous Will wait for tasks to finish executing.
|
|
70
87
|
def wait
|
|
71
|
-
|
|
88
|
+
return nil if @tasks.empty?
|
|
89
|
+
count = 0
|
|
90
|
+
|
|
91
|
+
while true
|
|
72
92
|
# Wait for a task to finish (we get the task node):
|
|
73
|
-
|
|
93
|
+
break unless waiting = @finished.wait
|
|
94
|
+
count += 1
|
|
74
95
|
|
|
75
96
|
# Remove the task as it is now finishing:
|
|
76
97
|
@tasks.remove?(waiting)
|
|
@@ -85,17 +106,27 @@ module Async
|
|
|
85
106
|
# Wait for it to either complete or raise an error:
|
|
86
107
|
task.wait
|
|
87
108
|
end
|
|
109
|
+
|
|
110
|
+
break if @tasks.empty?
|
|
88
111
|
end
|
|
112
|
+
|
|
113
|
+
return count
|
|
89
114
|
end
|
|
90
115
|
|
|
91
|
-
#
|
|
116
|
+
# Cancel all tasks held by the barrier.
|
|
92
117
|
# @asynchronous May wait for tasks to finish executing.
|
|
93
|
-
def
|
|
118
|
+
def cancel
|
|
94
119
|
@tasks.each do |waiting|
|
|
95
|
-
waiting.task.
|
|
120
|
+
waiting.task.cancel
|
|
96
121
|
end
|
|
97
122
|
|
|
98
123
|
@finished.close
|
|
99
124
|
end
|
|
125
|
+
|
|
126
|
+
# Backward compatibility alias for {#cancel}.
|
|
127
|
+
# @deprecated Use {#cancel} instead.
|
|
128
|
+
def stop
|
|
129
|
+
cancel
|
|
130
|
+
end
|
|
100
131
|
end
|
|
101
132
|
end
|
data/lib/async/cancel.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Async
|
|
7
|
+
# Raised when a task is explicitly cancelled.
|
|
8
|
+
class Cancel < Exception
|
|
9
|
+
# Represents the source of the cancel operation.
|
|
10
|
+
class Cause < Exception
|
|
11
|
+
if RUBY_VERSION >= "3.4"
|
|
12
|
+
# @returns [Array(Thread::Backtrace::Location)] The backtrace of the caller.
|
|
13
|
+
def self.backtrace
|
|
14
|
+
caller_locations(2..-1)
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
# @returns [Array(String)] The backtrace of the caller.
|
|
18
|
+
def self.backtrace
|
|
19
|
+
caller(2..-1)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Create a new cause of the cancel operation, with the given message.
|
|
24
|
+
#
|
|
25
|
+
# @parameter message [String] The error message.
|
|
26
|
+
# @returns [Cause] The cause of the cancel operation.
|
|
27
|
+
def self.for(message = "Task was cancelled!")
|
|
28
|
+
instance = self.new(message)
|
|
29
|
+
instance.set_backtrace(self.backtrace)
|
|
30
|
+
return instance
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if RUBY_VERSION < "3.5"
|
|
35
|
+
# Create a new cancel operation.
|
|
36
|
+
#
|
|
37
|
+
# This is a compatibility method for Ruby versions before 3.5 where cause is not propagated correctly when using {Fiber#raise}
|
|
38
|
+
#
|
|
39
|
+
# @parameter message [String | Hash] The error message or a hash containing the cause.
|
|
40
|
+
def initialize(message = "Task was cancelled")
|
|
41
|
+
|
|
42
|
+
if message.is_a?(Hash)
|
|
43
|
+
@cause = message[:cause]
|
|
44
|
+
message = "Task was cancelled"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
super(message)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @returns [Exception] The cause of the cancel operation.
|
|
51
|
+
#
|
|
52
|
+
# 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.
|
|
53
|
+
def cause
|
|
54
|
+
super || @cause
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Used to defer cancelling the current task until later.
|
|
59
|
+
class Later
|
|
60
|
+
# Create a new cancel later operation.
|
|
61
|
+
#
|
|
62
|
+
# @parameter task [Task] The task to cancel later.
|
|
63
|
+
# @parameter cause [Exception] The cause of the cancel operation.
|
|
64
|
+
def initialize(task, cause = nil)
|
|
65
|
+
@task = task
|
|
66
|
+
@cause = cause
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @returns [Boolean] Whether the task is alive.
|
|
70
|
+
def alive?
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Transfer control to the operation - this will cancel the task.
|
|
75
|
+
def transfer
|
|
76
|
+
@task.cancel(false, cause: @cause)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/async/clock.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2018-
|
|
4
|
+
# Copyright, 2018-2026, by Samuel Williams.
|
|
5
|
+
# Copyright, 2026, by Shopify Inc.
|
|
5
6
|
|
|
6
7
|
module Async
|
|
7
8
|
# A convenient wrapper around the internal monotonic clock.
|
|
@@ -36,6 +37,9 @@ module Async
|
|
|
36
37
|
@started = nil
|
|
37
38
|
end
|
|
38
39
|
|
|
40
|
+
# @returns [Numeric | Nil] The time when the clock was started, or nil if not started.
|
|
41
|
+
attr :started
|
|
42
|
+
|
|
39
43
|
# Start measuring a duration.
|
|
40
44
|
def start!
|
|
41
45
|
@started ||= Clock.now
|
|
@@ -70,5 +74,22 @@ module Async
|
|
|
70
74
|
@started = Clock.now
|
|
71
75
|
end
|
|
72
76
|
end
|
|
77
|
+
|
|
78
|
+
# Convert the clock to a JSON-compatible hash.
|
|
79
|
+
#
|
|
80
|
+
# @returns [Hash] The JSON-compatible hash.
|
|
81
|
+
def as_json(...)
|
|
82
|
+
{
|
|
83
|
+
started: self.started,
|
|
84
|
+
total: self.total,
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Convert the clock to a JSON string.
|
|
89
|
+
#
|
|
90
|
+
# @returns [String] The JSON string.
|
|
91
|
+
def to_json(...)
|
|
92
|
+
self.as_json.to_json(...)
|
|
93
|
+
end
|
|
73
94
|
end
|
|
74
95
|
end
|
data/lib/async/deadline.rb
CHANGED
data/lib/async/error.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Async
|
|
7
|
+
# Raised if a timeout occurs on a specific Fiber. Handled gracefully by `Task`.
|
|
8
|
+
# @public Since *Async v1*.
|
|
9
|
+
class TimeoutError < StandardError
|
|
10
|
+
# Create a new timeout error.
|
|
11
|
+
#
|
|
12
|
+
# @parameter message [String] The error message.
|
|
13
|
+
def initialize(message = "execution expired")
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025-2026, by Shopify Inc.
|
|
5
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
6
|
+
|
|
7
|
+
module Async
|
|
8
|
+
# Private module that hooks into Process._fork to handle fork events.
|
|
9
|
+
#
|
|
10
|
+
# If `Scheduler#process_fork` hook is adopted in Ruby 4, this code can be removed after Ruby < 4 is no longer supported.
|
|
11
|
+
module ForkHandler
|
|
12
|
+
def _fork(&block)
|
|
13
|
+
result = super
|
|
14
|
+
|
|
15
|
+
if result.zero?
|
|
16
|
+
# Child process:
|
|
17
|
+
if Fiber.scheduler.respond_to?(:process_fork)
|
|
18
|
+
Fiber.scheduler.process_fork
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
return result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private_constant :ForkHandler
|
|
27
|
+
|
|
28
|
+
# Hook into Process._fork to handle fork events automatically:
|
|
29
|
+
unless RUBY_VERSION > "4"
|
|
30
|
+
::Process.singleton_class.prepend(ForkHandler)
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/async/idler.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
module Async
|
|
7
7
|
# A load balancing mechanism that can be used process work when the system is idle.
|
|
@@ -13,10 +13,13 @@ module Async
|
|
|
13
13
|
# @parameter maximum_load [Numeric] The maximum load before we start shedding work.
|
|
14
14
|
# @parameter backoff [Numeric] The initial backoff time, used for delaying work.
|
|
15
15
|
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
|
|
16
|
-
def initialize(maximum_load = 0.8, backoff: 0.
|
|
16
|
+
def initialize(maximum_load = 0.8, backoff: 0.001, parent: nil)
|
|
17
17
|
@maximum_load = maximum_load
|
|
18
18
|
@backoff = backoff
|
|
19
|
+
@current = backoff
|
|
20
|
+
|
|
19
21
|
@parent = parent
|
|
22
|
+
@mutex = Mutex.new
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
# Wait until the system is idle, then execute the given block in a new task.
|
|
@@ -38,20 +41,29 @@ module Async
|
|
|
38
41
|
#
|
|
39
42
|
# If the scheduler is overloaded, this method will sleep for an exponentially increasing amount of time.
|
|
40
43
|
def wait
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
while true
|
|
45
|
-
load = scheduler.load
|
|
46
|
-
|
|
47
|
-
break if load < @maximum_load
|
|
44
|
+
@mutex.synchronize do
|
|
45
|
+
scheduler = Fiber.scheduler
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
while true
|
|
48
|
+
load = scheduler.load
|
|
49
|
+
|
|
50
|
+
if load <= @maximum_load
|
|
51
|
+
# Even though load is okay, if @current is high, we were recently overloaded. Sleep proportionally to prevent burst after load drop:
|
|
52
|
+
if @current > @backoff
|
|
53
|
+
# Sleep a fraction of @current to rate limit:
|
|
54
|
+
sleep(@current - @backoff)
|
|
55
|
+
|
|
56
|
+
# Decay @current gently towards @backoff:
|
|
57
|
+
alpha = 0.99
|
|
58
|
+
@current *= alpha + (1.0 - alpha) * (load / @maximum_load)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
break
|
|
62
|
+
else
|
|
63
|
+
# We're overloaded, so increase backoff:
|
|
64
|
+
@current *= (load / @maximum_load)
|
|
65
|
+
sleep(@current)
|
|
66
|
+
end
|
|
55
67
|
end
|
|
56
68
|
end
|
|
57
69
|
end
|
data/lib/async/loop.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "console"
|
|
7
|
+
|
|
8
|
+
module Async
|
|
9
|
+
# @namespace
|
|
10
|
+
module Loop
|
|
11
|
+
# Execute a block repeatedly at quantized (time-aligned) intervals.
|
|
12
|
+
#
|
|
13
|
+
# The alignment is computed modulo the current clock time in seconds. For example, with
|
|
14
|
+
# `interval: 60`, executions will occur at 00:00, 01:00, 02:00, etc., regardless of when
|
|
15
|
+
# the loop is started. With `interval: 300` (5 minutes), executions align to 00:00, 00:05,
|
|
16
|
+
# 00:10, etc.
|
|
17
|
+
#
|
|
18
|
+
# This is particularly useful for tasks that should run at predictable wall-clock times,
|
|
19
|
+
# such as metrics collection, periodic cleanup, or scheduled jobs that need to align
|
|
20
|
+
# across multiple processes.
|
|
21
|
+
#
|
|
22
|
+
# If an error occurs during block execution, it is logged and the loop continues.
|
|
23
|
+
#
|
|
24
|
+
# @example Run every minute at :00 seconds:
|
|
25
|
+
# Async::Loop.quantized(interval: 60) do
|
|
26
|
+
# puts "Current time: #{Time.now}"
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example Run every 5 minutes aligned to the hour:
|
|
30
|
+
# Async::Loop.quantized(interval: 300) do
|
|
31
|
+
# collect_metrics
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @parameter interval [Numeric] The interval in seconds. Executions will align to multiples of this interval based on the current time.
|
|
35
|
+
# @yields The block to execute at each interval.
|
|
36
|
+
#
|
|
37
|
+
# @public Since *Async v2.37*.
|
|
38
|
+
def self.quantized(interval: 60, &block)
|
|
39
|
+
while true
|
|
40
|
+
# Compute the wait time to the next interval:
|
|
41
|
+
wait = interval - (Time.now.to_f % interval)
|
|
42
|
+
if wait.positive?
|
|
43
|
+
# Sleep until the next interval boundary:
|
|
44
|
+
sleep(wait)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
yield
|
|
49
|
+
rescue => error
|
|
50
|
+
Console.error(self, "Loop error:", error)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Execute a block repeatedly with a fixed delay between executions.
|
|
56
|
+
#
|
|
57
|
+
# Unlike {quantized}, this method waits for the specified interval *after* each execution
|
|
58
|
+
# completes. This means the actual time between the start of successive executions will be
|
|
59
|
+
# `interval + execution_time`.
|
|
60
|
+
#
|
|
61
|
+
# If an error occurs during block execution, it is logged and the loop continues.
|
|
62
|
+
#
|
|
63
|
+
# @example Run every 5 seconds (plus execution time):
|
|
64
|
+
# Async::Loop.periodic(interval: 5) do
|
|
65
|
+
# process_queue
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# @parameter interval [Numeric] The delay in seconds between executions.
|
|
69
|
+
# @yields The block to execute periodically.
|
|
70
|
+
#
|
|
71
|
+
# @public Since *Async v2.37*.
|
|
72
|
+
def self.periodic(interval: 60, &block)
|
|
73
|
+
while true
|
|
74
|
+
begin
|
|
75
|
+
yield
|
|
76
|
+
rescue => error
|
|
77
|
+
Console.error(self, "Loop error:", error)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
sleep(interval)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/async/node.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2017-
|
|
4
|
+
# Copyright, 2017-2026, by Samuel Williams.
|
|
5
5
|
# Copyright, 2017, by Kent Gruber.
|
|
6
6
|
# Copyright, 2022, by Shannon Skipper.
|
|
7
|
-
# Copyright, 2025, by Shopify Inc.
|
|
7
|
+
# Copyright, 2025-2026, by Shopify Inc.
|
|
8
8
|
|
|
9
9
|
require "fiber/annotation"
|
|
10
10
|
|
|
@@ -214,7 +214,8 @@ module Async
|
|
|
214
214
|
end
|
|
215
215
|
|
|
216
216
|
protected def remove_child(child)
|
|
217
|
-
|
|
217
|
+
# In the case of a fork, the children list may be nil:
|
|
218
|
+
@children&.remove(child)
|
|
218
219
|
child.set_parent(nil)
|
|
219
220
|
end
|
|
220
221
|
|
|
@@ -279,26 +280,44 @@ module Async
|
|
|
279
280
|
return @children.nil?
|
|
280
281
|
end
|
|
281
282
|
|
|
282
|
-
# Attempt to
|
|
283
|
+
# Attempt to cancel the current node immediately, including all non-transient children. Invokes {#stop_children} to cancel all children.
|
|
283
284
|
#
|
|
284
|
-
# @parameter later [Boolean] Whether to defer
|
|
285
|
-
def
|
|
285
|
+
# @parameter later [Boolean] Whether to defer cancelling until some point in the future.
|
|
286
|
+
def cancel(later = false)
|
|
286
287
|
# The implementation of this method may defer calling `stop_children`.
|
|
287
288
|
stop_children(later)
|
|
288
289
|
end
|
|
289
290
|
|
|
291
|
+
# Backward compatibility alias for {#cancel}.
|
|
292
|
+
# @deprecated Use {#cancel} instead.
|
|
293
|
+
def stop(...)
|
|
294
|
+
cancel(...)
|
|
295
|
+
end
|
|
296
|
+
|
|
290
297
|
# Attempt to stop all non-transient children.
|
|
291
298
|
private def stop_children(later = false)
|
|
292
299
|
@children&.each do |child|
|
|
293
|
-
child.
|
|
300
|
+
child.cancel(later) unless child.transient?
|
|
294
301
|
end
|
|
295
302
|
end
|
|
296
303
|
|
|
297
|
-
#
|
|
298
|
-
|
|
304
|
+
# Wait for this node to complete. By default, nodes cannot be waited on.
|
|
305
|
+
# Subclasses like Task override this method to provide waiting functionality.
|
|
306
|
+
def wait
|
|
307
|
+
nil
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Whether the node has been cancelled.
|
|
311
|
+
def cancelled?
|
|
299
312
|
@children.nil?
|
|
300
313
|
end
|
|
301
314
|
|
|
315
|
+
# Backward compatibility alias for {#cancelled?}.
|
|
316
|
+
# @deprecated Use {#cancelled?} instead.
|
|
317
|
+
def stopped?
|
|
318
|
+
cancelled?
|
|
319
|
+
end
|
|
320
|
+
|
|
302
321
|
# Print the hierarchy of the task tree from the given node.
|
|
303
322
|
#
|
|
304
323
|
# @parameter out [IO] The output stream to write to.
|