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 4076x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::~promise_type() :56 4078x 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 2039x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::get_return_object() :82 2039x 100.0% 50.0% 66.0% boost::corosio::detail::timeout_coro::promise_type::initial_suspend() :88 2040x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::final_suspend() :92 2036x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::return_void() :96 2036x 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 4078x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_ready() :105 2040x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_resume() :110 2036x 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 2040x 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 2040x 100.0% 100.0% boost::corosio::detail::timeout_coro::timeout_coro(std::__1::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :152 4080x 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 2040x 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 2039x void set_env_owned(
73 io_context::executor_type ex,
74 std::stop_token token,
75 std::pmr::memory_resource* alloc)
76 {
77 2039x owned_ex_ = ex;
78 2039x env_storage_ = {owned_ex_, std::move(token), alloc};
79 2039x set_environment(&env_storage_);
80 2039x }
81
82 2039x timeout_coro get_return_object() noexcept
83 {
84 2039x return timeout_coro{
85
1/2
✓ Branch 0 taken 2039 times.
✗ Branch 1 not taken.
2039x std::coroutine_handle<promise_type>::from_promise(*this)};
86 }
87
88 2040x std::suspend_always initial_suspend() noexcept
89 {
90 2040x return {};
91 }
92 2036x std::suspend_never final_suspend() noexcept
93 {
94 2036x return {};
95 }
96 2036x 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 2040x bool await_ready() noexcept
106 {
107 2040x return a_.await_ready();
108 }
109
110 2036x decltype(auto) await_resume()
111 {
112 2036x capy::set_current_frame_allocator(
113 2036x p_->environment()->frame_allocator);
114 2036x return a_.await_resume();
115 }
116
117 template<class Promise>
118 2040x 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 2040 times.
✗ Branch 1 not taken.
2040x return a_.await_suspend(h, p_->environment());
128 #endif
129 }
130 };
131
132 template<class Awaitable>
133 2040x auto transform_awaitable(Awaitable&& a)
134 {
135 using A = std::decay_t<Awaitable>;
136 if constexpr (capy::IoAwaitable<A>)
137 {
138 4080x return transform_awaiter<Awaitable>{
139 2040x 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 4080x explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
153 2040x : h_(h)
154 2040x {
155 4080x }
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 2039 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2039 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4071 times.
✓ Branch 5 taken 6111 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2035 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 2035 times.
✓ Branch 16 taken 4075 times.
✓ Branch 17 taken 2035 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
10189x make_timeout(Timer& t, std::stop_source src)
189 2040x {
190
10/18
✓ Branch 0 taken 6111 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2040 times.
✓ Branch 3 taken 4071 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2040 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 8 taken 2035 times.
✗ Branch 9 not taken.
✓ Branch 9 taken 2040 times.
✗ Branch 10 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✓ Branch 11 taken 2036 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
6111x auto [ec] = co_await t.wait();
191
2/2
✓ Branch 0 taken 1356 times.
✓ Branch 1 taken 680 times.
2036x if (!ec)
192 680x src.request_stop();
193 6119x }
194
195 } // namespace boost::corosio::detail
196
197 #endif
198