Skip to content

Commit 21875c3

Browse files
committed
Add cancel_after / cancel_at timeout adapters
Lightweight free functions that race an IoAwaitable against a timer. A bidirectional std::stop_source ties the inner op and timeout together — whichever completes first cancels the other. Uses a fire-and-forget coroutine for the timer side, avoiding any timer_service modifications.
1 parent 6f89148 commit 21875c3

File tree

8 files changed

+1240
-0
lines changed

8 files changed

+1240
-0
lines changed

include/boost/corosio.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#define BOOST_COROSIO_HPP
1212

1313
#include <boost/corosio/backend.hpp>
14+
#include <boost/corosio/cancel.hpp>
1415
#include <boost/corosio/endpoint.hpp>
1516
#include <boost/corosio/io_buffer_param.hpp>
1617
#include <boost/corosio/io_context.hpp>
@@ -19,6 +20,7 @@
1920
#include <boost/corosio/resolver.hpp>
2021
#include <boost/corosio/resolver_results.hpp>
2122
#include <boost/corosio/signal_set.hpp>
23+
#include <boost/corosio/socket_option.hpp>
2224
#include <boost/corosio/tcp_acceptor.hpp>
2325
#include <boost/corosio/tcp_server.hpp>
2426
#include <boost/corosio/tcp_socket.hpp>

