include/boost/corosio/detail/timeout_coro.hpp

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