include/boost/corosio/detail/timeout_coro.hpp

97.1% Lines (34/35) 93.3% List of functions (14/15) 52.3% Branches (23/44)
timeout_coro.hpp
f(x) Functions (15)
Function Calls Lines Branches Blocks
boost::corosio::detail::timeout_coro::promise_type::promise_type() :56 4102x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::~promise_type() :56 4098x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::set_env_owned(boost::corosio::io_context::executor_type, std::__1::stop_token, std::__1::pmr::memory_resource*) :72 2052x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::get_return_object() :82 2053x 100.0% 50.0% 66.0% boost::corosio::detail::timeout_coro::promise_type::initial_suspend() :88 2052x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::final_suspend() :92 2046x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::return_void() :96 2046x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::unhandled_exception() :97 0 0.0% 0.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::~transform_awaiter() :100 4100x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_ready() :105 2052x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_resume() :110 2049x 100.0% 100.0% auto boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_suspend<boost::corosio::detail::timeout_coro::promise_type>(std::__1::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :118 2052x 100.0% 50.0% 66.0% auto boost::corosio::detail::timeout_coro::promise_type::transform_awaitable<boost::corosio::detail::wait_awaitable>(boost::corosio::detail::wait_awaitable&&) :133 2053x 100.0% 100.0% boost::corosio::detail::timeout_coro::timeout_coro(std::__1::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :152 4103x 100.0% 100.0% boost::corosio::detail::timeout_coro boost::corosio::detail::make_timeout<boost::corosio::detail::timer>(boost::corosio::detail::timer&, std::__1::stop_source) :188 2051x 100.0% 52.5% 65.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_DETAIL_TIMEOUT_CORO_HPP
11 #define BOOST_COROSIO_DETAIL_TIMEOUT_CORO_HPP
12
13 #include <boost/corosio/io_context.hpp>
14 #include <boost/capy/concept/io_awaitable.hpp>
15 #include <boost/capy/ex/frame_allocator.hpp>
16 #include <boost/capy/ex/io_awaitable_promise_base.hpp>
17 #include <boost/capy/ex/io_env.hpp>
18
19 #include <coroutine>
20 #include <memory_resource>
21 #include <stop_token>
22 #include <type_traits>
23 #include <utility>
24
25 /* Self-destroying coroutine that awaits a timer and signals a
26 stop_source on expiry. Created suspended (initial_suspend =
27 suspend_always); the caller sets an owned io_env copy then
28 resumes, which runs synchronously until the timer wait suspends.
29 At final_suspend, suspend_never destroys the frame — the
30 timeout_coro destructor is intentionally a no-op since the
31 handle is dangling after self-destruction. If the coroutine is
32 still suspended at shutdown, the timer service drains it via
33 completion_op::destroy().
34
35 The promise reuses task<>'s transform_awaiter pattern (including
36 the MSVC symmetric-transfer workaround) to inject io_env into
37 IoAwaitable co_await expressions.
38
39 The detached frame allocates from the awaiting chain's frame
40 allocator and can outlive that chain, extending the allocator's
41 required lifetime until the io_context drains. This is safe with
42 the default recycling resource. */
43
44 namespace boost::corosio::detail {
45
46 /** Fire-and-forget coroutine backing `timeout()`.
47
48 The coroutine awaits a timer and signals a stop_source if the
49 timer fires without being cancelled. It self-destroys at
50 final_suspend via suspend_never.
51
52 @see make_timeout
53 */
54 struct timeout_coro
55 {
56 struct promise_type : capy::io_awaitable_promise_base<promise_type>
57 {
58 io_context::executor_type owned_ex_;
59 capy::io_env env_storage_;
60
61 /** Store an owned copy of the environment.
62
63 The timeout coroutine can outlive the awaiting chain
64 that created it, so it must own its env rather than
65 pointing to external storage. The executor is owned by
66 value: the chain's env holds only a ref to chain-owned
67 executor storage, and the timer waiter's completion can
68 run after that storage is gone. The ref handed to the
69 waiter points at `owned_ex_` in this frame, which lives
70 until final_suspend, after the waiter has completed.
71 */
72 2052x void set_env_owned(
73 io_context::executor_type ex,
74 std::stop_token token,
75 std::pmr::memory_resource* alloc)
76 {
77 2052x owned_ex_ = ex;
78 2052x env_storage_ = {owned_ex_, std::move(token), alloc};
79 2052x set_environment(&env_storage_);
80 2052x }
81
82 2053x timeout_coro get_return_object() noexcept
83 {
84 2053x return timeout_coro{
85
1/2
✓ Branch 0 taken 2053 times.
✗ Branch 1 not taken.
2053x std::coroutine_handle<promise_type>::from_promise(*this)};
86 }
87
88 2052x std::suspend_always initial_suspend() noexcept
89 {
90 2052x return {};
91 }
92 2046x std::suspend_never final_suspend() noexcept
93 {
94 2046x return {};
95 }
96 2046x void return_void() noexcept {}
97 void unhandled_exception() noexcept {}
98
99 template<class Awaitable>
100 struct transform_awaiter
101 {
102 std::decay_t<Awaitable> a_;
103 promise_type* p_;
104
105 2052x bool await_ready() noexcept
106 {
107 2052x return a_.await_ready();
108 }
109
110 2049x decltype(auto) await_resume()
111 {
112 2049x capy::set_current_frame_allocator(
113 2049x p_->environment()->frame_allocator);
114 2049x return a_.await_resume();
115 }
116
117 template<class Promise>
118 2052x auto await_suspend(std::coroutine_handle<Promise> h) noexcept
119 {
120 #ifdef _MSC_VER
121 using R = decltype(a_.await_suspend(h, p_->environment()));
122 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
123 a_.await_suspend(h, p_->environment()).resume();
124 else
125 return a_.await_suspend(h, p_->environment());
126 #else
127
1/2
✓ Branch 0 taken 2052 times.
✗ Branch 1 not taken.
2052x return a_.await_suspend(h, p_->environment());
128 #endif
129 }
130 };
131
132 template<class Awaitable>
133 2053x auto transform_awaitable(Awaitable&& a)
134 {
135 using A = std::decay_t<Awaitable>;
136 if constexpr (capy::IoAwaitable<A>)
137 {
138 4106x return transform_awaiter<Awaitable>{
139 2053x std::forward<Awaitable>(a), this};
140 }
141 else
142 {
143 static_assert(sizeof(A) == 0, "requires IoAwaitable");
144 }
145 }
146 };
147
148 std::coroutine_handle<promise_type> h_;
149
150 timeout_coro() noexcept : h_(nullptr) {}
151
152 4103x explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
153 2052x : h_(h)
154 2051x {
155 4103x }
156
157 // Self-destroying via suspend_never at final_suspend
158 ~timeout_coro() = default;
159
160 timeout_coro(timeout_coro const&) = delete;
161 timeout_coro& operator=(timeout_coro const&) = delete;
162
163 timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
164 {
165 o.h_ = nullptr;
166 }
167
168 timeout_coro& operator=(timeout_coro&& o) noexcept
169 {
170 h_ = o.h_;
171 o.h_ = nullptr;
172 return *this;
173 }
174 };
175
176 /** Create a fire-and-forget timeout coroutine.
177
178 Wait on the timer. If it fires without cancellation, signal
179 the stop source to cancel the paired inner operation.
180
181 @tparam Timer The timer type, typically `detail::timer`.
182
183 @param t The timer to wait on (must have expiry set).
184 @param src Stop source to signal on timeout.
185 */
186 template<typename Timer>
187 timeout_coro
188
9/20
✓ Branch 0 taken 2051 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2051 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4097 times.
✓ Branch 5 taken 6148 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2045 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 1 time.
✓ Branch 15 taken 2044 times.
✓ Branch 16 taken 4100 times.
✓ Branch 17 taken 2049 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
10252x make_timeout(Timer& t, std::stop_source src)
189 2051x {
190
10/18
✓ Branch 0 taken 6148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2052 times.
✓ Branch 3 taken 4096 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2052 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 8 taken 2048 times.
✗ Branch 9 not taken.
✓ Branch 9 taken 2052 times.
✗ Branch 10 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 11 taken 2048 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
6148x auto [ec] = co_await t.wait();
191
2/2
✓ Branch 0 taken 1364 times.
✓ Branch 1 taken 684 times.
2048x if (!ec)
192 684x src.request_stop();
193 6156x }
194
195 } // namespace boost::corosio::detail
196
197 #endif
198