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 % 45 45
Test Date: 2026-02-26 19:04:11 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 = 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              18 :     cancel_at_awaitable(
      87                 :         A&& inner,
      88                 :         Timer& timer,
      89                 :         time_point deadline)
      90                 :         requires (!Owning)
      91              18 :         : inner_(std::move(inner))
      92              18 :         , timer_(&timer)
      93              18 :         , deadline_(deadline)
      94                 :     {
      95              18 :     }
      96                 : 
      97                 :     /// Construct without a timer (created in `await_suspend`).
      98               6 :     cancel_at_awaitable(
      99                 :         A&& inner,
     100                 :         time_point deadline)
     101                 :         requires Owning
     102               6 :         : inner_(std::move(inner))
     103               6 :         , deadline_(deadline)
     104                 :     {
     105               6 :     }
     106                 : 
     107              48 :     ~cancel_at_awaitable()
     108                 :     {
     109              48 :         destroy_parent_cb();
     110              48 :     }
     111                 : 
     112                 :     // Only moved before await_suspend, when cb_active_ is false
     113              24 :     cancel_at_awaitable(cancel_at_awaitable&& o) noexcept(
     114                 :         std::is_nothrow_move_constructible_v<A>)
     115              24 :         : inner_(std::move(o.inner_))
     116              24 :         , timer_(std::move(o.timer_))
     117              24 :         , deadline_(o.deadline_)
     118              24 :         , stop_src_(std::move(o.stop_src_))
     119                 :     {
     120              24 :     }
     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              24 :     bool await_ready() const noexcept { return false; }
     127                 : 
     128              24 :     auto await_suspend(
     129                 :         std::coroutine_handle<> h,
     130                 :         capy::io_env const* env)
     131                 :     {
     132                 :         if constexpr (Owning)
     133               6 :             timer_.emplace(env->executor.context());
     134                 : 
     135              24 :         timer_->expires_at(deadline_);
     136                 : 
     137                 :         // Launch fire-and-forget timeout (starts suspended)
     138              24 :         auto timeout = make_timeout(*timer_, stop_src_);
     139              48 :         timeout.h_.promise().set_env_owned({
     140                 :             env->executor,
     141                 :             stop_src_.get_token(),
     142              24 :             env->frame_allocator});
     143                 :         // Runs synchronously until timer.wait() suspends
     144              24 :         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              24 :         new (cb_buf_) stop_cb_type(
     150              24 :             env->stop_token, stop_forwarder{&stop_src_});
     151              24 :         cb_active_ = true;
     152                 : 
     153                 :         // Start the inner op with our interposed stop_token
     154              24 :         inner_env_ = {
     155                 :             env->executor,
     156                 :             stop_src_.get_token(),
     157              24 :             env->frame_allocator};
     158              48 :         return inner_.await_suspend(h, &inner_env_);
     159              48 :     }
     160                 : 
     161              24 :     decltype(auto) await_resume()
     162                 :     {
     163                 :         // Cancel whichever is still pending (idempotent)
     164              24 :         stop_src_.request_stop();
     165              24 :         destroy_parent_cb();
     166              24 :         return inner_.await_resume();
     167                 :     }
     168                 : 
     169              72 :     void destroy_parent_cb() noexcept
     170                 :     {
     171              72 :         if (cb_active_)
     172                 :         {
     173                 :             std::launder(reinterpret_cast<stop_cb_type*>(
     174              24 :                 cb_buf_))->~stop_cb_type();
     175              24 :             cb_active_ = false;
     176                 :         }
     177              72 :     }
     178                 : };
     179                 : 
     180                 : } // namespace boost::corosio::detail
     181                 : 
     182                 : #endif
        

Generated by: LCOV version 2.3