1 +
//
 
2 +
// Copyright (c) 2026 Steve Gerbino
 
3 +
//
 
4 +
// Distributed under the Boost Software License, Version 1.0. (See accompanying
 
5 +
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 
6 +
//
 
7 +
// Official repository: https://github.com/cppalliance/corosio
 
8 +
//
 
9 +

 
10 +
#ifndef BOOST_COROSIO_DETAIL_CANCEL_AT_AWAITABLE_HPP
 
11 +
#define BOOST_COROSIO_DETAIL_CANCEL_AT_AWAITABLE_HPP
 
12 +

 
13 +
#include <boost/corosio/detail/timeout_coro.hpp>
 
14 +
#include <boost/capy/ex/io_env.hpp>
 
15 +

 
16 +
#include <chrono>
 
17 +
#include <coroutine>
 
18 +
#include <new>
 
19 +
#include <optional>
 
20 +
#include <stop_token>
 
21 +
#include <type_traits>
 
22 +
#include <utility>
 
23 +

 
24 +
/* Races an inner IoAwaitable against a timer via a shared
 
25 +
   stop_source. await_suspend arms the timer by launching a
 
26 +
   fire-and-forget timeout_coro, then starts the inner op with
 
27 +
   an interposed stop_token. Whichever completes first signals
 
28 +
   the stop_source, cancelling the other.
 
29 +

 
30 +
   Parent cancellation is forwarded through a stop_callback
 
31 +
   stored in a placement-new buffer (stop_callback is not
 
32 +
   movable, but the awaitable must be movable for
 
33 +
   transform_awaiter). The buffer is inert during moves
 
34 +
   (before await_suspend) and constructed in-place once the
 
35 +
   awaitable is pinned on the coroutine frame.
 
36 +

 
37 +
   The timeout_coro can outlive this awaitable — it owns its
 
38 +
   env and self-destroys via suspend_never. When Owning is
 
39 +
   false the caller-supplied timer must outlive both; when
 
40 +
   Owning is true the timer lives in std::optional and is
 
41 +
   constructed lazily in await_suspend. */
 
42 +

 
43 +
namespace boost::corosio::detail {
 
44 +

 
45 +
/** Awaitable adapter that cancels an inner operation after a deadline.
 
46 +

 
47 +
    Races the inner awaitable against a timer. A shared stop_source
 
48 +
    ties them together: whichever completes first cancels the other.
 
49 +
    Parent cancellation is forwarded via stop_callback.
 
50 +

 
51 +
    When @p Owning is `false` (default), the caller supplies a timer
 
52 +
    reference that must outlive the awaitable. When @p Owning is
 
53 +
    `true`, the timer is constructed internally in `await_suspend`
 
54 +
    from the execution context in `io_env`.
 
55 +

 
56 +
    @tparam A The inner IoAwaitable type (decayed).
 
57 +
    @tparam Timer The timer type (`timer` or `native_timer<B>`).
 
58 +
    @tparam Owning When `true`, the awaitable owns its timer.
 
59 +
*/
 
60 +
template<typename A, typename Timer, bool Owning = false>
 
61 +
struct cancel_at_awaitable
 
62 +
{
 
63 +
    struct stop_forwarder
 
64 +
    {
 
65 +
        std::stop_source* src_;
 
66 +
        void operator()() const noexcept
 
67 +
        {
 
68 +
            src_->request_stop();
 
69 +
        }
 
70 +
    };
 
71 +

 
72 +
    using time_point = std::chrono::steady_clock::time_point;
 
73 +
    using stop_cb_type = std::stop_callback<stop_forwarder>;
 
74 +
    using timer_storage = std::conditional_t<
 
75 +
        Owning, std::optional<Timer>, Timer*>;
 
76 +

 
77 +
    A inner_;
 
78 +
    timer_storage timer_;
 
79 +
    time_point deadline_;
 
80 +
    std::stop_source stop_src_;
 
81 +
    capy::io_env inner_env_;
 
82 +
    alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)];
 
83 +
    bool cb_active_ = false;
 
84 +

 
85 +
    /// Construct with a caller-supplied timer reference.
 
