1  
//
1  
//
2  
// Copyright (c) 2026 Michael Vandeberg
2  
// Copyright (c) 2026 Michael Vandeberg
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/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_TIMEOUT_HPP
10  
#ifndef BOOST_CAPY_TIMEOUT_HPP
11  
#define BOOST_CAPY_TIMEOUT_HPP
11  
#define BOOST_CAPY_TIMEOUT_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/concept/io_awaitable.hpp>
14  
#include <boost/capy/concept/io_awaitable.hpp>
15  
#include <boost/capy/delay.hpp>
15  
#include <boost/capy/delay.hpp>
16  
#include <boost/capy/detail/io_result_combinators.hpp>
16  
#include <boost/capy/detail/io_result_combinators.hpp>
17  
#include <boost/capy/error.hpp>
17  
#include <boost/capy/error.hpp>
18  
#include <boost/capy/io_result.hpp>
18  
#include <boost/capy/io_result.hpp>
19  
#include <boost/capy/task.hpp>
19  
#include <boost/capy/task.hpp>
20  
#include <boost/capy/when_all.hpp>
20  
#include <boost/capy/when_all.hpp>
21  

21  

22  
#include <atomic>
22  
#include <atomic>
23  
#include <chrono>
23  
#include <chrono>
24  
#include <exception>
24  
#include <exception>
25  
#include <optional>
25  
#include <optional>
26  

26  

