stoppable_awaitable

An awaitable is stoppable if it participates in the stoppable awaitable protocol by accepting both a dispatcher and a stop token in its await_suspend method.

Requires: C++20

Synopsis

Defined in header <boost/capy/affine.hpp>

namespace boost::capy {

template<typename A, typename D, typename P = void>
concept stoppable_awaitable =
    affine_awaitable<A, D, P> &&
    requires(A a, std::coroutine_handle<P> h, D const& d, std::stop_token token) {
        a.await_suspend(h, d, token);
    };

} // namespace boost::capy

Description

The stoppable awaitable protocol extends affine_awaitable to enable automatic stop token propagation through coroutine chains. When a task has a stop token, it passes the token to any stoppable awaitables it awaits.

A stoppable awaitable must provide both overloads of await_suspend:

  • await_suspend(h, d) — for callers without stop tokens

  • await_suspend(h, d, token) — for callers with stop tokens

The awaitable should use the stop token to support cancellation of the underlying operation.

Preconditions

  • The dispatcher d remains valid until the awaitable resumes the caller

  • The stop token token remains valid until the operation completes

Valid Expressions

Given:

  • a — a value of type A

  • h — a value of type std::coroutine_handle<P>

  • d — a const value of type D satisfying dispatcher<D, P>

  • token — a value of type std::stop_token

Expression Return Type Description

a.await_ready()

bool

Return true if the operation has already completed

a.await_suspend(h, d)

(unspecified)

Suspend without cancellation support

a.await_suspend(h, d, token)

(unspecified)

Suspend with cancellation support via the stop token

a.await_resume()

(unspecified)

Return the operation result or rethrow any exception

Example

#include <boost/capy/affine.hpp>
#include <stop_token>

using boost::capy::coro;
using boost::capy::stoppable_awaitable;
using boost::capy::any_dispatcher;

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

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

    // Affine path (no cancellation)
    template<typename Dispatcher>
    auto await_suspend(coro h, Dispatcher const& d)
    {
        start_timer(duration_, [h, &d] { d(h); });
        return std::noop_coroutine();
    }

    // Stoppable path (with cancellation)
    template<typename Dispatcher>
    auto await_suspend(coro h, Dispatcher const& d, std::stop_token token)
    {
        if (token.stop_requested())
        {
            cancelled_ = true;
            return d(h);  // Resume immediately
        }

        auto timer_handle = start_timer(duration_, [h, &d] { d(h); });

        // Cancel timer if stop requested
        std::stop_callback cb(token, [timer_handle] {
            cancel_timer(timer_handle);
        });

        return std::noop_coroutine();
    }

    void await_resume()
    {
        if (cancelled_)
            throw std::runtime_error("cancelled");
    }
};

static_assert(stoppable_awaitable<stoppable_timer, any_dispatcher>);

See Also