include/boost/corosio/detail/timeout_coro.hpp

96.6% Lines (28 / 29) 91.7% Functions (11 / 12)
timeout_coro.hpp
f(x) Functions (12)
Function Calls Lines Blocks
boost::corosio::detail::timeout_coro::promise_type::set_env_owned(boost::corosio::io_context::executor_type, std::stop_token, std::pmr::memory_resource*) :71 3084x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::get_return_object() :81 3084x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::initial_suspend() :87 3084x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::final_suspend() :91 3075x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::return_void() :95 3075x 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>::await_ready() :104 3084x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::wait_awaitable>::await_resume() :109 3075x 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::__n4861::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :117 3084x 100.0% 100.0% auto boost::corosio::detail::timeout_coro::promise_type::transform_awaitable<boost::corosio::detail::wait_awaitable>(boost::corosio::detail::wait_awaitable&&) :132 3084x 100.0% 100.0% boost::corosio::detail::timeout_coro::timeout_coro(std::__n4861::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :151 3084x 100.0% 100.0% boost::corosio::detail::timeout_coro boost::corosio::detail::make_timeout<boost::corosio::detail::timer>(boost::corosio::detail::timer&, std::stop_source) :187 3084x 100.0% 47.0%
Line 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 3084x void set_env_owned(
72 io_context::executor_type ex,
73 std::stop_token token,
74 std::pmr::memory_resource* alloc)
75 {
76 3084x owned_ex_ = ex;
77 6168x env_storage_ = {owned_ex_, std::move(token), alloc};
78 3084x set_environment(&env_storage_);
79 6168x }
80
81 3084x timeout_coro get_return_object() noexcept
82 {
83 return timeout_coro{
84 3084x std::coroutine_handle<promise_type>::from_promise(*this)};
85 }
86
87 3084x std::suspend_always initial_suspend() noexcept
88 {
89 3084x return {};
90 }
91 3075x std::suspend_never final_suspend() noexcept
92 {
93 3075x return {};
94 }
95 3075x 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 3084x bool await_ready() noexcept
105 {
106 3084x return a_.await_ready();
107 }
108
109 3075x decltype(auto) await_resume()
110 {
111 3075x capy::set_current_frame_allocator(
112 3075x p_->environment()->frame_allocator);
113 3075x return a_.await_resume();
114 }
115
116 template<class Promise>
117 3084x 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 3084x return a_.await_suspend(h, p_->environment());
127 #endif
128 }
129 };
130
131 template<class Awaitable>
132 3084x auto transform_awaitable(Awaitable&& a)
133 {
134 using A = std::decay_t<Awaitable>;
135 if constexpr (capy::ContextAwaitable<A>)
136 {
137 return transform_awaiter<Awaitable>{
138 6168x std::forward<Awaitable>(a), this};
139 }
140 else
141 {
142 static_assert(sizeof(A) == 0, "requires ContextAwaitable");
143 }
144 3084x }
145 };
146
147 std::coroutine_handle<promise_type> h_;
148
149 timeout_coro() noexcept : h_(nullptr) {}
150
151 3084x explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
152 3084x : h_(h)
153 {
154 3084x }
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 3084x make_timeout(Timer& t, std::stop_source src)
188 {
189 auto [ec] = co_await t.wait();
190 if (!ec)
191 src.request_stop();
192 6168x }
193
194 } // namespace boost::corosio::detail
195
196 #endif
197