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 : capy::io_awaitable_promise_base<promise_type>
49  
    struct promise_type : capy::io_awaitable_promise_base<promise_type>
50  
    {
50  
    {
51  
        capy::io_env env_storage_;
51  
        capy::io_env env_storage_;
52  

52  

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

54  

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

64  

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

70  

71  
        std::suspend_always initial_suspend() noexcept
71  
        std::suspend_always initial_suspend() noexcept
72  
        {
72  
        {
73  
            return {};
73  
            return {};
74  
        }
74  
        }
75  
        std::suspend_never final_suspend() noexcept
75  
        std::suspend_never final_suspend() noexcept
76  
        {
76  
        {
77  
            return {};
77  
            return {};
78  
        }
78  
        }
79  
        void return_void() noexcept {}
79  
        void return_void() noexcept {}
80  
        void unhandled_exception() noexcept {}
80  
        void unhandled_exception() noexcept {}
81  

81  

82  
        template<class Awaitable>
82  
        template<class Awaitable>
83  
        struct transform_awaiter
83  
        struct transform_awaiter
84  
        {
84  
        {
85  
            std::decay_t<Awaitable> a_;
85  
            std::decay_t<Awaitable> a_;
86  
            promise_type* p_;
86  
            promise_type* p_;
87  

87  

88  
            bool await_ready() noexcept
88  
            bool await_ready() noexcept
89  
            {
89  
            {
90  
                return a_.await_ready();
90  
                return a_.await_ready();
91  
            }
91  
            }
92  

92  

93  
            decltype(auto) await_resume()
93  
            decltype(auto) await_resume()
94  
            {
94  
            {
95  
                capy::set_current_frame_allocator(
95  
                capy::set_current_frame_allocator(
96  
                    p_->environment()->frame_allocator);
96  
                    p_->environment()->frame_allocator);
97  
                return a_.await_resume();
97  
                return a_.await_resume();
98  
            }
98  
            }
99  

99  

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

114  

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

130  

131  
    std::coroutine_handle<promise_type> h_;
131  
    std::coroutine_handle<promise_type> h_;
132  

132  

133  
    timeout_coro() noexcept : h_(nullptr) {}
133  
    timeout_coro() noexcept : h_(nullptr) {}
134  

134  

135  
    explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
135  
    explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
136  
        : h_(h)
136  
        : h_(h)
137  
    {
137  
    {
138  
    }
138  
    }
139  

139  

140  
    // Self-destroying via suspend_never at final_suspend
140  
    // Self-destroying via suspend_never at final_suspend
141  
    ~timeout_coro() = default;
141  
    ~timeout_coro() = default;
142  

142  

143  
    timeout_coro(timeout_coro const&)            = delete;
143  
    timeout_coro(timeout_coro const&)            = delete;
144  
    timeout_coro& operator=(timeout_coro const&) = delete;
144  
    timeout_coro& operator=(timeout_coro const&) = delete;
145  

145  

146  
    timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
146  
    timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
147  
    {
147  
    {
148  
        o.h_ = nullptr;
148  
        o.h_ = nullptr;
149  
    }
149  
    }
150  

150  

151  
    timeout_coro& operator=(timeout_coro&& o) noexcept
151  
    timeout_coro& operator=(timeout_coro&& o) noexcept
152  
    {
152  
    {
153  
        h_   = o.h_;
153  
        h_   = o.h_;
154  
        o.h_ = nullptr;
154  
        o.h_ = nullptr;
155  
        return *this;
155  
        return *this;
156  
    }
156  
    }
157  
};
157  
};
158  

158  

159  
/** Create a fire-and-forget timeout coroutine.
159  
/** Create a fire-and-forget timeout coroutine.
160  

160  

161  
    Wait on the timer. If it fires without cancellation, signal
161  
    Wait on the timer. If it fires without cancellation, signal
162  
    the stop source to cancel the paired inner operation.
162  
    the stop source to cancel the paired inner operation.
163  

163  

164  
    @tparam Timer Timer type (`timer` or `native_timer<B>`).
164  
    @tparam Timer Timer type (`timer` or `native_timer<B>`).
165  

165  

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

177  

178  
} // namespace boost::corosio::detail
178  
} // namespace boost::corosio::detail
179  

179  

180  
#endif
180  
#endif