1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_COROSIO_DETAIL_TIMEOUT_CORO_HPP
10  
#ifndef BOOST_COROSIO_DETAIL_TIMEOUT_CORO_HPP
11  
#define BOOST_COROSIO_DETAIL_TIMEOUT_CORO_HPP
11  
#define BOOST_COROSIO_DETAIL_TIMEOUT_CORO_HPP
12  

12  

13  
#include <boost/capy/concept/io_awaitable.hpp>
13  
#include <boost/capy/concept/io_awaitable.hpp>
14  
#include <boost/capy/ex/frame_allocator.hpp>
14  
#include <boost/capy/ex/frame_allocator.hpp>
15  
#include <boost/capy/ex/io_awaitable_promise_base.hpp>
15  
#include <boost/capy/ex/io_awaitable_promise_base.hpp>
16  
#include <boost/capy/ex/io_env.hpp>
16  
#include <boost/capy/ex/io_env.hpp>
17  

17  

18  
#include <coroutine>
18  
#include <coroutine>
19  
#include <stop_token>
19  
#include <stop_token>
20  
#include <type_traits>
20  
#include <type_traits>
21  
#include <utility>
21  
#include <utility>
22  

22  

23  
/* Self-destroying coroutine that awaits a timer and signals a
23  
/* Self-destroying coroutine that awaits a timer and signals a
24  
   stop_source on expiry. Created suspended (initial_suspend =
24  
   stop_source on expiry. Created suspended (initial_suspend =
25  
   suspend_always); the caller sets an owned io_env copy then
25  
   suspend_always); the caller sets an owned io_env copy then
26  
   resumes, which runs synchronously until the timer wait suspends.
26  
   resumes, which runs synchronously until the timer wait suspends.
27  
   At final_suspend, suspend_never destroys the frame — the
27  
   At final_suspend, suspend_never destroys the frame — the
28  
   timeout_coro destructor is intentionally a no-op since the
28  
   timeout_coro destructor is intentionally a no-op since the
29  
   handle is dangling after self-destruction. If the coroutine is
29  
   handle is dangling after self-destruction. If the coroutine is
30  
   still suspended at shutdown, the timer service drains it via
30  
   still suspended at shutdown, the timer service drains it via
31  
   completion_op::destroy().
31  
   completion_op::destroy().
32  

32  

33  
   The promise reuses task<>'s transform_awaiter pattern (including
33  
   The promise reuses task<>'s transform_awaiter pattern (including
34  
   the MSVC symmetric-transfer workaround) to inject io_env into
34  
   the MSVC symmetric-transfer workaround) to inject io_env into
35  
   IoAwaitable co_await expressions. */
35  
   IoAwaitable co_await expressions. */
36  

36  

