include/boost/corosio/detail/timeout_coro.hpp

100.0% Lines (25/25) 100.0% Functions (11/11)
include/boost/corosio/detail/timeout_coro.hpp
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/capy/concept/io_awaitable.hpp>
14 #include <boost/capy/ex/frame_allocator.hpp>
15 #include <boost/capy/ex/io_awaitable_promise_base.hpp>
16 #include <boost/capy/ex/io_env.hpp>
17
18 #include <coroutine>
19 #include <stop_token>
20 #include <type_traits>
21 #include <utility>
22
23 /* Self-destroying coroutine that awaits a timer and signals a
24 stop_source on expiry. Created suspended (initial_suspend =
25 suspend_always); the caller sets an owned io_env copy then
26 resumes, which runs synchronously until the timer wait suspends.
27 At final_suspend, suspend_never destroys the frame — the
28 timeout_coro destructor is intentionally a no-op since the
29 handle is dangling after self-destruction. If the coroutine is
30 still suspended at shutdown, the timer service drains it via
31 completion_op::destroy().
32
33 The promise reuses task<>'s transform_awaiter pattern (including
34 the MSVC symmetric-transfer workaround) to inject io_env into
35 IoAwaitable co_await expressions. */
36
37 namespace boost::corosio::detail {
38
39 /** Fire-and-forget coroutine for the timeout side of cancel_at.
40
41 The coroutine awaits a timer and signals a stop_source if the
42 timer fires without being cancelled. It self-destroys at
43 final_suspend via suspend_never.
44
45 @see make_timeout
46 */
47 struct timeout_coro
48 {
49 struct promise_type
50 : capy::io_awaitable_promise_base<promise_type>
51 {
52 capy::io_env env_storage_;
53
54 /** Store an owned copy of the environment.
55
56 The timeout coroutine can outlive the cancel_at_awaitable
57 that created it, so it must own its env rather than
58 pointing to external storage.
59 */
60 24 void set_env_owned(capy::io_env env)
61 {
62 24 env_storage_ = std::move(env);
63 24 set_environment(&env_storage_);
64 24 }
65
66 24 timeout_coro get_return_object() noexcept
67 {
68 return timeout_coro{
69 std::coroutine_handle<promise_type>::from_promise(
70 24 *this)};
71 }
72
73 24 std::suspend_always initial_suspend() noexcept { return {}; }
74 24 std::suspend_never final_suspend() noexcept { return {}; }
75 24 void return_void() noexcept {}
76 void unhandled_exception() noexcept {}
77
78 template<class Awaitable>
79 struct transform_awaiter
80 {
81 std::decay_t<Awaitable> a_;
82 promise_type* p_;
83
84 24 bool await_ready() noexcept
85 {
86 24 return a_.await_ready();
87 }
88
89 24 decltype(auto) await_resume()
90 {
91 24 capy::set_current_frame_allocator(
92 24 p_->environment()->frame_allocator);
93 24 return a_.await_resume();
94 }
95
96 template<class Promise>
97 24 auto await_suspend(
98 std::coroutine_handle<Promise> h) noexcept
99 {
100 #ifdef _MSC_VER
101 using R = decltype(
102 a_.await_suspend(h, p_->environment()));
103 if constexpr (std::is_same_v<
104 R, std::coroutine_handle<>>)
105 a_.await_suspend(h, p_->environment())
106 .resume();
107 else
108 return a_.await_suspend(
109 h, p_->environment());
110 #else
111 24 return a_.await_suspend(h, p_->environment());
112 #endif
113 }
114 };
115
116 template<class Awaitable>
117 24 auto transform_awaitable(Awaitable&& a)
118 {
119 using A = std::decay_t<Awaitable>;
120 if constexpr (capy::IoAwaitable<A>)
121 {
122 return transform_awaiter<Awaitable>{
123 48 std::forward<Awaitable>(a), this};
124 }
125 else
126 {
127 static_assert(
128 sizeof(A) == 0, "requires IoAwaitable");
129 }
130 24 }
131 };
132
133 std::coroutine_handle<promise_type> h_;
134
135 timeout_coro() noexcept : h_(nullptr) {}
136
137 24 explicit timeout_coro(
138 std::coroutine_handle<promise_type> h) noexcept
139 24 : h_(h)
140 {
141 24 }
142
143 // Self-destroying via suspend_never at final_suspend
144 ~timeout_coro() = default;
145
146 timeout_coro(timeout_coro const&) = delete;
147 timeout_coro& operator=(timeout_coro const&) = delete;
148
149 timeout_coro(timeout_coro&& o) noexcept
150 : h_(o.h_)
151 {
152 o.h_ = nullptr;
153 }
154
155 timeout_coro& operator=(timeout_coro&& o) noexcept
156 {
157 h_ = o.h_;
158 o.h_ = nullptr;
159 return *this;
160 }
161 };
162
163 /** Create a fire-and-forget timeout coroutine.
164
165 Wait on the timer. If it fires without cancellation, signal
166 the stop source to cancel the paired inner operation.
167
168 @tparam Timer Timer type (`timer` or `native_timer<B>`).
169
170 @param t The timer to wait on (must have expiry set).
171 @param src Stop source to signal on timeout.
172 */
173 template<typename Timer>
174 24 timeout_coro make_timeout(Timer& t, std::stop_source src)
175 {
176 auto [ec] = co_await t.wait();
177 if (!ec)
178 src.request_stop();
179 48 }
180
181 } // namespace boost::corosio::detail
182
183 #endif
184