LCOV - code coverage report
Current view: top level - include/boost/corosio - timer.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 98.1 % 54 53 1
Test Date: 2026-02-16 16:21:08 Functions: 100.0 % 16 16

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

Generated by: LCOV version 2.3