LCOV - code coverage report
Current view: top level - corosio/detail - cancel_at_awaitable.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 46 46
Test Date: 2026-02-27 19:39:18 Functions: 93.8 % 16 15 1

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

Generated by: LCOV version 2.3