27  
namespace boost {
27  
namespace boost {
28  
namespace capy {
28  
namespace capy {
29  
namespace detail {
29  
namespace detail {
30  

30  

31  
template<typename T>
31  
template<typename T>
32  
struct timeout_state
32  
struct timeout_state
33  
{
33  
{
34  
    when_all_core core_;
34  
    when_all_core core_;
35  
    std::atomic<int> winner_{-1}; // -1=none, 0=inner, 1=delay
35  
    std::atomic<int> winner_{-1}; // -1=none, 0=inner, 1=delay
36  
    std::optional<T> inner_result_;
36  
    std::optional<T> inner_result_;
37  
    std::exception_ptr inner_exception_;
37  
    std::exception_ptr inner_exception_;
38  
    std::array<continuation, 2> runner_handles_{};
38  
    std::array<continuation, 2> runner_handles_{};
39  

39  

40  
    timeout_state()
40  
    timeout_state()
41  
        : core_(2)
41  
        : core_(2)
42  
    {
42  
    {
43  
    }
43  
    }
44  
};
44  
};
45  

45  

46  
template<IoAwaitable Awaitable, typename T>
46  
template<IoAwaitable Awaitable, typename T>
47  
when_all_runner<timeout_state<T>>
47  
when_all_runner<timeout_state<T>>
48  
make_timeout_inner_runner(
48  
make_timeout_inner_runner(
49  
    Awaitable inner, timeout_state<T>* state)
49  
    Awaitable inner, timeout_state<T>* state)
50  
{
50  
{
51  
    try
51  
    try
52  
    {
52  
    {
53  
        auto result = co_await std::move(inner);
53  
        auto result = co_await std::move(inner);
54  
        state->inner_result_.emplace(std::move(result));
54  
        state->inner_result_.emplace(std::move(result));
55  
    }
55  
    }
56  
    catch(...)
56  
    catch(...)
57  
    {
57  
    {
58  
        state->inner_exception_ = std::current_exception();
58  
        state->inner_exception_ = std::current_exception();
59  
    }
59  
    }
60  

60  

61  
    int expected = -1;
61  
    int expected = -1;
62  
    if(state->winner_.compare_exchange_strong(
62  
    if(state->winner_.compare_exchange_strong(
63  
        expected, 0, std::memory_order_relaxed))
63  
        expected, 0, std::memory_order_relaxed))
64  
        state->core_.stop_source_.request_stop();
64  
        state->core_.stop_source_.request_stop();
65  
}
65  
}
66  

66  

67  
template<typename DelayAw, typename T>
67  
template<typename DelayAw, typename T>
68  
when_all_runner<timeout_state<T>>
68  
when_all_runner<timeout_state<T>>
69  
make_timeout_delay_runner(
69  
make_timeout_delay_runner(
70  
    DelayAw d, timeout_state<T>* state)
70  
    DelayAw d, timeout_state<T>* state)
71  
{
71  
{
72  
    auto result = co_await std::move(d);
72  
    auto result = co_await std::move(d);
73  

73  

74  
    if(!result.ec)
74  
    if(!result.ec)
75  
    {
75  
    {
76  
        int expected = -1;
76  
        int expected = -1;
77  
        if(state->winner_.compare_exchange_strong(
77  
        if(state->winner_.compare_exchange_strong(
78  
            expected, 1, std::memory_order_relaxed))
78  
            expected, 1, std::memory_order_relaxed))
79  
            state->core_.stop_source_.request_stop();
79  
            state->core_.stop_source_.request_stop();
80  
    }
80  
    }
81  
}
81  
}
82  

82  

83  
template<IoAwaitable Inner, typename DelayAw, typename T>
83  
template<IoAwaitable Inner, typename DelayAw, typename T>
84  
class timeout_launcher
84  
class timeout_launcher
85  
{
85  
{
86  
    Inner* inner_;
86  
    Inner* inner_;
87  
    DelayAw* delay_;
87  
    DelayAw* delay_;
88  
    timeout_state<T>* state_;
88  
    timeout_state<T>* state_;
89  

89  

90  
public:
90  
public:
91  
    timeout_launcher(
91  
    timeout_launcher(
92  
        Inner* inner, DelayAw* delay,
92  
        Inner* inner, DelayAw* delay,
93  
        timeout_state<T>* state)
93  
        timeout_state<T>* state)
94  
        : inner_(inner)
94  
        : inner_(inner)
95  
        , delay_(delay)
95  
        , delay_(delay)
96  
        , state_(state)
96  
        , state_(state)
97  
    {
97  
    {
98  
    }
98  
    }
99  

99  

100  
    bool await_ready() const noexcept { return false; }
100  
    bool await_ready() const noexcept { return false; }
101  

101  

102  
    std::coroutine_handle<> await_suspend(
102  
    std::coroutine_handle<> await_suspend(
103  
        std::coroutine_handle<> continuation,
103  
        std::coroutine_handle<> continuation,
104  
        io_env const* caller_env)
104  
        io_env const* caller_env)
105  
    {
105  
    {
106  
        state_->core_.continuation_.h = continuation;
106  
        state_->core_.continuation_.h = continuation;
107  
        state_->core_.caller_env_ = caller_env;
107  
        state_->core_.caller_env_ = caller_env;
108  

108  

109  
        if(caller_env->stop_token.stop_possible())
109  
        if(caller_env->stop_token.stop_possible())
110  
        {
110  
        {
111  
            state_->core_.parent_stop_callback_.emplace(
111  
            state_->core_.parent_stop_callback_.emplace(
112  
                caller_env->stop_token,
112  
                caller_env->stop_token,
113  
                when_all_core::stop_callback_fn{
113  
                when_all_core::stop_callback_fn{
114  
                    &state_->core_.stop_source_});
114  
                    &state_->core_.stop_source_});
115  

115  

116  
            if(caller_env->stop_token.stop_requested())
116  
            if(caller_env->stop_token.stop_requested())
117  
                state_->core_.stop_source_.request_stop();
117  
                state_->core_.stop_source_.request_stop();
118  
        }
118  
        }
119  

119  

120  
        auto token = state_->core_.stop_source_.get_token();
120  
        auto token = state_->core_.stop_source_.get_token();
121  

121  

122  
        auto r0 = make_timeout_inner_runner(
122  
        auto r0 = make_timeout_inner_runner(
123  
            std::move(*inner_), state_);
123  
            std::move(*inner_), state_);
124  
        auto h0 = r0.release();
124  
        auto h0 = r0.release();
125  
        h0.promise().state_ = state_;
125  
        h0.promise().state_ = state_;
126  
        h0.promise().env_ = io_env{
126  
        h0.promise().env_ = io_env{
127  
            caller_env->executor, token,
127  
            caller_env->executor, token,
128  
            caller_env->frame_allocator};
128  
            caller_env->frame_allocator};
129  
        state_->runner_handles_[0].h =
129  
        state_->runner_handles_[0].h =
130  
            std::coroutine_handle<>{h0};
130  
            std::coroutine_handle<>{h0};
131  

131  

132  
        auto r1 = make_timeout_delay_runner(
132  
        auto r1 = make_timeout_delay_runner(
133  
            std::move(*delay_), state_);
133  
            std::move(*delay_), state_);
134  
        auto h1 = r1.release();
134  
        auto h1 = r1.release();
135  
        h1.promise().state_ = state_;
135  
        h1.promise().state_ = state_;
136  
        h1.promise().env_ = io_env{
136  
        h1.promise().env_ = io_env{
137  
            caller_env->executor, token,
137  
            caller_env->executor, token,
138  
            caller_env->frame_allocator};
138  
            caller_env->frame_allocator};
139  
        state_->runner_handles_[1].h =
139  
        state_->runner_handles_[1].h =
140  
            std::coroutine_handle<>{h1};
140  
            std::coroutine_handle<>{h1};
141  

141  

142  
        caller_env->executor.post(
142  
        caller_env->executor.post(
143  
            state_->runner_handles_[0]);
143  
            state_->runner_handles_[0]);
144  
        caller_env->executor.post(
144  
        caller_env->executor.post(
145  
            state_->runner_handles_[1]);
145  
            state_->runner_handles_[1]);
146  

146  

147  
        return std::noop_coroutine();
147  
        return std::noop_coroutine();
148  
    }
148  
    }
149  

149  

150  
    void await_resume() const noexcept {}
150  
    void await_resume() const noexcept {}
151  
};
151  
};
152  

152  

153  
} // namespace detail
153  
} // namespace detail
154  

154  

155  
/** Race an io_result-returning awaitable against a deadline.
155  
/** Race an io_result-returning awaitable against a deadline.
156  

156  

157  
    Starts the awaitable and a timer concurrently. The first to
157  
    Starts the awaitable and a timer concurrently. The first to
158  
    complete wins and cancels the other. If the awaitable finishes
158  
    complete wins and cancels the other. If the awaitable finishes
159  
    first, its result is returned as-is (success, error, or
159  
    first, its result is returned as-is (success, error, or
160  
    exception). If the timer fires first, an `io_result` with
160  
    exception). If the timer fires first, an `io_result` with
161  
    `ec == error::timeout` is produced.
161  
    `ec == error::timeout` is produced.
162  

162  

163  
    Unlike @ref when_any, exceptions from the inner awaitable
163  
    Unlike @ref when_any, exceptions from the inner awaitable
164  
    are always propagated — they are never swallowed by the timer.
164  
    are always propagated — they are never swallowed by the timer.
165  

165  

166  
    @par Return Type
166  
    @par Return Type
167  

167  

168  
    Always returns `io_result<Ts...>` matching the inner
168  
    Always returns `io_result<Ts...>` matching the inner
169  
    awaitable's result type. On timeout, `ec` is set to
169  
    awaitable's result type. On timeout, `ec` is set to
170  
    `error::timeout` and payload values are default-initialized.
170  
    `error::timeout` and payload values are default-initialized.
171  

171  

172  
    @par Precision
172  
    @par Precision
173  

173  

174  
    The timeout fires at or after the specified duration.
174  
    The timeout fires at or after the specified duration.
175  

175  

176  
    @par Cancellation
176  
    @par Cancellation
177  

177  

178  
    If the parent's stop token is activated, both children are
178  
    If the parent's stop token is activated, both children are
179  
    cancelled. The inner awaitable's cancellation result is
179  
    cancelled. The inner awaitable's cancellation result is
180  
    returned.
180  
    returned.
181  

181  

182  
    @par Example
182  
    @par Example
183  
    @code
183  
    @code
184  
    auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms);
184  
    auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms);
185  
    if (ec == cond::timeout) {
185  
    if (ec == cond::timeout) {
186  
        // handle timeout
186  
        // handle timeout
187  
    }
187  
    }
188  
    @endcode
188  
    @endcode
189  

189  

190  
    @tparam A An IoAwaitable returning `io_result<Ts...>`.
190  
    @tparam A An IoAwaitable returning `io_result<Ts...>`.
191  

191  

192  
    @param a The awaitable to race against the deadline.
192  
    @param a The awaitable to race against the deadline.
193  
    @param dur The maximum duration to wait.
193  
    @param dur The maximum duration to wait.
194  

194  

195  
    @return `task<awaitable_result_t<A>>`.
195  
    @return `task<awaitable_result_t<A>>`.
196  

196  

197  
    @throws Rethrows any exception from the inner awaitable,
197  
    @throws Rethrows any exception from the inner awaitable,
198  
        regardless of whether the timer has fired.
198  
        regardless of whether the timer has fired.
199  

199  

200  
    @see delay, cond::timeout
200  
    @see delay, cond::timeout
201  
*/
201  
*/
202  
template<IoAwaitable A, typename Rep, typename Period>
202  
template<IoAwaitable A, typename Rep, typename Period>
203  
    requires detail::is_io_result_v<awaitable_result_t<A>>
203  
    requires detail::is_io_result_v<awaitable_result_t<A>>
204  
auto timeout(A a, std::chrono::duration<Rep, Period> dur)
204  
auto timeout(A a, std::chrono::duration<Rep, Period> dur)
205  
    -> task<awaitable_result_t<A>>
205  
    -> task<awaitable_result_t<A>>
206  
{
206  
{
207  
    using T = awaitable_result_t<A>;
207  
    using T = awaitable_result_t<A>;
208  

208  

209  
    auto d = delay(dur);
209  
    auto d = delay(dur);
210  
    detail::timeout_state<T> state;
210  
    detail::timeout_state<T> state;
211  

211  

212  
    co_await detail::timeout_launcher<
212  
    co_await detail::timeout_launcher<
213  
        A, decltype(d), T>(&a, &d, &state);
213  
        A, decltype(d), T>(&a, &d, &state);
214  

214  

215  
    if(state.core_.first_exception_)
215  
    if(state.core_.first_exception_)
216  
        std::rethrow_exception(state.core_.first_exception_);
216  
        std::rethrow_exception(state.core_.first_exception_);
217  
    if(state.inner_exception_)
217  
    if(state.inner_exception_)
218  
        std::rethrow_exception(state.inner_exception_);
218  
        std::rethrow_exception(state.inner_exception_);
219  

219  

220  
    if(state.winner_.load(std::memory_order_relaxed) == 0)
220  
    if(state.winner_.load(std::memory_order_relaxed) == 0)
221  
        co_return std::move(*state.inner_result_);
221  
        co_return std::move(*state.inner_result_);
222  

222  

223  
    // Delay fired first: timeout
223  
    // Delay fired first: timeout
224  
    T r{};
224  
    T r{};
225  
    r.ec = make_error_code(error::timeout);
225  
    r.ec = make_error_code(error::timeout);
226  
    co_return r;
226  
    co_return r;
227  
}
227  
}
228  

228  

229  
} // capy
229  
} // capy
230  
} // boost
230  
} // boost
231  

231  

232  
#endif
232  
#endif