100.00% Lines (46/46) 100.00% Functions (9/9)
TLA Baseline Branch
Line Hits Code Line Hits Code
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_;
HITCBC 66   2 void operator()() const noexcept 66   2 void operator()() const noexcept
67   { 67   {
HITCBC 68   2 src_->request_stop(); 68   2 src_->request_stop();
HITCBC 69   2 } 69   2 }
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 = 74   using timer_storage =
75   std::conditional_t<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.
HITCBC 86   18 cancel_at_awaitable(A&& inner, Timer& timer, time_point deadline) 86   18 cancel_at_awaitable(A&& inner, Timer& timer, time_point deadline)
87   requires(!Owning) 87   requires(!Owning)
HITCBC 88   18 : inner_(std::move(inner)) 88   18 : inner_(std::move(inner))
HITCBC 89   18 , timer_(&timer) 89   18 , timer_(&timer)
HITCBC 90   18 , deadline_(deadline) 90   18 , deadline_(deadline)
91   { 91   {
HITCBC 92   18 } 92   18 }
93   93  
94   /// Construct without a timer (created in `await_suspend`). 94   /// Construct without a timer (created in `await_suspend`).
HITCBC 95   6 cancel_at_awaitable(A&& inner, time_point deadline) 95   6 cancel_at_awaitable(A&& inner, time_point deadline)
96   requires Owning 96   requires Owning
HITCBC 97   6 : inner_(std::move(inner)) 97   6 : inner_(std::move(inner))
HITCBC 98   6 , deadline_(deadline) 98   6 , deadline_(deadline)
99   { 99   {
HITCBC 100   6 } 100   6 }
101   101  
HITCBC 102   48 ~cancel_at_awaitable() 102   48 ~cancel_at_awaitable()
103   { 103   {
HITCBC 104   48 destroy_parent_cb(); 104   48 destroy_parent_cb();
HITCBC 105   48 } 105   48 }
106   106  
107   // Only moved before await_suspend, when cb_active_ is false 107   // Only moved before await_suspend, when cb_active_ is false
HITCBC 108   24 cancel_at_awaitable(cancel_at_awaitable&& o) noexcept( 108   24 cancel_at_awaitable(cancel_at_awaitable&& o) noexcept(
109   std::is_nothrow_move_constructible_v<A>) 109   std::is_nothrow_move_constructible_v<A>)
HITCBC 110   24 : inner_(std::move(o.inner_)) 110   24 : inner_(std::move(o.inner_))
HITCBC 111   24 , timer_(std::move(o.timer_)) 111   24 , timer_(std::move(o.timer_))
HITCBC 112   24 , deadline_(o.deadline_) 112   24 , deadline_(o.deadline_)
HITCBC 113   24 , stop_src_(std::move(o.stop_src_)) 113   24 , stop_src_(std::move(o.stop_src_))
114   { 114   {
HITCBC 115   24 } 115   24 }
116   116  
117   cancel_at_awaitable(cancel_at_awaitable const&) = delete; 117   cancel_at_awaitable(cancel_at_awaitable const&) = delete;
118   cancel_at_awaitable& operator=(cancel_at_awaitable const&) = delete; 118   cancel_at_awaitable& operator=(cancel_at_awaitable const&) = delete;
119   cancel_at_awaitable& operator=(cancel_at_awaitable&&) = delete; 119   cancel_at_awaitable& operator=(cancel_at_awaitable&&) = delete;
120   120  
HITCBC 121   24 bool await_ready() const noexcept 121   24 bool await_ready() const noexcept
122   { 122   {
HITCBC 123   24 return false; 123   24 return false;
124   } 124   }
125   125  
HITCBC 126   24 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 126   24 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
127   { 127   {
128   if constexpr (Owning) 128   if constexpr (Owning)
HITCBC 129   6 timer_.emplace(env->executor.context()); 129   6 timer_.emplace(env->executor.context());
130   130  
HITCBC 131   24 timer_->expires_at(deadline_); 131   24 timer_->expires_at(deadline_);
132   132  
133   // Launch fire-and-forget timeout (starts suspended) 133   // Launch fire-and-forget timeout (starts suspended)
HITCBC 134   24 auto timeout = make_timeout(*timer_, stop_src_); 134   24 auto timeout = make_timeout(*timer_, stop_src_);
HITCBC 135   48 timeout.h_.promise().set_env_owned( 135   48 timeout.h_.promise().set_env_owned(
HITCBC 136   24 {env->executor, stop_src_.get_token(), env->frame_allocator}); 136   24 {env->executor, stop_src_.get_token(), env->frame_allocator});
137   // Runs synchronously until timer.wait() suspends 137   // Runs synchronously until timer.wait() suspends
HITCBC 138   24 timeout.h_.resume(); 138   24 timeout.h_.resume();
139   // timeout goes out of scope; destructor is a no-op, 139   // timeout goes out of scope; destructor is a no-op,
140   // the coroutine self-destroys via suspend_never 140   // the coroutine self-destroys via suspend_never
141   141  
142   // Forward parent cancellation 142   // Forward parent cancellation
HITCBC 143   24 new (cb_buf_) stop_cb_type(env->stop_token, stop_forwarder{&stop_src_}); 143   24 new (cb_buf_) stop_cb_type(env->stop_token, stop_forwarder{&stop_src_});
HITCBC 144   24 cb_active_ = true; 144   24 cb_active_ = true;
145   145  
146   // Start the inner op with our interposed stop_token 146   // Start the inner op with our interposed stop_token
HITCBC 147   24 inner_env_ = { 147   24 inner_env_ = {
HITCBC 148   24 env->executor, stop_src_.get_token(), env->frame_allocator}; 148   24 env->executor, stop_src_.get_token(), env->frame_allocator};
HITCBC 149   48 return inner_.await_suspend(h, &inner_env_); 149   48 return inner_.await_suspend(h, &inner_env_);
HITCBC 150   48 } 150   48 }
151   151  
HITCBC 152   24 decltype(auto) await_resume() 152   24 decltype(auto) await_resume()
153   { 153   {
154   // Cancel whichever is still pending (idempotent) 154   // Cancel whichever is still pending (idempotent)
HITCBC 155   24 stop_src_.request_stop(); 155   24 stop_src_.request_stop();
HITCBC 156   24 destroy_parent_cb(); 156   24 destroy_parent_cb();
HITCBC 157   24 return inner_.await_resume(); 157   24 return inner_.await_resume();
158   } 158   }
159   159  
HITCBC 160   72 void destroy_parent_cb() noexcept 160   72 void destroy_parent_cb() noexcept
161   { 161   {
HITCBC 162   72 if (cb_active_) 162   72 if (cb_active_)
163   { 163   {
HITCBC 164   24 std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_)) 164   24 std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_))
HITCBC 165   24 ->~stop_cb_type(); 165   24 ->~stop_cb_type();
HITCBC 166   24 cb_active_ = false; 166   24 cb_active_ = false;
167   } 167   }
HITCBC 168   72 } 168   72 }
169   }; 169   };
170   170  
171   } // namespace boost::corosio::detail 171   } // namespace boost::corosio::detail
172   172  
173   #endif 173   #endif