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

9  

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

12  

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

15  

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

23  

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

29  

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

36  

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

42  

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

44  

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

46  

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

50  

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

55  

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

71  

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

76  

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

84  

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

93  

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

101  

107  
    ~cancel_at_awaitable()
102  
    ~cancel_at_awaitable()
108  
    {
103  
    {
109  
        destroy_parent_cb();
104  
        destroy_parent_cb();
110  
    }
105  
    }
111  

106  

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

116  

122 -
    cancel_at_awaitable(cancel_at_awaitable const&) = delete;
117 +
    cancel_at_awaitable(cancel_at_awaitable const&)            = delete;
123  
    cancel_at_awaitable& operator=(cancel_at_awaitable const&) = delete;
118  
    cancel_at_awaitable& operator=(cancel_at_awaitable const&) = delete;
124 -
    cancel_at_awaitable& operator=(cancel_at_awaitable&&) = delete;
119 +
    cancel_at_awaitable& operator=(cancel_at_awaitable&&)      = delete;
125  

120  

126 -
    bool await_ready() const noexcept { return false; }
121 +
    bool await_ready() const noexcept
 
122 +
    {
 
123 +
        return false;
 
124 +
    }
127  

125  

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

130  

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

132  

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

141  

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

145  

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

151  

161  
    decltype(auto) await_resume()
152  
    decltype(auto) await_resume()
162  
    {
153  
    {
163  
        // Cancel whichever is still pending (idempotent)
154  
        // Cancel whichever is still pending (idempotent)
164  
        stop_src_.request_stop();
155  
        stop_src_.request_stop();
165  
        destroy_parent_cb();
156  
        destroy_parent_cb();
166  
        return inner_.await_resume();
157  
        return inner_.await_resume();
167  
    }
158  
    }
168  

159  

169  
    void destroy_parent_cb() noexcept
160  
    void destroy_parent_cb() noexcept
170  
    {
161  
    {
171  
        if (cb_active_)
162  
        if (cb_active_)
172  
        {
163  
        {
173 -
            std::launder(reinterpret_cast<stop_cb_type*>(
164 +
            std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_))
174 -
                cb_buf_))->~stop_cb_type();
165 +
                ->~stop_cb_type();
175  
            cb_active_ = false;
166  
            cb_active_ = false;
176  
        }
167  
        }
177  
    }
168  
    }
178  
};
169  
};
179  

170  

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

172  

182  
#endif
173  
#endif