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