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/context_awaitable.hpp>
15 : #include <boost/capy/ex/frame_allocator.hpp>
16 : #include <boost/capy/ex/context_awaitable_promise_base.hpp>
17 : #include <boost/capy/ex/await_context.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 await_context 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 await_context into
37 : ContextAwaitable co_await expressions.
38 :
39 : The detached frame can outlive the awaiting chain, so it never
40 : allocates from that chain's frame allocator: timeout_awaitable
41 : creates it under the process-wide recycling resource. */
42 :
43 : namespace boost::corosio::detail {
44 :
45 : /** Fire-and-forget coroutine backing `timeout()`.
46 :
47 : The coroutine awaits a timer and signals a stop_source if the
48 : timer fires without being cancelled. It self-destroys at
49 : final_suspend via suspend_never.
50 :
51 : @see make_timeout
52 : */
53 : struct timeout_coro
54 : {
55 : struct promise_type : capy::context_awaitable_promise_base<promise_type>
56 : {
57 : io_context::executor_type owned_ex_;
58 : capy::await_context env_storage_;
59 :
60 : /** Store an owned copy of the environment.
61 :
62 : The timeout coroutine can outlive the awaiting chain
63 : that created it, so it must own its env rather than
64 : pointing to external storage. The executor is owned by
65 : value: the chain's env holds only a ref to chain-owned
66 : executor storage, and the timer waiter's completion can
67 : run after that storage is gone. The ref handed to the
68 : waiter points at `owned_ex_` in this frame, which lives
69 : until final_suspend, after the waiter has completed.
70 : */
71 HIT 2057 : void set_env_owned(
72 : io_context::executor_type ex,
73 : std::stop_token token,
74 : std::pmr::memory_resource* alloc)
75 : {
76 2057 : owned_ex_ = ex;
77 4114 : env_storage_ = {owned_ex_, std::move(token), alloc};
78 2057 : set_environment(&env_storage_);
79 4114 : }
80 :
81 2057 : timeout_coro get_return_object() noexcept
82 : {
83 : return timeout_coro{
84 2057 : std::coroutine_handle<promise_type>::from_promise(*this)};
85 : }
86 :
87 2057 : std::suspend_always initial_suspend() noexcept
88 : {
89 2057 : return {};
90 : }
91 2051 : std::suspend_never final_suspend() noexcept
92 : {
93 2051 : return {};
94 : }
95 2051 : void return_void() noexcept {}
96 MIS 0 : void unhandled_exception() noexcept {}
97 :
98 : template<class Awaitable>
99 : struct transform_awaiter
100 : {
101 : std::decay_t<Awaitable> a_;
102 : promise_type* p_;
103 :
104 HIT 2057 : bool await_ready() noexcept
105 : {
106 2057 : return a_.await_ready();
107 : }
108 :
109 2051 : decltype(auto) await_resume()
110 : {
111 2051 : capy::set_current_frame_allocator(
112 2051 : p_->environment()->frame_allocator);
113 2051 : return a_.await_resume();
114 : }
115 :
116 : template<class Promise>
117 2057 : auto await_suspend(std::coroutine_handle<Promise> h) noexcept
118 : {
119 : #ifdef _MSC_VER
120 : using R = decltype(a_.await_suspend(h, p_->environment()));
121 : if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
122 : a_.await_suspend(h, p_->environment()).resume();
123 : else
124 : return a_.await_suspend(h, p_->environment());
125 : #else
126 2057 : return a_.await_suspend(h, p_->environment());
127 : #endif
128 : }
129 : };
130 :
131 : template<class Awaitable>
132 2057 : auto transform_awaitable(Awaitable&& a)
133 : {
134 : using A = std::decay_t<Awaitable>;
135 : if constexpr (capy::ContextAwaitable<A>)
136 : {
137 : return transform_awaiter<Awaitable>{
138 4114 : std::forward<Awaitable>(a), this};
139 : }
140 : else
141 : {
142 : static_assert(sizeof(A) == 0, "requires ContextAwaitable");
143 : }
144 2057 : }
145 : };
146 :
147 : std::coroutine_handle<promise_type> h_;
148 :
149 : timeout_coro() noexcept : h_(nullptr) {}
150 :
151 2057 : explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
152 2057 : : h_(h)
153 : {
154 2057 : }
155 :
156 : // Self-destroying via suspend_never at final_suspend
157 : ~timeout_coro() = default;
158 :
159 : timeout_coro(timeout_coro const&) = delete;
160 : timeout_coro& operator=(timeout_coro const&) = delete;
161 :
162 : timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
163 : {
164 : o.h_ = nullptr;
165 : }
166 :
167 : timeout_coro& operator=(timeout_coro&& o) noexcept
168 : {
169 : h_ = o.h_;
170 : o.h_ = nullptr;
171 : return *this;
172 : }
173 : };
174 :
175 : /** Create a fire-and-forget timeout coroutine.
176 :
177 : Wait on the timer. If it fires without cancellation, signal
178 : the stop source to cancel the paired inner operation.
179 :
180 : @tparam Timer The timer type, typically `detail::timer`.
181 :
182 : @param t The timer to wait on (must have expiry set).
183 : @param src Stop source to signal on timeout.
184 : */
185 : template<typename Timer>
186 : timeout_coro
187 2057 : make_timeout(Timer& t, std::stop_source src)
188 : {
189 : auto [ec] = co_await t.wait();
190 : if (!ec)
191 : src.request_stop();
192 4114 : }
193 :
194 : } // namespace boost::corosio::detail
195 :
196 : #endif
|