include/boost/corosio/delay.hpp

94.7% Lines (89/94) 74.6% List of functions (53/71) 82.2% Branches (37/45)
delay.hpp
f(x) Functions (71)
Function Calls Lines Branches Blocks
std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > boost::corosio::detail::clamp_to_ns<double, std::ratio<1ll, 1ll> >(std::chrono::duration<double, std::ratio<1ll, 1ll> >) :38 1x 37.5% 16.7% 21.1% std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > boost::corosio::detail::clamp_to_ns<int, std::ratio<1ll, 1000ll> >(std::chrono::duration<int, std::ratio<1ll, 1000ll> >) :38 1x 100.0% 50.0% 88.9% std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > boost::corosio::detail::clamp_to_ns<long long, std::ratio<1ll, 1000000000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> >) :38 16x 100.0% 50.0% 88.9% std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > boost::corosio::detail::clamp_to_ns<long long, std::ratio<1ll, 1000000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000000ll> >) :38 1187x 100.0% 50.0% 88.9% std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > boost::corosio::detail::clamp_to_ns<long long, std::ratio<1ll, 1000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000ll> >) :38 3600x 100.0% 50.0% 88.9% std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > boost::corosio::detail::clamp_to_ns<long long, std::ratio<1ll, 1ll> >(std::chrono::duration<long long, std::ratio<1ll, 1ll> >) :38 22x 100.0% 50.0% 88.9% std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > boost::corosio::detail::clamp_to_ns<long long, std::ratio<3600ll, 1ll> >(std::chrono::duration<long long, std::ratio<3600ll, 1ll> >) :38 14x 100.0% 100.0% 100.0% boost::corosio::detail::emplace_delay_timer(std::optional<boost::corosio::detail::timer>&, boost::capy::execution_context&) :60 2834x 66.7% 50.0% 61.5% boost::corosio::delay_awaitable::delay_awaitable(std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> >) :127 4831x 100.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :133 13x 100.0% 100.0% boost::corosio::delay_awaitable::delay_awaitable(boost::corosio::delay_awaitable&&) :142 5858x 100.0% 100.0% boost::corosio::delay_awaitable::await_ready() const :151 4843x 100.0% 100.0% boost::corosio::delay_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :158 4844x 100.0% 100.0% 100.0% boost::corosio::delay_awaitable::await_resume() :185 4831x 100.0% 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>::next_wait(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) const :230 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>::next_wait(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) const :230 2x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>::next_wait(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) const :230 8x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >::next_wait(std::chrono::time_point<boost::corosio::wall_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) const :230 1x 100.0% 100.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>::next_wait(std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) const :230 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >::next_wait(std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) const :230 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>::on_fire(void*) :239 0 0.0% 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>::on_fire(void*) :239 1x 36.4% 16.7% 29.4% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>::on_fire(void*) :239 8x 72.7% 66.7% 82.4% boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >::on_fire(void*) :239 1x 54.5% 33.3% 52.9% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>::on_fire(void*) :239 0 0.0% 0.0% 0.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >::on_fire(void*) :239 0 0.0% 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>::clock_delay_awaitable(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :260 500x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>::clock_delay_awaitable(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :260 4x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>::clock_delay_awaitable(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :260 3x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >::clock_delay_awaitable(std::chrono::time_point<boost::corosio::wall_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :260 1x 100.0% 100.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>::clock_delay_awaitable(std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :260 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >::clock_delay_awaitable(std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :260 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>::clock_delay_awaitable(boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>&&) :268 500x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>::clock_delay_awaitable(boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>&&) :268 4x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>::clock_delay_awaitable(boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>&&) :268 3x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >::clock_delay_awaitable(boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >&&) :268 1x 100.0% 100.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>::clock_delay_awaitable(boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>&&) :268 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >::clock_delay_awaitable(boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >&&) :268 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>::await_ready() const :282 500x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>::await_ready() const :282 4x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>::await_ready() const :282 3x 100.0% 100.0% boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >::await_ready() const :282 1x 100.0% 100.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>::await_ready() const :282 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >::await_ready() const :282 0 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :289 500x 30.8% 12.5% 30.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :289 4x 92.3% 87.5% 95.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :289 3x 84.6% 87.5% 95.0% boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :289 1x 76.9% 75.0% 90.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :289 0 0.0% 0.0% 0.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :289 0 0.0% 0.0% 0.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits>::await_resume() :315 500x 50.0% 25.0% 50.0% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::hold_traits>::await_resume() :315 3x 83.3% 75.0% 87.5% boost::corosio::clock_delay_awaitable<boost::corosio::test_clock, boost::corosio::stepping_traits>::await_resume() :315 3x 83.3% 75.0% 75.0% boost::corosio::clock_delay_awaitable<boost::corosio::wall_clock, boost::corosio::wait_traits<boost::corosio::wall_clock> >::await_resume() :315 1x 66.7% 50.0% 62.5% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, (anonymous namespace)::capped_traits>::await_resume() :315 0 0.0% 0.0% 0.0% boost::corosio::clock_delay_awaitable<std::chrono::_V2::system_clock, boost::corosio::wait_traits<std::chrono::_V2::system_clock> >::await_resume() :315 0 0.0% 0.0% 0.0% boost::corosio::delay_awaitable boost::corosio::delay<double, std::ratio<1ll, 1ll> >(std::chrono::duration<double, std::ratio<1ll, 1ll> >) :343 1x 100.0% 100.0% boost::corosio::delay_awaitable boost::corosio::delay<int, std::ratio<1ll, 1000ll> >(std::chrono::duration<int, std::ratio<1ll, 1000ll> >) :343 1x 100.0% 100.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::ratio<1ll, 1000000000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> >) :343 5x 100.0% 100.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::ratio<1ll, 1000000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000000ll> >) :343 1187x 100.0% 100.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::ratio<1ll, 1000ll> >(std::chrono::duration<long long, std::ratio<1ll, 1000ll> >) :343 3600x 100.0% 100.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::ratio<1ll, 1ll> >(std::chrono::duration<long long, std::ratio<1ll, 1ll> >) :343 22x 100.0% 100.0% boost::corosio::delay_awaitable boost::corosio::delay<long long, std::ratio<3600ll, 1ll> >(std::chrono::duration<long long, std::ratio<3600ll, 1ll> >) :343 14x 100.0% 100.0% boost::corosio::delay(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :359 13x 100.0% 100.0% auto boost::corosio::delay<(anonymous namespace)::capped_traits, std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >(std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :396 0 0.0% 0.0% auto boost::corosio::delay<boost::corosio::delay_test<boost::corosio::iocp_t{}>::testClockRearmStopRace()::race_traits, boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :396 500x 100.0% 100.0% auto boost::corosio::delay<boost::corosio::hold_traits, boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :396 4x 100.0% 100.0% auto boost::corosio::delay<boost::corosio::stepping_traits, boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :396 2x 100.0% 100.0% auto boost::corosio::delay<boost::corosio::stepping_traits, boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000ll> > >(std::chrono::time_point<boost::corosio::test_clock, std::chrono::duration<long long, std::ratio<1ll, 1000ll> > >) :396 1x 100.0% 100.0% auto boost::corosio::delay<void, boost::corosio::wall_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >(std::chrono::time_point<boost::corosio::wall_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :396 1x 100.0% 100.0% auto boost::corosio::delay<void, std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >(std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >) :396 0 0.0% 0.0%
Line Branch TLA Hits 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_DELAY_HPP
11 #define BOOST_COROSIO_DELAY_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/detail/except.hpp>
15 #include <boost/corosio/detail/timer.hpp>
16 #include <boost/corosio/wait_traits.hpp>
17 #include <boost/capy/error.hpp>
18 #include <boost/capy/ex/io_env.hpp>
19 #include <boost/capy/io_result.hpp>
20
21 #include <chrono>
22 #include <concepts>
23 #include <coroutine>
24 #include <exception>
25 #include <optional>
26 #include <stdexcept>
27 #include <system_error>
28 #include <type_traits>
29
30 namespace boost::corosio {
31
32 namespace detail {
33
34 // Narrow reps wrap if nanoseconds::max() is converted into them;
35 // a double comparison clamps safely in both directions.
36 template<typename Rep, typename Period>
37 std::chrono::nanoseconds
38 4841x clamp_to_ns(std::chrono::duration<Rep, Period> dur) noexcept
39 {
40 using namespace std::chrono;
41 using dsec = duration<double>;
42 if constexpr (std::is_floating_point_v<Rep>)
43 {
44 // NaN fails both clamp comparisons and would reach the
45 // cast; treat it as no wait rather than undefined behavior.
46
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 5 not taken.
1x if (dur != dur)
47 1x return nanoseconds::zero();
48 }
49 4840x return dsec(dur) >= dsec((nanoseconds::max)())
50
2/4
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 4839 times.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
4840x ? (nanoseconds::max)()
51 9679x : dsec(dur) <= dsec((nanoseconds::min)())
52
2/4
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 4838 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
4839x ? (nanoseconds::min)()
53 4840x : duration_cast<nanoseconds>(dur);
54 }
55
56 // A non-io_context executor cannot supply a timer service, and
57 // await_suspend is driven through a noexcept wrapper, so translate
58 // the service-lookup failure into a clear terminate.
59 inline void
60 2834x emplace_delay_timer(
61 std::optional<timer>& t, capy::execution_context& ctx)
62 {
63 try
64 {
65
1/1
✓ Branch 2 → 3 taken 2833 times.
2834x t.emplace(ctx);
66 }
67
1/3
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
✗ Branch 4 → 8 not taken.
1x catch(std::logic_error const&)
68 {
69 1x throw_logic_error(
70 "delay requires an io_context-backed executor");
71 1x }
72 catch(std::exception const& e)
73 {
74 throw_logic_error(e.what());
75 }
76 2833x }
77
78 } // namespace detail
79
80 /** IoAwaitable returned by @ref delay.
81
82 Suspends the calling coroutine until the deadline elapses or
83 the environment's stop token is activated, whichever comes
84 first. A deadline already elapsed at suspension, or a stop
85 token already active, resumes the coroutine inline, without
86 starting a timer (see Cancellation below). Otherwise the
87 coroutine resumes through the executor once the timer fires
88 or a mid-wait cancellation arrives.
89
90 Not intended to be named directly; use the @ref delay factory
91 overloads instead.
92
93 @par Preconditions
94 The awaiting coroutine's executor must belong to an
95 `io_context`. Any other execution context terminates with a
96 diagnostic, because silently running without a timer would
97 drop the requested delay.
98
99 @par Cancellation
100 If stop is already requested before suspension, the coroutine
101 resumes immediately with `error::canceled`. If stop is
102 requested while suspended, the pending wait is cancelled and
103 the coroutine resumes with `error::canceled`. Requesting stop
104 from another thread while the io_context runs in
105 single_threaded mode (auto-enabled at concurrency_hint == 1)
106 is not permitted by io_context's threading rules;
107 cross-thread cancellation requires a multi-threaded-capable
108 context.
109
110 @see delay
111 */
112 class delay_awaitable
113 {
114 // wait() names timer's private awaitable type; decltype is
115 // the only way to store it here.
116 using wait_type = decltype(std::declval<detail::timer&>().wait());
117
118 std::chrono::steady_clock::time_point deadline_{};
119 std::chrono::nanoseconds dur_{};
120 bool has_deadline_ = false;
121 bool canceled_ = false;
122 std::optional<detail::timer> timer_;
123 std::optional<wait_type> wait_;
124
125 public:
126 /// Construct an awaitable that waits for `dur` nanoseconds.
127 4831x explicit delay_awaitable(std::chrono::nanoseconds dur) noexcept
128 4831x : dur_(dur)
129 {
130 4831x }
131
132 /// Construct an awaitable that waits until `tp`.
133 13x explicit delay_awaitable(
134 std::chrono::steady_clock::time_point tp) noexcept
135 13x : deadline_(tp)
136 13x , has_deadline_(true)
137 {
138 13x }
139
140 /// Construct by transferring state from `other`.
141 // Only moved before await_suspend; wait_ is engaged after.
142 5858x delay_awaitable(delay_awaitable&&) = default;
143
144 delay_awaitable(delay_awaitable const&) = delete;
145 delay_awaitable& operator=(delay_awaitable const&) = delete;
146 delay_awaitable& operator=(delay_awaitable&&) = delete;
147
148 /// Return false unconditionally; see await_suspend.
149 // The elapsed-deadline fast path must run after the stop-token
150 // check, and only await_suspend receives the env carrying it.
151 4843x bool await_ready() const noexcept
152 {
153 4843x return false;
154 }
155
156 /// Resume inline if stopped or elapsed; else wait on a timer.
157 std::coroutine_handle<>
158 4844x await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
159 {
160
2/2
✓ Branch 3 → 4 taken 2011 times.
✓ Branch 3 → 5 taken 2833 times.
4844x if(env->stop_token.stop_requested())
161 {
162 2011x canceled_ = true;
163 2011x return h;
164 }
165
166 // Elapsed deadlines complete synchronously, but only once a
167 // pending stop request has already been ruled out above.
168
2/2
✓ Branch 5 → 6 taken 12 times.
✓ Branch 5 → 10 taken 2821 times.
5654x if(has_deadline_ ?
169
3/3
✓ Branch 7 → 8 taken 12 times.
✓ Branch 12 → 13 taken 4 times.
✓ Branch 12 → 14 taken 2829 times.
2833x deadline_ <= std::chrono::steady_clock::now() :
170 2821x dur_.count() <= 0)
171 4x return h;
172
173 2829x detail::emplace_delay_timer(timer_, env->executor.context());
174
175
2/2
✓ Branch 16 → 17 taken 11 times.
✓ Branch 16 → 19 taken 2817 times.
2828x if(has_deadline_)
176 11x timer_->expires_at(deadline_);
177 else
178 2817x timer_->expires_after(dur_);
179
180 2828x wait_.emplace(timer_->wait());
181 2828x return wait_->await_suspend(h, env);
182 }
183
184 /// Return empty on expiry, `error::canceled` if stop won.
185 4831x [[nodiscard]] capy::io_result<> await_resume() noexcept
186 {
187
2/2
✓ Branch 2 → 3 taken 2011 times.
✓ Branch 2 → 5 taken 2820 times.
4831x if(canceled_)
188 2011x return {capy::error::canceled};
189
2/2
✓ Branch 6 → 7 taken 2816 times.
✓ Branch 6 → 9 taken 4 times.
2820x if(wait_)
190 2816x return wait_->await_resume();
191 4x return {};
192 }
193 };
194
195 /** IoAwaitable returned by the clock overloads of @ref delay.
196
197 Suspends the calling coroutine until `Clock::now()` reaches the
198 deadline or the environment's stop token is activated. The wait
199 is a sequence of steady-clock timer waits: after each expiry the
200 clock is re-read and, if the deadline is unreached, the same
201 frame-embedded waiter is re-published for the next
202 `Traits::to_wait_duration` cap — without resuming the coroutine
203 and without allocating.
204
205 Not intended to be named directly; use the @ref delay factory
206 overloads instead.
207
208 @par Preconditions
209 The awaiting coroutine's executor must belong to an
210 `io_context`. Any other execution context terminates with a
211 diagnostic, because silently running without a timer would
212 drop the requested delay.
213
214 @par Cancellation
215 Identical to @ref delay_awaitable: stop already requested
216 resumes inline with `error::canceled`; stop while suspended
217 cancels the pending wait, including between re-arms.
218
219 @see delay, wait_traits
220 */
221 template<class Clock, class Traits>
222 class clock_delay_awaitable
223 {
224 typename Clock::time_point deadline_{};
225 bool canceled_ = false;
226 std::optional<detail::timer> timer_;
227 detail::waiter_node w_;
228
229 std::chrono::nanoseconds
230 11x next_wait(typename Clock::time_point now) const noexcept
231 {
232 11x return detail::clamp_to_ns(
233 22x Traits::to_wait_duration(deadline_ - now));
234 }
235
236 // Runs on the scheduler thread executing the completion op,
237 // before the continuation is posted, so the frame cannot die
238 // concurrently.
239 10x static bool on_fire(void* ctx) noexcept
240 {
241 10x auto* self = static_cast<clock_delay_awaitable*>(ctx);
242 // Canceled: resume and surface the error
243
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 9 times.
10x if(self->w_.ec_)
244 1x return false;
245 9x auto now = Clock::now();
246
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 6 times.
9x if(now >= self->deadline_)
247 3x return false;
248 // Re-publish and return without touching the node again:
249 // the wait may complete on another thread immediately after.
250
1/2
✓ Branch 13 → 14 taken 6 times.
✗ Branch 13 → 15 not taken.
6x if(self->timer_->rearm_wait(self->w_, self->next_wait(now)))
251 6x return true;
252 // Heap growth failed; finish the wait with an error rather
253 // than strand the frame with an unbalanced work count.
254 self->w_.ec_ = std::make_error_code(std::errc::not_enough_memory);
255 return false;
256 }
257
258 public:
259 /// Construct an awaitable that waits until `tp` on `Clock`.
260 508x explicit clock_delay_awaitable(
261 typename Clock::time_point tp) noexcept
262 508x : deadline_(tp)
263 {
264 508x }
265
266 /// Construct by transferring the deadline from `other`.
267 // Only moved before await_suspend; w_ is quiescent until then.
268 508x clock_delay_awaitable(clock_delay_awaitable&& other) noexcept
269 508x : deadline_(other.deadline_)
270 {
271 508x }
272
273 clock_delay_awaitable(clock_delay_awaitable const&) = delete;
274 clock_delay_awaitable&
275 operator=(clock_delay_awaitable const&) = delete;
276 clock_delay_awaitable&
277 operator=(clock_delay_awaitable&&) = delete;
278
279 /// Return false unconditionally; see await_suspend.
280 // The elapsed-deadline fast path must run after the stop-token
281 // check, and only await_suspend receives the env carrying it.
282 508x bool await_ready() const noexcept
283 {
284 508x return false;
285 }
286
287 /// Resume inline if stopped or reached; else wait on a timer.
288 std::coroutine_handle<>
289 508x await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
290 {
291
2/2
✓ Branch 3 → 4 taken 502 times.
✓ Branch 3 → 5 taken 6 times.
508x if(env->stop_token.stop_requested())
292 {
293 502x canceled_ = true;
294 502x return h;
295 }
296
297 6x auto now = Clock::now();
298
3/3
✓ Branch 6 → 7 taken 6 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 5 times.
6x if(now >= deadline_)
299 1x return h;
300
301
1/1
✓ Branch 11 → 12 taken 5 times.
5x detail::emplace_delay_timer(timer_, env->executor.context());
302
303
1/1
✓ Branch 14 → 15 taken 5 times.
5x timer_->expires_after(next_wait(now));
304
305 5x w_.bind(h, *env);
306 5x w_.on_fire_ = &on_fire;
307 5x w_.on_fire_ctx_ = this;
308 // Never the elapsed fast path: a capped expiry that elapses
309 // before publication must still reach on_fire, not complete
310 // the clock wait early.
311
1/1
✓ Branch 17 → 18 taken 5 times.
5x return timer_->publish_wait(w_);
312 }
313
314 /// Return empty on deadline, `error::canceled` if stop won.
315 507x [[nodiscard]] capy::io_result<> await_resume() noexcept
316 {
317
2/2
✓ Branch 2 → 3 taken 502 times.
✓ Branch 2 → 5 taken 5 times.
507x if(canceled_)
318 502x return {capy::error::canceled};
319
2/2
✓ Branch 6 → 7 taken 4 times.
✓ Branch 6 → 8 taken 1 time.
5x if(timer_)
320 4x return {w_.ec_};
321 1x return {};
322 }
323 };
324
325 /** Suspend the current coroutine for a duration.
326
327 Returns an IoAwaitable that completes at or after the
328 specified duration, or earlier if the environment's stop
329 token is activated. Zero or negative durations complete
330 synchronously.
331
332 @par Example
333 @code
334 auto [ec] = co_await delay(std::chrono::milliseconds(100));
335 @endcode
336
337 @param dur The duration to wait.
338
339 @return A @ref delay_awaitable yielding `io_result<>`.
340 */
341 template<typename Rep, typename Period>
342 [[nodiscard]] delay_awaitable
343 4830x delay(std::chrono::duration<Rep, Period> dur) noexcept
344 {
345 4830x return delay_awaitable(detail::clamp_to_ns(dur));
346 }
347
348 /** Suspend the current coroutine until a time point.
349
350 Returns an IoAwaitable that completes at or after `tp`, or
351 earlier if the environment's stop token is activated. Time
352 points already reached complete synchronously.
353
354 @param tp The steady-clock time point to wait until.
355
356 @return A @ref delay_awaitable yielding `io_result<>`.
357 */
358 [[nodiscard]] inline delay_awaitable
359 13x delay(std::chrono::steady_clock::time_point tp) noexcept
360 {
361 13x return delay_awaitable(tp);
362 }
363
364 /** Suspend the current coroutine until a time point on `Clock`.
365
366 Returns an IoAwaitable that completes at or after the first
367 observation of `Clock::now() >= tp`, or earlier if the
368 environment's stop token is activated. The wait is one or more
369 bounded steady-clock waits, re-reading `Clock::now()` after
370 each; `Traits::to_wait_duration` bounds each one. With the
371 default @ref wait_traits a single full-length wait is used, so
372 an adjustment of `Clock` mid-wait is observed only at natural
373 wakeup; supply capping traits to bound that latency. Time
374 points already reached complete synchronously.
375
376 @note `Clock::now()` and `Traits::to_wait_duration` are invoked
377 on the io_context's run thread and must not throw or block.
378
379 @par Example
380 @code
381 auto [ec] = co_await delay(
382 std::chrono::system_clock::now() + std::chrono::minutes(5));
383 @endcode
384
385 @tparam Traits The wait-traits policy; `void` selects
386 @ref wait_traits.
387
388 @param tp The time point to wait until.
389
390 @return A @ref clock_delay_awaitable yielding `io_result<>`.
391 */
392 template<class Traits = void, class Clock, class Duration>
393 requires (!std::same_as<Clock, std::chrono::steady_clock>) &&
394 (std::is_void_v<Traits> || WaitTraits<Traits, Clock>)
395 [[nodiscard]] auto
396 508x delay(std::chrono::time_point<Clock, Duration> tp) noexcept
397 {
398 using traits_type = std::conditional_t<
399 std::is_void_v<Traits>, wait_traits<Clock>, Traits>;
400 // ceil preserves completes-at-or-after when Duration is coarser
401 // than the clock's native duration
402 return clock_delay_awaitable<Clock, traits_type>(
403 508x std::chrono::ceil<typename Clock::duration>(tp));
404 }
405
406 } // namespace boost::corosio
407
408 #endif
409