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 +
        void set_env_owned(capy::io_env env)
 
61 +
        {
 
62 +
            env_storage_ = std::move(env);
 
63 +
            set_environment(&env_storage_);
 
64 +
        }
 
65 +

 
66 +
        timeout_coro get_return_object() noexcept
 
67 +
        {
 
68 +
            return timeout_coro{
 
69 +
                std::coroutine_handle<promise_type>::from_promise(
 
70 +
                    *this)};
 
71 +
        }
 
72 +

 
73 +
        std::suspend_always initial_suspend() noexcept { return {}; }
 
74 +
        std::suspend_never final_suspend() noexcept { return {}; }
 
75 +
        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 +
            bool await_ready() noexcept
 
85 +
            {
 
86 +
                return a_.await_ready();
 
87 +
            }
 
88 +

 
89 +
            decltype(auto) await_resume()
 
90 +
            {
 
91 +
                capy::set_current_frame_allocator(
 
92 +
                    p_->environment()->frame_allocator);
 
93 +
                return a_.await_resume();
 
94 +
            }
 
95 +

 
96 +
            template<class Promise>
 
97 +
            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 +
                return a_.await_suspend(h, p_->environment());
 
112 +
#endif
 
113 +
            }
 
114 +
        };
 
115 +

 
116 +
        template<class Awaitable>
 
117 +
        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 +
                    std::forward<Awaitable>(a), this};
 
124 +
            }
 
125 +
            else
 
126 +
            {
 
127 +
                static_assert(
 
128 +
                    sizeof(A) == 0, "requires IoAwaitable");
 
129 +
            }
 
130 +
        }
 
131 +
    };
 
132 +

 
133 +
    std::coroutine_handle<promise_type> h_;
 
134 +

 
135 +
    timeout_coro() noexcept : h_(nullptr) {}
 
136 +

 
137 +
    explicit timeout_coro(
 
138 +
        std::coroutine_handle<promise_type> h) noexcept
 
139 +
        : h_(h)
 
140 +
    {
 
141 +
    }
 
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 +
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 +
}
 
180 +

 
181 +
} // namespace boost::corosio::detail
 
182 +

 
183 +
#endif