1 +
//
 
2 +
// Copyright (c) 2026 Michael Vandeberg
 
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/capy
 
8 +
//
 
9 +

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

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

 
21 +
#include <chrono>
 
22 +
#include <system_error>
 
23 +
#include <type_traits>
 
24 +

 
25 +
namespace boost {
 
26 +
namespace capy {
 
27 +

 
28 +
namespace detail {
 
29 +

 
30 +
template<typename T>
 
31 +
struct is_io_result : std::false_type {};
 
32 +

 
33 +
template<typename... Args>
 
34 +
struct is_io_result<io_result<Args...>> : std::true_type {};
 
35 +

 
36 +
template<typename T>
 
37 +
inline constexpr bool is_io_result_v = is_io_result<T>::value;
 
38 +

 
39 +
} // detail
 
40 +

 
41 +
/** Race an awaitable against a deadline.
 
42 +

 
43 +
    Starts the awaitable and a timer concurrently. If the
 
44 +
    awaitable completes first, its result is returned. If the
 
45 +
    timer fires first, stop is requested for the awaitable and
 
46 +
    a timeout error is produced.
 
47 +

 
48 +
    @par Return Type
 
49 +

 
50 +
    The return type matches the inner awaitable's result type:
 
51 +

 
52 +
    @li For `io_result<...>` types: returns `io_result` with
 
53 +
        `ec == error::timeout` and default-initialized values
 
54 +
    @li For non-void types: throws `std::system_error(error::timeout)`
 
55 +
    @li For void: throws `std::system_error(error::timeout)`
 
56 +

 
57 +
    @par Precision
 
58 +

 
59 +
    The timeout fires at or after the specified duration.
 
60 +

 
61 +
    @par Cancellation
 
62 +

 
63 +
    If the parent's stop token is activated, the inner awaitable
 
64 +
    is cancelled normally (not a timeout). The result reflects
 
65 +
    the inner awaitable's cancellation behavior.
 
66 +

 
67 +
    @par Example
 
68 +
    @code
 
69 +
    auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms);
 
70 +
    if (ec == cond::timeout) {
 
71 +
        // handle timeout
 
72 +
    }
 
73 +
    @endcode
 
74 +

 
75 +
    @tparam A An IoAwaitable whose result type determines
 
76 +
        how timeouts are reported.
 
77 +

 
78 +
    @param a The awaitable to race against the deadline.
 
79 +
    @param dur The maximum duration to wait.
 
80 +

 
81 +
    @return `task<awaitable_result_t<A>>`.
 
82 +

 
83 +
    @throws std::system_error with `error::timeout` if the timer
 
84 +
        fires first and the result type is not `io_result`.
 
85 +
        Exceptions thrown by the inner awaitable propagate
 
86 +
        unchanged.
 
87 +

 
88 +
    @see delay, when_any, cond::timeout
 
89 +
*/
 
90 +
template<IoAwaitable A, typename Rep, typename Period>
 
91 +
auto timeout(A a, std::chrono::duration<Rep, Period> dur)
 
92 +
    -> task<awaitable_result_t<A>>
 
93 +
{
 
94 +
    using T = awaitable_result_t<A>;
 
95 +

 
96 +
    auto result = co_await when_any(
 
97 +
        std::move(a), delay(dur));
 
98 +

 
99 +
    if(result.index() == 0)
 
100 +
    {
 
101 +
        // Task completed first
 
102 +
        if constexpr (std::is_void_v<T>)
 
103 +
            co_return;
 
104 +
        else
 
105 +
            co_return std::get<0>(std::move(result));
 
106 +
    }
 
107 +
    else
 
108 +
    {
 
109 +
        // Timer won
 
110 +
        if constexpr (detail::is_io_result_v<T>)
 
111 +
        {
 
112 +
            T timeout_result{};
 
113 +
            timeout_result.ec = make_error_code(error::timeout);
 
114 +
            co_return timeout_result;
 
115 +
        }
 
116 +
        else if constexpr (std::is_void_v<T>)
 
117 +
        {
 
118 +
            throw std::system_error(
 
119 +
                make_error_code(error::timeout));
 
120 +
        }
 
121 +
        else
 
122 +
        {
 
123 +
            throw std::system_error(
 
124 +
                make_error_code(error::timeout));
 
125 +
        }
 
126 +
    }
 
127 +
}
 
128 +

 
129 +
} // capy
 
130 +
} // boost
 
131 +

 
132 +
#endif