86 +
    cancel_at_awaitable(
 
87 +
        A&& inner,
 
88 +
        Timer& timer,
 
89 +
        time_point deadline)
 
90 +
        requires (!Owning)
 
91 +
        : inner_(std::move(inner))
 
92 +
        , timer_(&timer)
 
93 +
        , deadline_(deadline)
 
94 +
    {
 
95 +
    }
 
96 +

 
97 +
    /// Construct without a timer (created in `await_suspend`).
 
98 +
    cancel_at_awaitable(
 
99 +
        A&& inner,
 
100 +
        time_point deadline)
 
101 +
        requires Owning
 
102 +
        : inner_(std::move(inner))
 
103 +
        , deadline_(deadline)
 
104 +
    {
 
105 +
    }
 
106 +

 
107 +
    ~cancel_at_awaitable()
 
108 +
    {
 
109 +
        destroy_parent_cb();
 
110 +
    }
 
111 +

 
112 +
    // Only moved before await_suspend, when cb_active_ is false
 
113 +
    cancel_at_awaitable(cancel_at_awaitable&& o) noexcept(
 
114 +
        std::is_nothrow_move_constructible_v<A>)
 
115 +
        : inner_(std::move(o.inner_))
 
116 +
        , timer_(std::move(o.timer_))
 
117 +
        , deadline_(o.deadline_)
 
118 +
        , stop_src_(std::move(o.stop_src_))
 
119 +
    {
 
120 +
    }
 
121 +

 
122 +
    cancel_at_awaitable(cancel_at_awaitable const&) = delete;
 
123 +
    cancel_at_awaitable& operator=(cancel_at_awaitable const&) = delete;
 
124 +
    cancel_at_awaitable& operator=(cancel_at_awaitable&&) = delete;
 
125 +

 
126 +
    bool await_ready() const noexcept { return false; }
 
127 +

 
128 +
    auto await_suspend(
 
129 +
        std::coroutine_handle<> h,
 
130 +
        capy::io_env const* env)
 
131 +
    {
 
132 +
        if constexpr (Owning)
 
133 +
            timer_.emplace(env->executor.context());
 
134 +

 
135 +
        timer_->expires_at(deadline_);
 
136 +

 
137 +
        // Launch fire-and-forget timeout (starts suspended)
 
138 +
        auto timeout = make_timeout(*timer_, stop_src_);
 
139 +
        timeout.h_.promise().set_env_owned({
 
140 +
            env->executor,
 
141 +
            stop_src_.get_token(),
 
142 +
            env->frame_allocator});
 
143 +
        // Runs synchronously until timer.wait() suspends
 
144 +
        timeout.h_.resume();
 
145 +
        // timeout goes out of scope; destructor is a no-op,
 
146 +
        // the coroutine self-destroys via suspend_never
 
147 +

 
148 +
        // Forward parent cancellation
 
149 +
        new (cb_buf_) stop_cb_type(
 
150 +
            env->stop_token, stop_forwarder{&stop_src_});
 
151 +
        cb_active_ = true;
 
152 +

 
153 +
        // Start the inner op with our interposed stop_token
 
154 +
        inner_env_ = {
 
155 +
            env->executor,
 
156 +
            stop_src_.get_token(),
 
157 +
            env->frame_allocator};
 
158 +
        return inner_.await_suspend(h, &inner_env_);
 
159 +
    }
 
160 +

 
161 +
    decltype(auto) await_resume()
 
162 +
    {
 
163 +
        // Cancel whichever is still pending (idempotent)
 
164 +
        stop_src_.request_stop();
 
165 +
        destroy_parent_cb();
 
166 +
        return inner_.await_resume();
 
167 +
    }
 
168 +

 
169 +
    void destroy_parent_cb() noexcept
 
170 +
    {
 
171 +
        if (cb_active_)
 
172 +
        {
 
173 +
            std::launder(reinterpret_cast<stop_cb_type*>(
 
174 +
                cb_buf_))->~stop_cb_type();
 
175 +
            cb_active_ = false;
 
176 +
        }
 
177 +
    }
 
178 +
};
 
179 +

 
180 +
} // namespace boost::corosio::detail
 
181 +

 
182 +
#endif