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 : 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 HIT 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
|