Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions context/scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Async do |task|
task.print_hierarchy($stderr)

# Kill the subtask
subtask.stop
subtask.cancel
end
~~~

Expand Down Expand Up @@ -69,9 +69,9 @@ end

You can use this approach to embed the reactor in another event loop. For some integrations, you may want to specify the maximum time to wait to {ruby Async::Scheduler#run_once}.

### Stopping a Scheduler
### Cancelling a Scheduler

{ruby Async::Scheduler#stop} will stop the current scheduler and all children tasks.
{ruby Async::Scheduler#cancel} will cancel the current scheduler and all children tasks.

### Fiber Scheduler Integration

Expand Down
56 changes: 28 additions & 28 deletions context/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This guide explains how asynchronous tasks work and how to use them.

## Overview

Tasks are the smallest unit of sequential code execution in {ruby Async}. Tasks can create other tasks, and Async tracks the parent-child relationship between tasks. When a parent task is stopped, it will also stop all its children tasks. The reactor always starts with one root task.
Tasks are the smallest unit of sequential code execution in {ruby Async}. Tasks can create other tasks, and Async tracks the parent-child relationship between tasks. When a parent task is cancelled, it will also cancel all its children tasks. The reactor always starts with one root task.

```mermaid
graph LR
Expand All @@ -23,11 +23,11 @@ graph LR

A fiber is a lightweight unit of execution that can be suspended and resumed at specific points. After a fiber is suspended, it can be resumed later at the same point with the same execution state. Because only one fiber can execute at a time, they are often referred to as a mechanism for cooperative concurrency.

A task provides extra functionality on top of fibers. A task behaves like a promise: it either succeeds with a value or fails with an exception. Tasks keep track of their parent-child relationships, and when a parent task is stopped, it will also stop all its children tasks. This makes it easier to create complex programs with many concurrent tasks.
A task provides extra functionality on top of fibers. A task behaves like a promise: it either succeeds with a value or fails with an exception. Tasks keep track of their parent-child relationships, and when a parent task is cancelled, it will also cancel all its children tasks. This makes it easier to create complex programs with many concurrent tasks.

### Why does Async manipulate tasks and not fibers?

The {ruby Async::Scheduler} actually works directly with fibers for most operations and isn't aware of tasks. However, the reactor does maintain a tree of tasks for the purpose of managing task and reactor life-cycle. For example, stopping a parent task will stop all its children tasks, and the reactor will exit when all tasks are finished.
The {ruby Async::Scheduler} actually works directly with fibers for most operations and isn't aware of tasks. However, the reactor does maintain a tree of tasks for the purpose of managing task and reactor life-cycle. For example, cancelling a parent task will cancel all its children tasks, and the reactor will exit when all tasks are finished.

## Task Lifecycle

Expand All @@ -40,20 +40,20 @@ stateDiagram-v2

running --> failed : unhandled StandardError-derived exception
running --> complete : user code finished
running --> stopped : stop
running --> cancelled : cancel

initialized --> stopped : stop
initialized --> cancelled : cancel

failed --> [*]
complete --> [*]
stopped --> [*]
cancelled --> [*]
```

Tasks are created in the `initialized` state, and are run by the reactor. During the execution, a task can either `complete` successfully, become `failed` with an unhandled `StandardError`-derived exception, or be explicitly `stopped`. In all of these cases, you can wait for a task to complete by using {ruby Async::Task#wait}.
Tasks are created in the `initialized` state, and are run by the reactor. During the execution, a task can either `complete` successfully, become `failed` with an unhandled `StandardError`-derived exception, or be explicitly `cancelled`. In all of these cases, you can wait for a task to complete by using {ruby Async::Task#wait}.

1. In the case the task successfully completed, the result will be whatever value was generated by the last expression in the task.
2. In the case the task failed with an unhandled `StandardError`-derived exception, waiting on the task will re-raise the exception.
3. In the case the task was stopped, the result will be `nil`.
3. In the case the task was cancelled, the result will be `nil`.

## Starting A Task

Expand Down Expand Up @@ -175,8 +175,8 @@ Async do
break if done.size >= 2
end
ensure
# The remainder of the tasks will be stopped:
barrier.stop
# The remainder of the tasks will be cancelled:
barrier.cancel
end
end
```
Expand All @@ -199,18 +199,18 @@ begin
# Wait until all jobs are done:
barrier.wait
ensure
# Stop any remaining jobs:
barrier.stop
# Cancel any remaining jobs:
barrier.cancel
end
~~~

## Stopping a Task
## Cancelling a Task

When a task completes execution, it will enter the `complete` state (or the `failed` state if it raises an unhandled exception).

There are various situations where you may want to stop a task ({ruby Async::Task#stop}) before it completes. The most common case is shutting down a server. A more complex example is this: you may fan out multiple (10s, 100s) of requests, wait for a subset to complete (e.g. the first 5 or all those that complete within a given deadline), and then stop (terminate/cancel) the remaining operations.
There are various situations where you may want to cancel a task ({ruby Async::Task#cancel}) before it completes. The most common case is shutting down a server. A more complex example is this: you may fan out multiple (10s, 100s) of requests, wait for a subset to complete (e.g. the first 5 or all those that complete within a given deadline), and then cancel the remaining operations.

Using the above program as an example, let's stop all the tasks just after the first one completes.
Using the above program as an example, let's cancel all the tasks just after the first one completes.

```ruby
Async do
Expand All @@ -221,14 +221,14 @@ Async do
end
end

# Stop all the above tasks:
tasks.each(&:stop)
# Cancel all the above tasks:
tasks.each(&:cancel)
end
```

### Stopping all Tasks held in a Barrier
### Cancelling all Tasks held in a Barrier

To stop (terminate/cancel) all the tasks held in a barrier:
To cancel all the tasks held in a barrier:

```ruby
barrier = Async::Barrier.new
Expand All @@ -241,11 +241,11 @@ Async do
end
end

barrier.stop
barrier.cancel
end
```

Unless your tasks all rescue and suppresses `StandardError`-derived exceptions, be sure to call ({ruby Async::Barrier#stop}) to stop the remaining tasks:
Unless your tasks all rescue and suppresses `StandardError`-derived exceptions, be sure to call ({ruby Async::Barrier#cancel}) to cancel the remaining tasks:

```ruby
barrier = Async::Barrier.new
Expand All @@ -261,7 +261,7 @@ Async do
begin
barrier.wait
ensure
barrier.stop
barrier.cancel
end
end
```
Expand All @@ -273,10 +273,10 @@ In order to ensure your resources are cleaned up correctly, make sure you wrap r
~~~ ruby
Async do
begin
socket = connect(remote_address) # May raise Async::Stop
socket = connect(remote_address) # May raise Async::Cancel

socket.write(...) # May raise Async::Stop
socket.read(...) # May raise Async::Stop
socket.write(...) # May raise Async::Cancel
socket.read(...) # May raise Async::Cancel
ensure
socket.close if socket
end
Expand Down Expand Up @@ -398,9 +398,9 @@ end

Transient tasks are similar to normal tasks, except for the following differences:

1. They are not considered by {ruby Async::Task#finished?}, so they will not keep the reactor alive. Instead, they are stopped (with a {ruby Async::Stop} exception) when all other (non-transient) tasks are finished.
1. They are not considered by {ruby Async::Task#finished?}, so they will not keep the reactor alive. Instead, they are cancelled (with a {ruby Async::Cancel} exception) when all other (non-transient) tasks are finished.
2. As soon as a parent task is finished, any transient child tasks will be moved up to be children of the parent's parent. This ensures that they never keep a sub-tree alive.
3. Similarly, if you `stop` a task, any transient child tasks will be moved up the tree as above rather than being stopped.
3. Similarly, if you `cancel` a task, any transient child tasks will be moved up the tree as above rather than being cancelled.

The purpose of transient tasks is when a task is an implementation detail of an object or instance, rather than a concurrency process. Some examples of transient tasks:

Expand Down Expand Up @@ -439,7 +439,7 @@ class TimeStringCache
sleep(1)
end
ensure
# When the reactor terminates all tasks, `Async::Stop` will be raised from `sleep` and this code will be invoked. By clearing `@refresh`, we ensure that the task will be recreated if needed again:
# When the reactor terminates all tasks, `Async::Cancel` will be raised from `sleep` and this code will be invoked. By clearing `@refresh`, we ensure that the task will be recreated if needed again:
@refresh = nil
end
end
Expand Down
6 changes: 3 additions & 3 deletions guides/scheduler/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Async do |task|
task.print_hierarchy($stderr)

# Kill the subtask
subtask.stop
subtask.cancel
end
~~~

Expand Down Expand Up @@ -69,9 +69,9 @@ end

You can use this approach to embed the reactor in another event loop. For some integrations, you may want to specify the maximum time to wait to {ruby Async::Scheduler#run_once}.

### Stopping a Scheduler
### Cancelling a Scheduler

{ruby Async::Scheduler#stop} will stop the current scheduler and all children tasks.
{ruby Async::Scheduler#cancel} will cancel the current scheduler and all children tasks.

### Fiber Scheduler Integration

Expand Down
56 changes: 28 additions & 28 deletions guides/tasks/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This guide explains how asynchronous tasks work and how to use them.

## Overview

Tasks are the smallest unit of sequential code execution in {ruby Async}. Tasks can create other tasks, and Async tracks the parent-child relationship between tasks. When a parent task is stopped, it will also stop all its children tasks. The reactor always starts with one root task.
Tasks are the smallest unit of sequential code execution in {ruby Async}. Tasks can create other tasks, and Async tracks the parent-child relationship between tasks. When a parent task is cancelled, it will also cancel all its children tasks. The reactor always starts with one root task.

```mermaid
graph LR
Expand All @@ -23,11 +23,11 @@ graph LR

A fiber is a lightweight unit of execution that can be suspended and resumed at specific points. After a fiber is suspended, it can be resumed later at the same point with the same execution state. Because only one fiber can execute at a time, they are often referred to as a mechanism for cooperative concurrency.

A task provides extra functionality on top of fibers. A task behaves like a promise: it either succeeds with a value or fails with an exception. Tasks keep track of their parent-child relationships, and when a parent task is stopped, it will also stop all its children tasks. This makes it easier to create complex programs with many concurrent tasks.
A task provides extra functionality on top of fibers. A task behaves like a promise: it either succeeds with a value or fails with an exception. Tasks keep track of their parent-child relationships, and when a parent task is cancelled, it will also cancel all its children tasks. This makes it easier to create complex programs with many concurrent tasks.

### Why does Async manipulate tasks and not fibers?

The {ruby Async::Scheduler} actually works directly with fibers for most operations and isn't aware of tasks. However, the reactor does maintain a tree of tasks for the purpose of managing task and reactor life-cycle. For example, stopping a parent task will stop all its children tasks, and the reactor will exit when all tasks are finished.
The {ruby Async::Scheduler} actually works directly with fibers for most operations and isn't aware of tasks. However, the reactor does maintain a tree of tasks for the purpose of managing task and reactor life-cycle. For example, cancelling a parent task will cancel all its children tasks, and the reactor will exit when all tasks are finished.

## Task Lifecycle

Expand All @@ -40,20 +40,20 @@ stateDiagram-v2

running --> failed : unhandled StandardError-derived exception
running --> complete : user code finished
running --> stopped : stop
running --> cancelled : cancel

initialized --> stopped : stop
initialized --> cancelled : cancel

failed --> [*]
complete --> [*]
stopped --> [*]
cancelled --> [*]
```

Tasks are created in the `initialized` state, and are run by the reactor. During the execution, a task can either `complete` successfully, become `failed` with an unhandled `StandardError`-derived exception, or be explicitly `stopped`. In all of these cases, you can wait for a task to complete by using {ruby Async::Task#wait}.
Tasks are created in the `initialized` state, and are run by the reactor. During the execution, a task can either `complete` successfully, become `failed` with an unhandled `StandardError`-derived exception, or be explicitly `cancelled`. In all of these cases, you can wait for a task to complete by using {ruby Async::Task#wait}.

1. In the case the task successfully completed, the result will be whatever value was generated by the last expression in the task.
2. In the case the task failed with an unhandled `StandardError`-derived exception, waiting on the task will re-raise the exception.
3. In the case the task was stopped, the result will be `nil`.
3. In the case the task was cancelled, the result will be `nil`.

## Starting A Task

Expand Down Expand Up @@ -175,8 +175,8 @@ Async do
break if done.size >= 2
end
ensure
# The remainder of the tasks will be stopped:
barrier.stop
# The remainder of the tasks will be cancelled:
barrier.cancel
end
end
```
Expand All @@ -199,18 +199,18 @@ begin
# Wait until all jobs are done:
barrier.wait
ensure
# Stop any remaining jobs:
barrier.stop
# Cancel any remaining jobs:
barrier.cancel
end
~~~

## Stopping a Task
## Cancelling a Task

When a task completes execution, it will enter the `complete` state (or the `failed` state if it raises an unhandled exception).

There are various situations where you may want to stop a task ({ruby Async::Task#stop}) before it completes. The most common case is shutting down a server. A more complex example is this: you may fan out multiple (10s, 100s) of requests, wait for a subset to complete (e.g. the first 5 or all those that complete within a given deadline), and then stop (terminate/cancel) the remaining operations.
There are various situations where you may want to cancel a task ({ruby Async::Task#cancel}) before it completes. The most common case is shutting down a server. A more complex example is this: you may fan out multiple (10s, 100s) of requests, wait for a subset to complete (e.g. the first 5 or all those that complete within a given deadline), and then cancel the remaining operations.

Using the above program as an example, let's stop all the tasks just after the first one completes.
Using the above program as an example, let's cancel all the tasks just after the first one completes.

```ruby
Async do
Expand All @@ -221,14 +221,14 @@ Async do
end
end

# Stop all the above tasks:
tasks.each(&:stop)
# Cancel all the above tasks:
tasks.each(&:cancel)
end
```

### Stopping all Tasks held in a Barrier
### Cancelling all Tasks held in a Barrier

To stop (terminate/cancel) all the tasks held in a barrier:
To cancel all the tasks held in a barrier:

```ruby
barrier = Async::Barrier.new
Expand All @@ -241,11 +241,11 @@ Async do
end
end

barrier.stop
barrier.cancel
end
```

Unless your tasks all rescue and suppresses `StandardError`-derived exceptions, be sure to call ({ruby Async::Barrier#stop}) to stop the remaining tasks:
Unless your tasks all rescue and suppresses `StandardError`-derived exceptions, be sure to call ({ruby Async::Barrier#cancel}) to cancel the remaining tasks:

```ruby
barrier = Async::Barrier.new
Expand All @@ -261,7 +261,7 @@ Async do
begin
barrier.wait
ensure
barrier.stop
barrier.cancel
end
end
```
Expand All @@ -273,10 +273,10 @@ In order to ensure your resources are cleaned up correctly, make sure you wrap r
~~~ ruby
Async do
begin
socket = connect(remote_address) # May raise Async::Stop
socket = connect(remote_address) # May raise Async::Cancel

socket.write(...) # May raise Async::Stop
socket.read(...) # May raise Async::Stop
socket.write(...) # May raise Async::Cancel
socket.read(...) # May raise Async::Cancel
ensure
socket.close if socket
end
Expand Down Expand Up @@ -398,9 +398,9 @@ end

Transient tasks are similar to normal tasks, except for the following differences:

1. They are not considered by {ruby Async::Task#finished?}, so they will not keep the reactor alive. Instead, they are stopped (with a {ruby Async::Stop} exception) when all other (non-transient) tasks are finished.
1. They are not considered by {ruby Async::Task#finished?}, so they will not keep the reactor alive. Instead, they are cancelled (with a {ruby Async::Cancel} exception) when all other (non-transient) tasks are finished.
2. As soon as a parent task is finished, any transient child tasks will be moved up to be children of the parent's parent. This ensures that they never keep a sub-tree alive.
3. Similarly, if you `stop` a task, any transient child tasks will be moved up the tree as above rather than being stopped.
3. Similarly, if you `cancel` a task, any transient child tasks will be moved up the tree as above rather than being cancelled.

The purpose of transient tasks is when a task is an implementation detail of an object or instance, rather than a concurrency process. Some examples of transient tasks:

Expand Down Expand Up @@ -439,7 +439,7 @@ class TimeStringCache
sleep(1)
end
ensure
# When the reactor terminates all tasks, `Async::Stop` will be raised from `sleep` and this code will be invoked. By clearing `@refresh`, we ensure that the task will be recreated if needed again:
# When the reactor terminates all tasks, `Async::Cancel` will be raised from `sleep` and this code will be invoked. By clearing `@refresh`, we ensure that the task will be recreated if needed again:
@refresh = nil
end
end
Expand Down
14 changes: 10 additions & 4 deletions lib/async/barrier.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.
# Copyright, 2019-2026, by Samuel Williams.

require_relative "list"
require_relative "task"
Expand Down Expand Up @@ -88,14 +88,20 @@ def wait
end
end

# Stop all tasks held by the barrier.
# Cancel all tasks held by the barrier.
# @asynchronous May wait for tasks to finish executing.
def stop
def cancel
@tasks.each do |waiting|
waiting.task.stop
waiting.task.cancel
end

@finished.close
end

# Backward compatibility alias for {#cancel}.
# @deprecated Use {#cancel} instead.
def stop
cancel
end
end
end
Loading
Loading