include/boost/corosio/detail/timeout_coro.hpp

100.0% Lines (27/27) 100.0% List of functions (11/11)
timeout_coro.hpp
f(x) Functions (11)
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*) :72 2040x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::get_return_object() :82 2040x 100.0% 100.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::transform_awaiter<boost::corosio::detail::timer::wait_awaitable>::await_ready() :105 2040x 100.0% 100.0% boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::timer::wait_awaitable>::await_resume() :110 2036x 100.0% 100.0% auto boost::corosio::detail::timeout_coro::promise_type::transform_awaiter<boost::corosio::detail::timer::wait_awaitable>::await_suspend<boost::corosio::detail::timeout_coro::promise_type>(std::__n4861::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :118 2040x 100.0% 100.0% auto boost::corosio::detail::timeout_coro::promise_type::transform_awaitable<boost::corosio::detail::timer::wait_awaitable>(boost::corosio::detail::timer::wait_awaitable&&) :133 2040x 100.0% 100.0% boost::corosio::detail::timeout_coro::timeout_coro(std::__n4861::coroutine_handle<boost::corosio::detail::timeout_coro::promise_type>) :152 2040x 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) :188 2040x 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/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 2040x void set_env_owned(
73 io_context::executor_type ex,
74 std::stop_token token,
75 std::pmr::memory_resource* alloc)
76 {
77 2040x owned_ex_ = ex;
78 4080x env_storage_ = {owned_ex_, std::move(token), alloc};
79 2040x set_environment(&env_storage_);
80 4080x }
81
82 2040x timeout_coro get_return_object() noexcept
83 {
84 return timeout_coro{
85 2040x 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 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 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 2040x explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
153 2040x : h_(h)
154 {
155 2040x }
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 2040x make_timeout(Timer& t, std::stop_source src)
189 {
190 auto [ec] = co_await t.wait();
191 if (!ec)
192 src.request_stop();
193 4080x }
194
195 } // namespace boost::corosio::detail
196
197 #endif
198