LCOV - code coverage report
Current view: top level - capy - timeout.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 89.1 % 46 41 5
Test Date: 2026-03-19 01:23:19 Functions: 100.0 % 34 34

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Michael Vandeberg
       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/capy
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_CAPY_TIMEOUT_HPP
      11                 : #define BOOST_CAPY_TIMEOUT_HPP
      12                 : 
      13                 : #include <boost/capy/detail/config.hpp>
      14                 : #include <boost/capy/concept/io_awaitable.hpp>
      15                 : #include <boost/capy/delay.hpp>
      16                 : #include <boost/capy/detail/io_result_combinators.hpp>
      17                 : #include <boost/capy/error.hpp>
      18                 : #include <boost/capy/io_result.hpp>
      19                 : #include <boost/capy/task.hpp>
      20                 : #include <boost/capy/when_all.hpp>
      21                 : 
      22                 : #include <atomic>
      23                 : #include <chrono>
      24                 : #include <exception>
      25                 : #include <optional>
      26                 : 
      27                 : namespace boost {
      28                 : namespace capy {
      29                 : namespace detail {
      30                 : 
      31                 : template<typename T>
      32                 : struct timeout_state
      33                 : {
      34                 :     when_all_core core_;
      35                 :     std::atomic<int> winner_{-1}; // -1=none, 0=inner, 1=delay
      36                 :     std::optional<T> inner_result_;
      37                 :     std::exception_ptr inner_exception_;
      38                 :     std::array<std::coroutine_handle<>, 2> runner_handles_{};
      39                 : 
      40 HIT           8 :     timeout_state()
      41               8 :         : core_(2)
      42                 :     {
      43               8 :     }
      44                 : };
      45                 : 
      46                 : template<IoAwaitable Awaitable, typename T>
      47                 : when_all_runner<timeout_state<T>>
      48               8 : make_timeout_inner_runner(
      49                 :     Awaitable inner, timeout_state<T>* state)
      50                 : {
      51                 :     try
      52                 :     {
      53                 :         auto result = co_await std::move(inner);
      54                 :         state->inner_result_.emplace(std::move(result));
      55                 :     }
      56                 :     catch(...)
      57                 :     {
      58                 :         state->inner_exception_ = std::current_exception();
      59                 :     }
      60                 : 
      61                 :     int expected = -1;
      62                 :     if(state->winner_.compare_exchange_strong(
      63                 :         expected, 0, std::memory_order_relaxed))
      64                 :         state->core_.stop_source_.request_stop();
      65              16 : }
      66                 : 
      67                 : template<typename DelayAw, typename T>
      68                 : when_all_runner<timeout_state<T>>
      69               8 : make_timeout_delay_runner(
      70                 :     DelayAw d, timeout_state<T>* state)
      71                 : {
      72                 :     auto result = co_await std::move(d);
      73                 : 
      74                 :     if(!result.ec)
      75                 :     {
      76                 :         int expected = -1;
      77                 :         if(state->winner_.compare_exchange_strong(
      78                 :             expected, 1, std::memory_order_relaxed))
      79                 :             state->core_.stop_source_.request_stop();
      80                 :     }
      81              16 : }
      82                 : 
      83                 : template<IoAwaitable Inner, typename DelayAw, typename T>
      84                 : class timeout_launcher
      85                 : {
      86                 :     Inner* inner_;
      87                 :     DelayAw* delay_;
      88                 :     timeout_state<T>* state_;
      89                 : 
      90                 : public:
      91               8 :     timeout_launcher(
      92                 :         Inner* inner, DelayAw* delay,
      93                 :         timeout_state<T>* state)
      94               8 :         : inner_(inner)
      95               8 :         , delay_(delay)
      96               8 :         , state_(state)
      97                 :     {
      98               8 :     }
      99                 : 
     100               8 :     bool await_ready() const noexcept { return false; }
     101                 : 
     102               8 :     std::coroutine_handle<> await_suspend(
     103                 :         std::coroutine_handle<> continuation,
     104                 :         io_env const* caller_env)
     105                 :     {
     106               8 :         state_->core_.continuation_ = continuation;
     107               8 :         state_->core_.caller_env_ = caller_env;
     108                 : 
     109               8 :         if(caller_env->stop_token.stop_possible())
     110                 :         {
     111 MIS           0 :             state_->core_.parent_stop_callback_.emplace(
     112               0 :                 caller_env->stop_token,
     113                 :                 when_all_core::stop_callback_fn{
     114               0 :                     &state_->core_.stop_source_});
     115                 : 
     116               0 :             if(caller_env->stop_token.stop_requested())
     117               0 :                 state_->core_.stop_source_.request_stop();
     118                 :         }
     119                 : 
     120 HIT           8 :         auto token = state_->core_.stop_source_.get_token();
     121                 : 
     122               8 :         auto r0 = make_timeout_inner_runner(
     123               8 :             std::move(*inner_), state_);
     124               8 :         auto h0 = r0.release();
     125               8 :         h0.promise().state_ = state_;
     126               8 :         h0.promise().env_ = io_env{
     127                 :             caller_env->executor, token,
     128               8 :             caller_env->frame_allocator};
     129               8 :         state_->runner_handles_[0] =
     130                 :             std::coroutine_handle<>{h0};
     131                 : 
     132               8 :         auto r1 = make_timeout_delay_runner(
     133               8 :             std::move(*delay_), state_);
     134               8 :         auto h1 = r1.release();
     135               8 :         h1.promise().state_ = state_;
     136               8 :         h1.promise().env_ = io_env{
     137                 :             caller_env->executor, token,
     138               8 :             caller_env->frame_allocator};
     139               8 :         state_->runner_handles_[1] =
     140                 :             std::coroutine_handle<>{h1};
     141                 : 
     142               8 :         caller_env->executor.post(
     143               8 :             state_->runner_handles_[0]);
     144               8 :         caller_env->executor.post(
     145               8 :             state_->runner_handles_[1]);
     146                 : 
     147              16 :         return std::noop_coroutine();
     148              24 :     }
     149                 : 
     150               8 :     void await_resume() const noexcept {}
     151                 : };
     152                 : 
     153                 : } // namespace detail
     154                 : 
     155                 : /** Race an io_result-returning awaitable against a deadline.
     156                 : 
     157                 :     Starts the awaitable and a timer concurrently. The first to
     158                 :     complete wins and cancels the other. If the awaitable finishes
     159                 :     first, its result is returned as-is (success, error, or
     160                 :     exception). If the timer fires first, an `io_result` with
     161                 :     `ec == error::timeout` is produced.
     162                 : 
     163                 :     Unlike @ref when_any, exceptions from the inner awaitable
     164                 :     are always propagated — they are never swallowed by the timer.
     165                 : 
     166                 :     @par Return Type
     167                 : 
     168                 :     Always returns `io_result<Ts...>` matching the inner
     169                 :     awaitable's result type. On timeout, `ec` is set to
     170                 :     `error::timeout` and payload values are default-initialized.
     171                 : 
     172                 :     @par Precision
     173                 : 
     174                 :     The timeout fires at or after the specified duration.
     175                 : 
     176                 :     @par Cancellation
     177                 : 
     178                 :     If the parent's stop token is activated, both children are
     179                 :     cancelled. The inner awaitable's cancellation result is
     180                 :     returned.
     181                 : 
     182                 :     @par Example
     183                 :     @code
     184                 :     auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms);
     185                 :     if (ec == cond::timeout) {
     186                 :         // handle timeout
     187                 :     }
     188                 :     @endcode
     189                 : 
     190                 :     @tparam A An IoAwaitable returning `io_result<Ts...>`.
     191                 : 
     192                 :     @param a The awaitable to race against the deadline.
     193                 :     @param dur The maximum duration to wait.
     194                 : 
     195                 :     @return `task<awaitable_result_t<A>>`.
     196                 : 
     197                 :     @throws Rethrows any exception from the inner awaitable,
     198                 :         regardless of whether the timer has fired.
     199                 : 
     200                 :     @see delay, cond::timeout
     201                 : */
     202                 : template<IoAwaitable A, typename Rep, typename Period>
     203                 :     requires detail::is_io_result_v<awaitable_result_t<A>>
     204               8 : auto timeout(A a, std::chrono::duration<Rep, Period> dur)
     205                 :     -> task<awaitable_result_t<A>>
     206                 : {
     207                 :     using T = awaitable_result_t<A>;
     208                 : 
     209                 :     auto d = delay(dur);
     210                 :     detail::timeout_state<T> state;
     211                 : 
     212                 :     co_await detail::timeout_launcher<
     213                 :         A, decltype(d), T>(&a, &d, &state);
     214                 : 
     215                 :     if(state.core_.first_exception_)
     216                 :         std::rethrow_exception(state.core_.first_exception_);
     217                 :     if(state.inner_exception_)
     218                 :         std::rethrow_exception(state.inner_exception_);
     219                 : 
     220                 :     if(state.winner_.load(std::memory_order_relaxed) == 0)
     221                 :         co_return std::move(*state.inner_result_);
     222                 : 
     223                 :     // Delay fired first: timeout
     224                 :     T r{};
     225                 :     r.ec = make_error_code(error::timeout);
     226                 :     co_return r;
     227              16 : }
     228                 : 
     229                 : } // capy
     230                 : } // boost
     231                 : 
     232                 : #endif
        

Generated by: LCOV version 2.3