include/boost/corosio/detail/timeout_coro.hpp

100.0% Lines (27/27) 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 : capy::io_awaitable_promise_base<promise_type>
50 {
51 capy::io_env env_storage_;
52
53 /** Store an owned copy of the environment.
54
55 The timeout coroutine can outlive the cancel_at_awaitable
56 that created it, so it must own its env rather than
57 pointing to external storage.
58 */
59 24 void set_env_owned(capy::io_env env)
60 {
61 24 env_storage_ = std::move(env);
62 24 set_environment(&env_storage_);
63 24 }
64
65 24 timeout_coro get_return_object() noexcept
66 {
67 return timeout_coro{
68 24 std::coroutine_handle<promise_type>::from_promise(*this)};
69 }
70
71 24 std::suspend_always initial_suspend() noexcept
72 {
73 24 return {};
74 }
75 24 std::suspend_never final_suspend() noexcept
76 {
77 24 return {};
78 }
79 24 void return_void() noexcept {}
80 void unhandled_exception() noexcept {}
81
82 template<class Awaitable>
83 struct transform_awaiter
84 {
85 std::decay_t<Awaitable> a_;
86 promise_type* p_;
87
88 24 bool await_ready() noexcept
89 {
90 24 return a_.await_ready();
91 }
92
93 24 decltype(auto) await_resume()
94 {
95 24 capy::set_current_frame_allocator(
96 24 p_->environment()->frame_allocator);
97 24 return a_.await_resume();
98 }
99
100 template<class Promise>
101 24 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
102 {
103 #ifdef _MSC_VER
104 using R = decltype(a_.await_suspend(h, p_->environment()));
105 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
106 a_.await_suspend(h, p_->environment()).resume();
107 else
108 return a_.await_suspend(h, p_->environment());
109 #else
110 24 return a_.await_suspend(h, p_->environment());
111 #endif
112 }
113 };
114
115 template<class Awaitable>
116 24 auto transform_awaitable(Awaitable&& a)
117 {
118 using A = std::decay_t<Awaitable>;
119 if constexpr (capy::IoAwaitable<A>)
120 {
121 return transform_awaiter<Awaitable>{
122 48 std::forward<Awaitable>(a), this};
123 }
124 else
125 {
126 static_assert(sizeof(A) == 0, "requires IoAwaitable");
127 }
128 24 }
129 };
130
131 std::coroutine_handle<promise_type> h_;
132
133 timeout_coro() noexcept : h_(nullptr) {}
134
135 24 explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
136 24 : h_(h)
137 {
138 24 }
139
140 // Self-destroying via suspend_never at final_suspend
141 ~timeout_coro() = default;
142
143 timeout_coro(timeout_coro const&) = delete;
144 timeout_coro& operator=(timeout_coro const&) = delete;
145
146 timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
147 {
148 o.h_ = nullptr;
149 }
150
151 timeout_coro& operator=(timeout_coro&& o) noexcept
152 {
153 h_ = o.h_;
154 o.h_ = nullptr;
155 return *this;
156 }
157 };
158
159 /** Create a fire-and-forget timeout coroutine.
160
161 Wait on the timer. If it fires without cancellation, signal
162 the stop source to cancel the paired inner operation.
163
164 @tparam Timer Timer type (`timer` or `native_timer<B>`).
165
166 @param t The timer to wait on (must have expiry set).
167 @param src Stop source to signal on timeout.
168 */
169 template<typename Timer>
170 timeout_coro
171 24 make_timeout(Timer& t, std::stop_source src)
172 {
173 auto [ec] = co_await t.wait();
174 if (!ec)
175 src.request_stop();
176 48 }
177
178 } // namespace boost::corosio::detail
179
180 #endif
181