TLA Line data 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 HIT 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
|