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 = std::conditional_t<
75  
        Owning, std::optional<Timer>, Timer*>;
75  
        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(
87  
        A&& inner,
87  
        A&& inner,
88  
        Timer& timer,
88  
        Timer& timer,
89  
        time_point deadline)
89  
        time_point deadline)
90  
        requires (!Owning)
90  
        requires (!Owning)
91  
        : inner_(std::move(inner))
91  
        : inner_(std::move(inner))
92  
        , timer_(&timer)
92  
        , timer_(&timer)
93  
        , deadline_(deadline)
93  
        , deadline_(deadline)
94  
    {
94  
    {
95  
    }
95  
    }
96  

96  

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

106  

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

111  

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

121  

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

125  

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

127  

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

134  

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

136  

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

147  

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

152  

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

160  

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

168  

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

179  

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

181  

182  
#endif
182  
#endif