include/boost/corosio/detail/timeout_coro.hpp

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