LCOV - code coverage report
Current view: top level - include/boost/corosio - timer.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 98.2 % 55 54
Test Date: 2026-02-12 20:11:43 Functions: 100.0 % 16 16

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3              : // Copyright (c) 2026 Steve Gerbino
       4              : //
       5              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7              : //
       8              : // Official repository: https://github.com/cppalliance/corosio
       9              : //
      10              : 
      11              : #ifndef BOOST_COROSIO_TIMER_HPP
      12              : #define BOOST_COROSIO_TIMER_HPP
      13              : 
      14              : #include <boost/corosio/detail/config.hpp>
      15              : #include <boost/corosio/detail/except.hpp>
      16              : #include <boost/corosio/io_object.hpp>
      17              : #include <boost/capy/io_result.hpp>
      18              : #include <boost/capy/error.hpp>
      19              : #include <boost/capy/ex/executor_ref.hpp>
      20              : #include <boost/capy/ex/execution_context.hpp>
      21              : #include <boost/capy/ex/io_env.hpp>
      22              : #include <boost/capy/concept/executor.hpp>
      23              : #include <system_error>
      24              : 
      25              : #include <chrono>
      26              : #include <coroutine>
      27              : #include <cstddef>
      28              : #include <limits>
      29              : #include <stop_token>
      30              : 
      31              : namespace boost::corosio {
      32              : 
      33              : /** An asynchronous timer for coroutine I/O.
      34              : 
      35              :     This class provides asynchronous timer operations that return
      36              :     awaitable types. The timer can be used to schedule operations
      37              :     to occur after a specified duration or at a specific time point.
      38              : 
      39              :     Multiple coroutines may wait concurrently on the same timer.
      40              :     When the timer expires, all waiters complete with success. When
      41              :     the timer is cancelled, all waiters complete with an error that
      42              :     compares equal to `capy::cond::canceled`.
      43              : 
      44              :     Each timer operation participates in the affine awaitable protocol,
      45              :     ensuring coroutines resume on the correct executor.
      46              : 
      47              :     @par Thread Safety
      48              :     Distinct objects: Safe.@n
      49              :     Shared objects: Unsafe.
      50              : 
      51              :     @par Semantics
      52              :     Wraps platform timer facilities via the io_context reactor.
      53              :     Operations dispatch to OS timer APIs (timerfd, IOCP timers,
      54              :     kqueue EVFILT_TIMER).
      55              : */
      56              : class BOOST_COROSIO_DECL timer : public io_object
      57              : {
      58              :     struct wait_awaitable
      59              :     {
      60              :         timer& t_;
      61              :         std::stop_token token_;
      62              :         mutable std::error_code ec_;
      63              : 
      64         9187 :         explicit wait_awaitable(timer& t) noexcept : t_(t) {}
      65              : 
      66         9187 :         bool await_ready() const noexcept
      67              :         {
      68         9187 :             return token_.stop_requested();
      69              :         }
      70              : 
      71         9187 :         capy::io_result<> await_resume() const noexcept
      72              :         {
      73         9187 :             if (token_.stop_requested())
      74            0 :                 return {capy::error::canceled};
      75         9187 :             return {ec_};
      76              :         }
      77              : 
      78         9187 :         auto await_suspend(
      79              :             std::coroutine_handle<> h,
      80              :             capy::io_env const* env) -> std::coroutine_handle<>
      81              :         {
      82         9187 :             token_ = env->stop_token;
      83         9187 :             auto& impl = t_.get();
      84              :             // Inline fast path: already expired and not in the heap
      85        18352 :             if (impl.heap_index_ == timer_impl::npos &&
      86        18326 :                 (impl.expiry_ == (time_point::min)() ||
      87        18348 :                     impl.expiry_ <= clock_type::now()))
      88              :             {
      89          226 :                 ec_ = {};
      90          226 :                 auto d = env->executor;
      91          226 :                 d.post(h);
      92          226 :                 return std::noop_coroutine();
      93              :             }
      94        17922 :             return impl.wait(
      95        17922 :                 h, env->executor, std::move(token_), &ec_);
      96              :         }
      97              :     };
      98              : 
      99              : public:
     100              :     struct timer_impl : io_object_impl
     101              :     {
     102              :         static constexpr std::size_t npos =
     103              :             (std::numeric_limits<std::size_t>::max)();
     104              : 
     105              :         std::chrono::steady_clock::time_point expiry_{};
     106              :         std::size_t heap_index_ = npos;
     107              :         bool might_have_pending_waits_ = false;
     108              : 
     109              :         virtual std::coroutine_handle<> wait(
     110              :             std::coroutine_handle<>,
     111              :             capy::executor_ref,
     112              :             std::stop_token,
     113              :             std::error_code*) = 0;
     114              :     };
     115              : 
     116              : public:
     117              :     /// The clock type used for time operations.
     118              :     using clock_type = std::chrono::steady_clock;
     119              : 
     120              :     /// The time point type for absolute expiry times.
     121              :     using time_point = clock_type::time_point;
     122              : 
     123              :     /// The duration type for relative expiry times.
     124              :     using duration = clock_type::duration;
     125              : 
     126              :     /** Destructor.
     127              : 
     128              :         Cancels any pending operations and releases timer resources.
     129              :     */
     130              :     ~timer();
     131              : 
     132              :     /** Construct a timer from an execution context.
     133              : 
     134              :         @param ctx The execution context that will own this timer.
     135              :     */
     136              :     explicit timer(capy::execution_context& ctx);
     137              : 
     138              :     /** Construct a timer with an initial absolute expiry time.
     139              : 
     140              :         @param ctx The execution context that will own this timer.
     141              :         @param t The initial expiry time point.
     142              :     */
     143              :     timer(capy::execution_context& ctx, time_point t);
     144              : 
     145              :     /** Construct a timer with an initial relative expiry time.
     146              : 
     147              :         @param ctx The execution context that will own this timer.
     148              :         @param d The initial expiry duration relative to now.
     149              :     */
     150              :     template<class Rep, class Period>
     151            2 :     timer(
     152              :         capy::execution_context& ctx,
     153              :         std::chrono::duration<Rep, Period> d)
     154            2 :         : timer(ctx)
     155              :     {
     156            2 :         expires_after(d);
     157            2 :     }
     158              : 
     159              :     /** Move constructor.
     160              : 
     161              :         Transfers ownership of the timer resources.
     162              : 
     163              :         @param other The timer to move from.
     164              :     */
     165              :     timer(timer&& other) noexcept;
     166              : 
     167              :     /** Move assignment operator.
     168              : 
     169              :         Closes any existing timer and transfers ownership.
     170              :         The source and destination must share the same execution context.
     171              : 
     172              :         @param other The timer to move from.
     173              : 
     174              :         @return Reference to this timer.
     175              : 
     176              :         @throws std::logic_error if the timers have different execution contexts.
     177              :     */
     178              :     timer& operator=(timer&& other);
     179              : 
     180              :     timer(timer const&) = delete;
     181              :     timer& operator=(timer const&) = delete;
     182              : 
     183              :     /** Cancel all pending asynchronous wait operations.
     184              : 
     185              :         All outstanding operations complete with an error code that
     186              :         compares equal to `capy::cond::canceled`.
     187              : 
     188              :         @return The number of operations that were cancelled.
     189              :     */
     190           20 :     std::size_t cancel()
     191              :     {
     192           20 :         if (!get().might_have_pending_waits_)
     193           12 :             return 0;
     194            8 :         return do_cancel();
     195              :     }
     196              : 
     197              :     /** Cancel one pending asynchronous wait operation.
     198              : 
     199              :         The oldest pending wait is cancelled (FIFO order). It
     200              :         completes with an error code that compares equal to
     201              :         `capy::cond::canceled`.
     202              : 
     203              :         @return The number of operations that were cancelled (0 or 1).
     204              :     */
     205            4 :     std::size_t cancel_one()
     206              :     {
     207            4 :         if (!get().might_have_pending_waits_)
     208            2 :             return 0;
     209            2 :         return do_cancel_one();
     210              :     }
     211              : 
     212              :     /** Return the timer's expiry time as an absolute time.
     213              : 
     214              :         @return The expiry time point. If no expiry has been set,
     215              :             returns a default-constructed time_point.
     216              :     */
     217           34 :     time_point expiry() const noexcept
     218              :     {
     219           34 :         return get().expiry_;
     220              :     }
     221              : 
     222              :     /** Set the timer's expiry time as an absolute time.
     223              : 
     224              :         Any pending asynchronous wait operations will be cancelled.
     225              : 
     226              :         @param t The expiry time to be used for the timer.
     227              : 
     228              :         @return The number of pending operations that were cancelled.
     229              :     */
     230           18 :     std::size_t expires_at(time_point t)
     231              :     {
     232           18 :         auto& impl = get();
     233           18 :         impl.expiry_ = t;
     234           18 :         if (impl.heap_index_ == timer_impl::npos &&
     235           16 :             !impl.might_have_pending_waits_)
     236           16 :             return 0;
     237            2 :         return do_update_expiry();
     238              :     }
     239              : 
     240              :     /** Set the timer's expiry time relative to now.
     241              : 
     242              :         Any pending asynchronous wait operations will be cancelled.
     243              : 
     244              :         @param d The expiry time relative to now.
     245              : 
     246              :         @return The number of pending operations that were cancelled.
     247              :     */
     248         9187 :     std::size_t expires_after(duration d)
     249              :     {
     250         9187 :         auto& impl = get();
     251         9187 :         if (d <= duration::zero())
     252            6 :             impl.expiry_ = (time_point::min)();
     253              :         else
     254         9181 :             impl.expiry_ = clock_type::now() + d;
     255         9187 :         if (impl.heap_index_ == timer_impl::npos &&
     256         9183 :             !impl.might_have_pending_waits_)
     257         9183 :             return 0;
     258            4 :         return do_update_expiry();
     259              :     }
     260              : 
     261              :     /** Set the timer's expiry time relative to now.
     262              : 
     263              :         This is a convenience overload that accepts any duration type
     264              :         and converts it to the timer's native duration type. Any
     265              :         pending asynchronous wait operations will be cancelled.
     266              : 
     267              :         @param d The expiry time relative to now.
     268              : 
     269              :         @return The number of pending operations that were cancelled.
     270              :     */
     271              :     template<class Rep, class Period>
     272         9187 :     std::size_t expires_after(std::chrono::duration<Rep, Period> d)
     273              :     {
     274         9187 :         return expires_after(std::chrono::duration_cast<duration>(d));
     275              :     }
     276              : 
     277              :     /** Wait for the timer to expire.
     278              : 
     279              :         Multiple coroutines may wait on the same timer concurrently.
     280              :         When the timer expires, all waiters complete with success.
     281              : 
     282              :         The operation supports cancellation via `std::stop_token` through
     283              :         the affine awaitable protocol. If the associated stop token is
     284              :         triggered, only that waiter completes with an error that
     285              :         compares equal to `capy::cond::canceled`; other waiters are
     286              :         unaffected.
     287              : 
     288              :         @par Example
     289              :         @code
     290              :         timer t(ctx);
     291              :         t.expires_after(std::chrono::seconds(5));
     292              :         auto [ec] = co_await t.wait();
     293              :         if (ec == capy::cond::canceled)
     294              :         {
     295              :             // Cancelled via stop_token or cancel()
     296              :             co_return;
     297              :         }
     298              :         if (ec)
     299              :         {
     300              :             // Handle other errors
     301              :             co_return;
     302              :         }
     303              :         // Timer expired
     304              :         @endcode
     305              : 
     306              :         @return An awaitable that completes with `io_result<>`.
     307              :             Returns success (default error_code) when the timer expires,
     308              :             or an error code on failure. Compare against error conditions
     309              :             (e.g., `ec == capy::cond::canceled`) rather than error codes.
     310              : 
     311              :         @par Preconditions
     312              :         The timer must have an expiry time set via expires_at() or
     313              :         expires_after().
     314              :     */
     315         9187 :     auto wait()
     316              :     {
     317         9187 :         return wait_awaitable(*this);
     318              :     }
     319              : 
     320              : private:
     321              :     // Out-of-line cancel/expiry when inline fast-path
     322              :     // conditions (no waiters, not in heap) are not met.
     323              :     std::size_t do_cancel();
     324              :     std::size_t do_cancel_one();
     325              :     std::size_t do_update_expiry();
     326              : 
     327              :     /// Return the underlying implementation.
     328        27664 :     timer_impl& get() const noexcept
     329              :     {
     330        27664 :         return *static_cast<timer_impl*>(impl_);
     331              :     }
     332              : };
     333              : 
     334              : } // namespace boost::corosio
     335              : 
     336              : #endif
        

Generated by: LCOV version 2.3