37  
namespace boost::corosio::detail {
37  
namespace boost::corosio::detail {
38  

38  

39  
/** Fire-and-forget coroutine for the timeout side of cancel_at.
39  
/** Fire-and-forget coroutine for the timeout side of cancel_at.
40  

40  

41  
    The coroutine awaits a timer and signals a stop_source if the
41  
    The coroutine awaits a timer and signals a stop_source if the
42  
    timer fires without being cancelled. It self-destroys at
42  
    timer fires without being cancelled. It self-destroys at
43  
    final_suspend via suspend_never.
43  
    final_suspend via suspend_never.
44  

44  

45  
    @see make_timeout
45  
    @see make_timeout
46  
*/
46  
*/
47  
struct timeout_coro
47  
struct timeout_coro
48  
{
48  
{
49  
    struct promise_type
49  
    struct promise_type
50  
        : capy::io_awaitable_promise_base<promise_type>
50  
        : capy::io_awaitable_promise_base<promise_type>
51  
    {
51  
    {
52  
        capy::io_env env_storage_;
52  
        capy::io_env env_storage_;
53  

53  

54  
        /** Store an owned copy of the environment.
54  
        /** Store an owned copy of the environment.
55  

55  

56  
            The timeout coroutine can outlive the cancel_at_awaitable
56  
            The timeout coroutine can outlive the cancel_at_awaitable
57  
            that created it, so it must own its env rather than
57  
            that created it, so it must own its env rather than
58  
            pointing to external storage.
58  
            pointing to external storage.
59  
        */
59  
        */
60  
        void set_env_owned(capy::io_env env)
60  
        void set_env_owned(capy::io_env env)
61  
        {
61  
        {
62  
            env_storage_ = std::move(env);
62  
            env_storage_ = std::move(env);
63  
            set_environment(&env_storage_);
63  
            set_environment(&env_storage_);
64  
        }
64  
        }
65  

65  

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

72  

73  
        std::suspend_always initial_suspend() noexcept { return {}; }
73  
        std::suspend_always initial_suspend() noexcept { return {}; }
74  
        std::suspend_never final_suspend() noexcept { return {}; }
74  
        std::suspend_never final_suspend() noexcept { return {}; }
75  
        void return_void() noexcept {}
75  
        void return_void() noexcept {}
76  
        void unhandled_exception() noexcept {}
76  
        void unhandled_exception() noexcept {}
77  

77  

78  
        template<class Awaitable>
78  
        template<class Awaitable>
79  
        struct transform_awaiter
79  
        struct transform_awaiter
80  
        {
80  
        {
81  
            std::decay_t<Awaitable> a_;
81  
            std::decay_t<Awaitable> a_;
82  
            promise_type* p_;
82  
            promise_type* p_;
83  

83  

84  
            bool await_ready() noexcept
84  
            bool await_ready() noexcept
85  
            {
85  
            {
86  
                return a_.await_ready();
86  
                return a_.await_ready();
87  
            }
87  
            }
88  

88  

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

95  

96  
            template<class Promise>
96  
            template<class Promise>
97  
            auto await_suspend(
97  
            auto await_suspend(
98  
                std::coroutine_handle<Promise> h) noexcept
98  
                std::coroutine_handle<Promise> h) noexcept
99  
            {
99  
            {
100  
#ifdef _MSC_VER
100  
#ifdef _MSC_VER
101  
                using R = decltype(
101  
                using R = decltype(
102  
                    a_.await_suspend(h, p_->environment()));
102  
                    a_.await_suspend(h, p_->environment()));
103  
                if constexpr (std::is_same_v<
103  
                if constexpr (std::is_same_v<
104  
                                  R, std::coroutine_handle<>>)
104  
                                  R, std::coroutine_handle<>>)
105  
                    a_.await_suspend(h, p_->environment())
105  
                    a_.await_suspend(h, p_->environment())
106  
                        .resume();
106  
                        .resume();
107  
                else
107  
                else
108  
                    return a_.await_suspend(
108  
                    return a_.await_suspend(
109  
                        h, p_->environment());
109  
                        h, p_->environment());
110  
#else
110  
#else
111  
                return a_.await_suspend(h, p_->environment());
111  
                return a_.await_suspend(h, p_->environment());
112  
#endif
112  
#endif
113  
            }
113  
            }
114  
        };
114  
        };
115  

115  

116  
        template<class Awaitable>
116  
        template<class Awaitable>
117  
        auto transform_awaitable(Awaitable&& a)
117  
        auto transform_awaitable(Awaitable&& a)
118  
        {
118  
        {
119  
            using A = std::decay_t<Awaitable>;
119  
            using A = std::decay_t<Awaitable>;
120  
            if constexpr (capy::IoAwaitable<A>)
120  
            if constexpr (capy::IoAwaitable<A>)
121  
            {
121  
            {
122  
                return transform_awaiter<Awaitable>{
122  
                return transform_awaiter<Awaitable>{
123  
                    std::forward<Awaitable>(a), this};
123  
                    std::forward<Awaitable>(a), this};
124  
            }
124  
            }
125  
            else
125  
            else
126  
            {
126  
            {
127  
                static_assert(
127  
                static_assert(
128  
                    sizeof(A) == 0, "requires IoAwaitable");
128  
                    sizeof(A) == 0, "requires IoAwaitable");
129  
            }
129  
            }
130  
        }
130  
        }
131  
    };
131  
    };
132  

132  

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

134  

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

136  

137  
    explicit timeout_coro(
137  
    explicit timeout_coro(
138  
        std::coroutine_handle<promise_type> h) noexcept
138  
        std::coroutine_handle<promise_type> h) noexcept
139  
        : h_(h)
139  
        : h_(h)
140  
    {
140  
    {
141  
    }
141  
    }
142  

142  

143  
    // Self-destroying via suspend_never at final_suspend
143  
    // Self-destroying via suspend_never at final_suspend
144  
    ~timeout_coro() = default;
144  
    ~timeout_coro() = default;
145  

145  

146  
    timeout_coro(timeout_coro const&) = delete;
146  
    timeout_coro(timeout_coro const&) = delete;
147  
    timeout_coro& operator=(timeout_coro const&) = delete;
147  
    timeout_coro& operator=(timeout_coro const&) = delete;
148  

148  

149  
    timeout_coro(timeout_coro&& o) noexcept
149  
    timeout_coro(timeout_coro&& o) noexcept
150  
        : h_(o.h_)
150  
        : h_(o.h_)
151  
    {
151  
    {
152  
        o.h_ = nullptr;
152  
        o.h_ = nullptr;
153  
    }
153  
    }
154  

154  

155  
    timeout_coro& operator=(timeout_coro&& o) noexcept
155  
    timeout_coro& operator=(timeout_coro&& o) noexcept
156  
    {
156  
    {
157  
        h_   = o.h_;
157  
        h_   = o.h_;
158  
        o.h_ = nullptr;
158  
        o.h_ = nullptr;
159  
        return *this;
159  
        return *this;
160  
    }
160  
    }
161  
};
161  
};
162  

162  

163  
/** Create a fire-and-forget timeout coroutine.
163  
/** Create a fire-and-forget timeout coroutine.
164  

164  

165  
    Wait on the timer. If it fires without cancellation, signal
165  
    Wait on the timer. If it fires without cancellation, signal
166  
    the stop source to cancel the paired inner operation.
166  
    the stop source to cancel the paired inner operation.
167  

167  

168  
    @tparam Timer Timer type (`timer` or `native_timer<B>`).
168  
    @tparam Timer Timer type (`timer` or `native_timer<B>`).
169  

169  

170  
    @param t The timer to wait on (must have expiry set).
170  
    @param t The timer to wait on (must have expiry set).
171  
    @param src Stop source to signal on timeout.
171  
    @param src Stop source to signal on timeout.
172  
*/
172  
*/
173  
template<typename Timer>
173  
template<typename Timer>
174  
timeout_coro make_timeout(Timer& t, std::stop_source src)
174  
timeout_coro make_timeout(Timer& t, std::stop_source src)
175  
{
175  
{
176  
    auto [ec] = co_await t.wait();
176  
    auto [ec] = co_await t.wait();
177  
    if (!ec)
177  
    if (!ec)
178  
        src.request_stop();
178  
        src.request_stop();
179  
}
179  
}
180  

180  

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

182  

183  
#endif
183  
#endif