Cancellation

This section explains how to cancel running coroutines using stop tokens.

Code examples assume using namespace boost::capy; is in effect.

Cooperative Cancellation

Capy supports cooperative cancellation through std::stop_token:

  • Cooperative: Operations check the token and decide how to respond

  • Non-preemptive: Nothing is forcibly terminated

  • Propagating: Tokens flow automatically through co_await chains

task<void> cancellable_work()
{
    auto token = co_await this_coro::stop_token;
    while (!token.stop_requested())
    {
        co_await do_chunk();
    }
    cleanup();
}

How Stop Tokens Propagate

Stop tokens propagate through co_await chains:

task<void> child()
{
    auto token = co_await this_coro::stop_token;
    // token is from parent
}

task<void> parent()
{
    co_await child();  // child sees our token
}

std::stop_source source;
run_async(ex, source.get_token())(parent());
source.request_stop();  // Both parent and child see this

Checking Cancellation

Use this_coro::stop_token to check status:

task<void> long_running()
{
    auto token = co_await this_coro::stop_token;

    for (int i = 0; i < 1000; ++i)
    {
        if (token.stop_requested())
        {
            // Clean up and exit
            cleanup();
            co_return;
        }
        co_await process_item(i);
    }
}

this_coro::stop_token never suspends—it returns immediately.

Implementing Stoppable Operations

Custom awaitables can support cancellation:

struct stoppable_timer
{
    std::chrono::milliseconds duration_;
    bool cancelled_ = false;

    bool await_ready() const
    {
        return duration_.count() <= 0;
    }

    void await_suspend(std::coroutine_handle<> h,
                       executor_ref ex,
                       std::stop_token token)
    {
        if (token.stop_requested())
        {
            cancelled_ = true;
            ex.dispatch(h).resume();
            return;
        }

        auto handle = start_timer(duration_, [h, ex]() {
            ex.dispatch(h).resume();
        });

        std::stop_callback cb(token, [handle]() {
            cancel_timer(handle);
        });
    }

    void await_resume()
    {
        if (cancelled_)
            throw operation_cancelled();
    }
};

Key points:

  • Check stop_requested() before starting

  • Use stop_callback to cancel underlying operations

  • Signal cancellation in await_resume()

Stop Propagation in when_all

when_all creates an internal stop source for sibling coordination:

task<void> example()
{
    co_await when_all(
        might_fail(),
        long_running(),
        another_task()
    );
}

If might_fail() throws:

  1. Exception captured

  2. when_all requests stop on internal source

  3. long_running() and another_task() see stop_requested()

  4. Wait for all to complete

  5. Rethrow first exception

Parent stop tokens also forward:

std::stop_source source;
run_async(ex, source.get_token())(example());
source.request_stop();  // All children see this

Cleanup on Cancellation

Handle cancellation gracefully:

task<void> with_resource()
{
    auto resource = acquire_resource();
    auto token = co_await this_coro::stop_token;

    try {
        while (!token.stop_requested())
            co_await process(resource);
    } catch (...) {
        release_resource(resource);
        throw;
    }

    release_resource(resource);
}

Or use RAII:

task<void> with_raii()
{
    ResourceGuard guard(acquire_resource());
    auto token = co_await this_coro::stop_token;

    while (!token.stop_requested())
        co_await process(guard.get());

    // guard releases on destruction
}

When NOT to Use Cancellation

Use cancellation when:

  • Operations may take a long time

  • Users need to abort operations

  • Timeouts are required

Avoid cancellation when:

  • Operations are very short (overhead not worth it)

  • Operations cannot be interrupted meaningfully

  • You need guaranteed completion

Summary

Component Purpose

std::stop_source

Controller that requests stop

std::stop_token

Observer that checks if stop was requested

this_coro::stop_token

Query current token inside coroutine

std::stop_callback

Execute code when stop is requested

when_all propagation

Sibling cancellation on first error

Next Steps