# Understanding ASIO cancellation_state and propagate_on_cancel: A Complete Guide

> Master ASIO cancellation_state and propagate_on_cancel. Learn to control signal propagation between parent and child operations with this complete guide for precise ASIO cancellation management.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: deep-dive
- Published: 2026-07-11

---

**ASIO's `cancellation_state` class filters cancellation requests between parent signals and child operations, allowing precise control over propagation using predefined policies like `enable_terminal_cancellation` or custom filter functions.**

The `chriskohlhoff/asio` library implements a granular cancellation model for asynchronous operations through the `cancellation_state` mechanism. Unlike simple stop tokens, this system allows intermediate layers to transform, suppress, or forward cancellation requests based on operation-specific requirements. This article examines the implementation in [`include/asio/cancellation_state.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/cancellation_state.hpp) and explains how the `propagate_on_cancel` filter policies control cancellation flow.

## How cancellation_state Works

The `cancellation_state` class template serves as a bridge between a parent `cancellation_slot` and the child slot passed to composed operations. It maintains internal state about which cancellation types have been requested while applying transformation filters to incoming and outgoing signals.

### Construction and Slot Management

When you construct a `cancellation_state`, it creates an internal implementation object stored via the parent slot. According to the source in [`include/asio/cancellation_state.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/cancellation_state.hpp) (lines 99–102), the constructor stores a pointer to an internal implementation (`impl_`). If the parent slot is connected, the implementation is created via `slot.emplace<impl<…>>()`.

The class provides two essential accessors:
- `slot()` returns the child `cancellation_slot` that you pass to asynchronous operations (lines 74–78)
- `cancelled()` returns the `cancellation_type_t` bits that have already been propagated to the child

### The Filtering Mechanism

The core of `propagate_on_cancel` behavior lies in the bidirectional filter system defined in lines 109–126. Each `cancellation_state` maintains two filters:
- **In-filter**: Transforms the cancellation type received from the parent before storing it
- **Out-filter**: Transforms the stored cancellation type before emitting it to the child slot

These filters are callable objects with the signature `cancellation_type_t(cancellation_type_t)`. When the parent signal emits a cancellation request, the implementation applies the in-filter to transform the type, stores the result in `cancelled_`, then applies the out-filter to determine what reaches the child.

The `clear(mask)` method (lines 80–87) allows removing specific cancellation bits from the stored state, accepting a `cancellation_type_t` mask that defaults to clearing all bits.

### Emission and Propagation Flow

When a parent `cancellation_signal` emits a cancellation request, the `cancellation_state` implementation invokes its function call operator. As implemented in lines 173–184, this method:
1. Applies the in-filter to the incoming cancellation type
2. Updates the internal `cancelled_` state using bitwise OR
3. Applies the out-filter to the result
4. Emits the transformed cancellation to the child signal if the result is not `none`

This architecture decouples the cancellation origin from handling, allowing each composition layer to decide whether to forward, suppress, or transform requests.

## Built-in Propagate-on-Cancel Policies

