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