include/boost/corosio/detail/timeout_coro.hpp

97.0% Lines (32 / 33) 54.5% Branches (24 / 44) 93.3% Functions (14 / 15)
timeout_coro.hpp
f(x) Functions (15)
Function Calls Lines Branches Blocks
boost::corosio::detail::timeout_coro::promise_type::promise_type() :55 4109x 100.0% – 100.0% boost::corosio::detail::timeout_coro::promise_type::~promise_type() :55 4090x 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*) :71 2056x 100.0% – 100.0% boost::corosio::detail::timeout_coro::promise_type::get_return_object() :81 2054x 100.0% 50.0% 66.0% boost::corosio::detail::timeout_coro::promise_type::initial_suspend() :87 2055x 100.0% – 100.0% boost::corosio::detail::timeout_coro::promise_type::final_suspend() :91 2042x 100.0% – 100.0% boost::corosio::detail::timeout_coro::promise_type::return_void() :95 2042x 100.0% – 100.0% boost::corosio::detail::timeout_coro::promise_type::unhandled_exception() :96 0 0.0% – 0.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::~transform_awaiter() :99 4086x 100.0% – 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_ready() :104 2056x 100.0% – 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_resume() :109 2032x 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>) :117 2057x 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&&) :132 2053x 100.0% – 100.0% boost::corosio::detail::timeout_coro::timeout_coro(std::__1::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :151 4106x 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) :187 2056x 100.0% 55.0% 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/context_awaitable.hpp>
15 #include <boost/capy/ex/frame_allocator.hpp>
16 #include <boost/capy/ex/context_awaitable_promise_base.hpp>
17 #include <boost/capy/ex/await_context.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 await_context 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 await_context into
37 ContextAwaitable co_await expressions.
38
39 The detached frame can outlive the awaiting chain, so it never
40 allocates from that chain's frame allocator: timeout_awaitable
41 creates it under the process-wide recycling resource. */
42
43 namespace boost::corosio::detail {
44
45 /** Fire-and-forget coroutine backing `timeout()`.
46
47 The coroutine awaits a timer and signals a stop_source if the
48 timer fires without being cancelled. It self-destroys at
49 final_suspend via suspend_never.
50
51 @see make_timeout
52 */
53 struct timeout_coro
54 {
55 struct promise_type : capy::context_awaitable_promise_base<promise_type>
56 {
57 io_context::executor_type owned_ex_;
58 capy::await_context env_storage_;
59
60 /** Store an owned copy of the environment.
61
62 The timeout coroutine can outlive the awaiting chain
63 that created it, so it must own its env rather than
64 pointing to external storage. The executor is owned by
65 value: the chain's env holds only a ref to chain-owned
66 executor storage, and the timer waiter's completion can
67 run after that storage is gone. The ref handed to the
68 waiter points at `owned_ex_` in this frame, which lives
69 until final_suspend, after the waiter has completed.
70 */
71 2056x void set_env_owned(
72 io_context::executor_type ex,
73 std::stop_token token,
74 std::pmr::memory_resource* alloc)
75 {
76 2056x owned_ex_ = ex;
77 2056x env_storage_ = {owned_ex_, std::move(token), alloc};
78 set_environment(&env_storage_);
79 }
80
81 2054x timeout_coro get_return_object() noexcept
82 {
83 2054x return timeout_coro{
84
1/2
✓ Branch 0 taken 2054 times.
✗ Branch 1 not taken.
2054x std::coroutine_handle<promise_type>::from_promise(*this)};
85 }
86
87 2055x std::suspend_always initial_suspend() noexcept
88 {
89 2055x return {};
90 }
91 2042x std::suspend_never final_suspend() noexcept
92 {
93 2042x return {};
94 }
95 2042x void return_void() noexcept {}
96 ✗ void unhandled_exception() noexcept {}
97
98 template<class Awaitable>
99 struct transform_awaiter
100 {
101 std::decay_t<Awaitable> a_;
102 promise_type* p_;
103
104 2056x bool await_ready() noexcept
105 {
106 2056x return a_.await_ready();
107 }
108
109 2032x decltype(auto) await_resume()
110 {
111 2032x capy::set_current_frame_allocator(
112 2032x p_->environment()->frame_allocator);
113 2032x return a_.await_resume();
114 }
115
116 template<class Promise>
117 2057x auto await_suspend(std::coroutine_handle<Promise> h) noexcept
118 {
119 #ifdef _MSC_VER
120 using R = decltype(a_.await_suspend(h, p_->environment()));
121 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
122 a_.await_suspend(h, p_->environment()).resume();
123 else
124 return a_.await_suspend(h, p_->environment());
125 #else
126
1/2
✓ Branch 0 taken 2057 times.
✗ Branch 1 not taken.
2057x return a_.await_suspend(h, p_->environment());
127 #endif
128 }
129 };
130
131 template<class Awaitable>
132 2053x auto transform_awaitable(Awaitable&& a)
133 {
134 using A = std::decay_t<Awaitable>;
135 if constexpr (capy::ContextAwaitable<A>)
136 {
137 4106x return transform_awaiter<Awaitable>{
138 2053x std::forward<Awaitable>(a), this};
139 }
140 else
141 {
142 static_assert(sizeof(A) == 0, "requires ContextAwaitable");
143 }
144 }
145 };
146
147 std::coroutine_handle<promise_type> h_;
148
149 timeout_coro() noexcept : h_(nullptr) {}
150
151 4106x explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
152 2053x : h_(h)
153 2053x {
154 4106x }
155
156 // Self-destroying via suspend_never at final_suspend
157 ~timeout_coro() = default;
158
159 timeout_coro(timeout_coro const&) = delete;
160 timeout_coro& operator=(timeout_coro const&) = delete;
161
162 timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
163 {
164 o.h_ = nullptr;
165 }
166
167 timeout_coro& operator=(timeout_coro&& o) noexcept
168 {
169 h_ = o.h_;
170 o.h_ = nullptr;
171 return *this;
172 }
173 };
174
175 /** Create a fire-and-forget timeout coroutine.
176
177 Wait on the timer. If it fires without cancellation, signal
178 the stop source to cancel the paired inner operation.
179
180 @tparam Timer The timer type, typically `detail::timer`.
181
182 @param t The timer to wait on (must have expiry set).
183 @param src Stop source to signal on timeout.
184 */
185 template<typename Timer>
186 timeout_coro
187
10/20
✓ Branch 0 taken 2054 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2054 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4090 times.
✓ Branch 5 taken 6146 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2040 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 2039 times.
✓ Branch 16 taken 4095 times.
✓ Branch 17 taken 2041 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
10238x make_timeout(Timer& t, std::stop_source src)
188 2056x {
189
10/18
✓ Branch 0 taken 6146 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2056 times.
✓ Branch 3 taken 4090 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2056 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 8 taken 2040 times.
✗ Branch 9 not taken.
✓ Branch 9 taken 2056 times.
✗ Branch 10 not taken.
✓ Branch 10 taken 1 time.
✗ Branch 11 not taken.
✓ Branch 11 taken 2045 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
6146x auto [ec] = co_await t.wait();
190
2/2
✓ Branch 0 taken 1360 times.
✓ Branch 1 taken 685 times.
2045x if (!ec)
191 685x src.request_stop();
192 6158x }
193
194 } // namespace boost::corosio::detail
195
196 #endif
197