ASIO provides predefined filter types in [`include/asio/cancellation_state.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/cancellation_state.hpp) that express common propagation policies. These control which `cancellation_type_t` values flow downstream.

### Disable Cancellation

The `disable_cancellation` filter (lines 42–45) blocks all propagation. Any cancellation request from the parent is transformed to `cancellation_type::none`, effectively isolating child operations from parent cancellation signals.

### Enable Terminal Cancellation

The `enable_terminal_cancellation` policy (lines 46–48) propagates only terminal cancellation, the most severe form that typically indicates complete abandonment of the operation. This is useful when child operations should only stop during catastrophic shutdowns but continue during normal timeouts.

### Enable Partial Cancellation

The `enable_partial_cancellation` filter (lines 64–69) allows both terminal and partial cancellation types to propagate. Partial cancellation typically indicates a soft stop where operations may complete current work but reject new items.

### Enable Total Cancellation

The `enable_total_cancellation` policy (lines 70–75) propagates all cancellation types: terminal, partial, and total. This provides the most permissive propagation, where any parent cancellation request immediately reaches child operations.

## Practical Implementation

Implementing cancellation propagation requires connecting a `cancellation_signal` to a `cancellation_state` and passing the resulting child slot to asynchronous operations.

### Basic Usage Example

The following example demonstrates total cancellation propagation:

```cpp
#include <asio.hpp>
#include <iostream>

using asio::cancellation_signal;
using asio::cancellation_state;
using asio::enable_total_cancellation;

int main() {
  asio::io_context io_context;
  
  // 1. Create a signal that may be cancelled externally
  cancellation_signal sig;
  
  // 2. Build a state that propagates all cancellations downstream
  cancellation_state<enable_total_cancellation> st(sig.slot());
  
  // 3. Use the child slot in an async operation
  asio::steady_timer timer(io_context);
  timer.async_wait(st.slot(), [](const asio::error_code& ec) {
    if (ec == asio::error::operation_aborted) {
      std::cout << "Operation was cancelled\n";
    }
  });
  
  // 4. Emit cancellation (e.g., timeout or user interrupt)
  sig.emit(asio::cancellation_type::total);
  
  io_context.run();
  return 0;
}

```

To restrict propagation to only terminal cancellation, change the template argument:

```cpp
cancellation_state<asio::enable_terminal_cancellation> st(sig.slot());
// Only terminal cancellation will reach the timer

```

### Custom Filter Policies

You can define custom propagation logic by creating filter functors. For example, to propagate only partial cancellation:

```cpp
struct only_partial {
  asio::cancellation_type_t operator()(asio::cancellation_type_t t) const noexcept {
    return (t & asio::cancellation_type::partial) 
           ? t 
           : asio::cancellation_type::none;
  }
};

// Apply custom filter
cancellation_state<only_partial> st(sig.slot());

```

Custom filters enable domain-specific cancellation semantics, such as converting total cancellation to partial based on operation state or suppressing cancellation during critical sections.

## Summary

- **`cancellation_state`** acts as a bidirectional filter between parent cancellation signals and child operations, defined in [`include/asio/cancellation_state.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/cancellation_state.hpp)
- **Propagation policies** like `enable_total_cancellation`, `enable_terminal_cancellation`, and `disable_cancellation` determine which `cancellation_type_t` values flow downstream
- **Custom filters** allow arbitrary transformation of cancellation requests using callables with signature `cancellation_type_t(cancellation_type_t)`
- The **`slot()`** method provides the child `cancellation_slot` to pass to asynchronous operations, while **`cancelled()`** queries the current cancellation state
- **Clearing state** via `clear(mask)` removes specific cancellation bits, allowing operations to reset cancellation status when handling partial shutdowns

## Frequently Asked Questions

### What is the difference between cancellation_signal and cancellation_state?

A `cancellation_signal` owns a slot and can emit cancellation requests to all connected handlers. A `cancellation_state` sits between a parent slot and child operations, filtering and transforming those requests before they reach the child. While the signal broadcasts, the state controls propagation and maintains cancellation history.

### When should I use enable_terminal_cancellation versus enable_total_cancellation?

Use `enable_terminal_cancellation` when child operations should only abort during complete system shutdowns or fatal errors, ignoring softer cancellation requests like timeouts. Use `enable_total_cancellation` when you want child operations to respond immediately to any cancellation signal, including partial or total cancellation types. The choice depends on whether the child operation can safely handle intermediate cancellation states.

### How do I completely disable cancellation propagation to a child operation?

Pass `disable_cancellation` as the template argument to `cancellation_state`. This filter transforms all incoming cancellation types to `none`, ensuring the child slot never receives cancellation signals regardless of parent activity:

```cpp
asio::cancellation_state<asio::disable_cancellation> st(parent_slot);

```

### Can I change the cancellation filter after construction?

No, the filter type is fixed at compile time as a template parameter to `cancellation_state`. However, you can create a new `cancellation_state` with a different filter and reconnect it to the parent slot if runtime policy changes are required. The filters themselves can be stateful functors that change behavior based on internal state, even though the type remains constant.