include/boost/corosio/cancel.hpp

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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_CANCEL_HPP
11+
#define BOOST_COROSIO_CANCEL_HPP
12+
13+
#include <boost/corosio/detail/cancel_at_awaitable.hpp>
14+
#include <boost/corosio/timer.hpp>
15+
#include <boost/capy/concept/io_awaitable.hpp>
16+
17+
#include <type_traits>
18+
#include <utility>
19+
20+
namespace boost::corosio {
21+
22+
/** Cancel an operation if it does not complete by a deadline.
23+
24+
Races @p op against the given timer. If the deadline is reached
25+
first, the inner operation is cancelled via its stop token and
26+
completes with an error comparing equal to `capy::cond::canceled`.
27+
If the inner operation completes first, the timer is cancelled.
28+
29+
Parent cancellation (from the caller's stop token) is forwarded
30+
to both the inner operation and the timeout timer.
31+
32+
The timer's expiry is overwritten by this call. The timer must
33+
outlive the returned awaitable. Do not issue overlapping waits
34+
on the same timer.
35+
36+
@par Completion Conditions
37+
The returned awaitable resumes when either:
38+
@li The inner operation completes (successfully or with error).
39+
@li The deadline expires and the inner operation is cancelled.
40+
@li The caller's stop token is triggered, cancelling both.
41+
42+
@par Error Conditions
43+
@li On timeout or parent cancellation, the inner operation
44+
completes with an error equal to `capy::cond::canceled`.
45+
@li All other errors are propagated from the inner operation.
46+
47+
@par Example
48+
@code
49+
timer timeout_timer( ioc );
50+
auto [ec, n] = co_await cancel_at(
51+
sock.read_some( buf ), timeout_timer,
52+
clock::now() + 5s );
53+
if (ec == capy::cond::canceled)
54+
// timed out or parent cancelled
55+
@endcode
56+
57+
@param op The inner I/O awaitable to wrap.
58+
@param t The timer to use for the deadline. Must outlive
59+
the returned awaitable.
60+
@param deadline The absolute time point at which to cancel.
61+
62+
@return An awaitable whose result matches @p op's result type.
63+
64+
@see cancel_after
65+
*/
66+
auto cancel_at(
67+
capy::IoAwaitable auto&& op,
68+
timer& t,
69+
timer::time_point deadline)
70+
{
71+
return detail::cancel_at_awaitable<
72+
std::decay_t<decltype(op)>, timer>(
73+
std::forward<decltype(op)>(op), t, deadline);
74+
}
75+
76+
/** Cancel an operation if it does not complete within a duration.
77+
78+
Equivalent to `cancel_at( op, t, clock::now() + timeout )`.
79+
80+
The timer's expiry is overwritten by this call. The timer must
81+
outlive the returned awaitable. Do not issue overlapping waits
82+
on the same timer.
83+
84+
@par Completion Conditions
85+
The returned awaitable resumes when either:
86+
@li The inner operation completes (successfully or with error).
87+
@li The timeout elapses and the inner operation is cancelled.
88+
@li The caller's stop token is triggered, cancelling both.
89+
90+
@par Error Conditions
91+
@li On timeout or parent cancellation, the inner operation
92+
completes with an error equal to `capy::cond::canceled`.
93+
@li All other errors are propagated from the inner operation.
94+
95+
@par Example
96+
@code
97+
timer timeout_timer( ioc );
98+
auto [ec, n] = co_await cancel_after(
99+
sock.read_some( buf ), timeout_timer, 5s );
100+
if (ec == capy::cond::canceled)
101+
// timed out
102+
@endcode
103+
104+
@param op The inner I/O awaitable to wrap.
105+
@param t The timer to use for the timeout. Must outlive
106+
the returned awaitable.
107+
@param timeout The relative duration after which to cancel.
108+
109+
@return An awaitable whose result matches @p op's result type.
110+
111+
@see cancel_at
112+
*/
113+
auto cancel_after(
114+
capy::IoAwaitable auto&& op,
115+
timer& t,
116+
timer::duration timeout)
117+
{
118+
return cancel_at(
119+
std::forward<decltype(op)>(op), t,
120+
timer::clock_type::now() + timeout);
121+
}
122+
123+
/** Cancel an operation if it does not complete by a deadline.
124+
125+
Convenience overload that creates a @ref timer internally.
126+
Otherwise identical to the explicit-timer overload.
127+
128+
@par Completion Conditions
129+
The returned awaitable resumes when either:
130+
@li The inner operation completes (successfully or with error).
131+
@li The deadline expires and the inner operation is cancelled.
132+
@li The caller's stop token is triggered, cancelling both.
133+
134+
@par Error Conditions
135+
@li On timeout or parent cancellation, the inner operation
136+
completes with an error equal to `capy::cond::canceled`.
137+
@li All other errors are propagated from the inner operation.
138+
139+
@note Creates a timer per call. Use the explicit-timer overload
140+
to amortize allocation across multiple timeouts.
141+
142+
@par Example
143+
@code
144+
auto [ec, n] = co_await cancel_at(
145+
sock.read_some( buf ),
146+
clock::now() + 5s );
147+
if (ec == capy::cond::canceled)
148+
// timed out or parent cancelled
149+
@endcode
150+
151+
@param op The inner I/O awaitable to wrap.
152+
@param deadline The absolute time point at which to cancel.
153+
154+
@return An awaitable whose result matches @p op's result type.
155+
156+
@see cancel_after
157+
*/
158+
auto cancel_at(
159+
capy::IoAwaitable auto&& op,
160+
timer::time_point deadline)
161+
{
162+
return detail::cancel_at_awaitable<
163+
std::decay_t<decltype(op)>, timer, true>(
164+
std::forward<decltype(op)>(op), deadline);
165+
}
166+
167+
/** Cancel an operation if it does not complete within a duration.
168+
169+
Convenience overload that creates a @ref timer internally.
170+
Equivalent to `cancel_at( op, clock::now() + timeout )`.
171+
172+
@par Completion Conditions
173+
The returned awaitable resumes when either:
174+
@li The inner operation completes (successfully or with error).
175+
@li The timeout elapses and the inner operation is cancelled.
176+
@li The caller's stop token is triggered, cancelling both.
177+
178+
@par Error Conditions
179+
@li On timeout or parent cancellation, the inner operation
180+
completes with an error equal to `capy::cond::canceled`.
181+
@li All other errors are propagated from the inner operation.
182+
183+
@note Creates a timer per call. Use the explicit-timer overload
184+
to amortize allocation across multiple timeouts.
185+
186+
@par Example
187+
@code
188+
auto [ec, n] = co_await cancel_after(
189+
sock.read_some( buf ), 5s );
190+
if (ec == capy::cond::canceled)
191+
// timed out
192+
@endcode
193+
194+
@param op The inner I/O awaitable to wrap.
195+
@param timeout The relative duration after which to cancel.
196+
197+
@return An awaitable whose result matches @p op's result type.
198+
199+
@see cancel_at
200+
*/
201+
auto cancel_after(
202+
capy::IoAwaitable auto&& op,
203+
timer::duration timeout)
204+
{
205+
return cancel_at(
206+
std::forward<decltype(op)>(op),
207+
timer::clock_type::now() + timeout);
208+
}
209+
210+
} // namespace boost::corosio
211+
212+
#endif

0 commit comments

Comments
 (0)