Add cancel_after / cancel_at timeout adapters#180
Add cancel_after / cancel_at timeout adapters#180sgerbino merged 1 commit intocppalliance:developfrom
Conversation
📝 WalkthroughWalkthroughAdds deadline- and timeout-based cancellation utilities: new public Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant API as cancel_at / cancel_after
participant Await as cancel_at_awaitable
participant Timer as Timer
participant Timeout as timeout_coro
participant Inner as Inner IoAwaitable
participant StopSrc as std::stop_source
Client->>API: call cancel_at(op, timer, deadline)
API->>Await: construct awaitable(op, timer, deadline)
Client->>Await: co_await
Await->>StopSrc: create shared stop_source
Await->>Timeout: make_timeout(timer, StopSrc) -- fire-and-forget
Timeout->>Timer: await timer.wait()
par race
Await->>Inner: resume inner with forwarded stop_token
Inner-->>Await: completes with result/error
and
Timer-->>StopSrc: timer expires -> request_stop()
StopSrc-->>Inner: stop requested (propagated)
end
alt Inner completed first
Await->>Timeout: cancel timeout
Await-->>Client: return inner result
else Timer expired first
Await-->>Client: return canceled condition
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
An automated preview of the documentation is available at https://180.corosio.prtest3.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-02-26 19:01:00 UTC |
|
GCOVR code coverage report https://180.corosio.prtest3.cppalliance.org/gcovr/index.html Build time: 2026-02-26 19:07:43 UTC |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
include/boost/corosio/detail/timeout_coro.hpp (1)
58-62:unhandled_exception()swallowing exceptions is risky for maintenance/debugging.For a fire-and-forget coroutine, doing nothing makes failures silent. Consider
std::terminate()(or at least an assertion/log hook if the project has one) to avoid “hung timeout logic” being masked by unexpected exceptions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@include/boost/corosio/detail/timeout_coro.hpp` around lines 58 - 62, The unhandled_exception() currently swallows exceptions silently in the timeout coroutine; change its implementation in the coroutine promise (function unhandled_exception) to terminate or surface the error (e.g., call std::terminate() or your project's fatal/assert/log hook) so exceptions do not get silently dropped and can’t mask timeout logic failures; update the promise type (where initial_suspend(), final_suspend(), return_void(), unhandled_exception() are defined) to invoke the chosen termination/logging action.include/boost/corosio/detail/cancel_at_awaitable.hpp (1)
37-44: Avoid storing a raw pointer in the stop callback; capturestd::stop_sourceby value instead.
stop_forwardercurrently storesstd::stop_source*(Line 39) and assumes the awaiter won’t move after the callback is installed (comment on Line 72). Capturing astd::stop_sourceby value removes that fragility and makes the callback independent of the awaiter object’s address.Proposed change
struct stop_forwarder { - std::stop_source* src_; + std::stop_source src_; void operator()() const noexcept { - src_->request_stop(); + src_.request_stop(); } }; @@ new (cb_buf_) stop_cb_type( - env->stop_token, stop_forwarder{&stop_src_}); + env->stop_token, stop_forwarder{stop_src_});Also applies to: 105-108, 126-134
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@include/boost/corosio/detail/cancel_at_awaitable.hpp` around lines 37 - 44, The stop callback type stop_forwarder currently stores a raw std::stop_source* (member src_) which is fragile if the awaiter moves; change stop_forwarder to capture a std::stop_source by value (store std::stop_source src_ or std::stop_source src_;) and update operator() to call src_.request_stop(); ensure proper noexcept specifiers remain and that the type remains copyable/movable as needed. Apply the same fix to the other callback types/structs in this file that mirror stop_forwarder’s pattern (the other stop-forwarding structs around the other awaitable implementations) so none keep raw pointers to a stop_source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@include/boost/corosio/cancel.hpp`:
- Around line 79-87: The public helper cancel_after (which computes
timer::clock_type::now() + timeout) can overflow for extreme timeout durations;
update cancel_after to either document the valid timeout range or, better,
perform saturating arithmetic when computing the deadline: obtain now =
timer::clock_type::now(), compute a safe deadline by clamping now + timeout into
[timer::clock_type::time_point::min(), timer::clock_type::time_point::max()] (or
reuse the same logic as expires_after in timer.hpp), then call cancel_at with
that clamped time_point; mention the change in the function docstring if you add
a precondition instead of clamping.
In `@include/boost/corosio/detail/cancel_at_awaitable.hpp`:
- Around line 13-23: Add a file-level /* */ overview comment immediately after
the includes in include/boost/corosio/detail/cancel_at_awaitable.hpp that
summarizes the control flow and lifetime assumptions for this header: describe
the fire‑and‑forget timeout coroutine started via timeout_coro, how parent
stop_token forwarding is performed, how the interposed io_env (capy::ex::io_env)
is used to isolate/resume the awaited operation, and the ownership/lifetime
expectations for any internal handles or coroutine frames so callers know which
objects must outlive others; reference the namespace boost::corosio::detail and
the key concepts (timeout_coro, stop_token forwarding, interposed env, and the
cancel/await handshake) in the comment so readers can quickly map the overview
to the implementation.
In `@include/boost/corosio/detail/timeout_coro.hpp`:
- Around line 13-21: The header uses std::move and std::forward but doesn't
include <utility>, making the public header non-self-sufficient; add `#include`
<utility> to the top of include/boost/corosio/detail/timeout_coro.hpp alongside
the other includes so symbols used in functions that call std::move (around the
code referencing move at line ~47) and std::forward (around the code referencing
forward at line ~108) are defined for consumers of this header.
- Around line 13-23: Add a /* */ block comment immediately after the includes
that gives a high-level implementation overview: explain the role and lifecycle
of the self-destroying coroutine (how/when it frees itself), why and how owned
io_env storage is used, what the awaitable-transform wrapper does to
convert/forward awaitables (mention the transform semantics), and the MSVC
workaround rationale for specific promise/coroutine shims; reference the key
implementation symbols such as timeout_coro, io_awaitable_promise_base, io_env
and the awaitable transform wrapper so readers can map the narrative to the
code.
In `@include/boost/corosio/native/native_cancel.hpp`:
- Around line 22-47: Update the documentation for the native overload cancel_at
and its sibling native overloads (e.g., the native_timer<Backend> variant) to
match the timer overloads: add Completion conditions (use `@li` to list success vs
cancellation via capy::cond::canceled), Concurrency/overlap notes (state that
native_timer must outlive the awaitable, expiry is overwritten by new waits, and
overlapping waits on the same native_timer are forbidden), Error conditions and
`@throws` (document that timeouts/cancellation produce capy::cond::canceled and
any exceptions the inner awaitable may propagate), precise `@param`
lifetime/ownership annotations for op, t, and deadline, and include 2–3 `@par`
Example blocks showing normal completion, timeout cancellation, and shared-timer
misuse; keep wording parallel to the timer overload docs and reference
cancel_at, native_timer<Backend>, and capy::cond::canceled to help locate the
behavior.
---
Nitpick comments:
In `@include/boost/corosio/detail/cancel_at_awaitable.hpp`:
- Around line 37-44: The stop callback type stop_forwarder currently stores a
raw std::stop_source* (member src_) which is fragile if the awaiter moves;
change stop_forwarder to capture a std::stop_source by value (store
std::stop_source src_ or std::stop_source src_;) and update operator() to call
src_.request_stop(); ensure proper noexcept specifiers remain and that the type
remains copyable/movable as needed. Apply the same fix to the other callback
types/structs in this file that mirror stop_forwarder’s pattern (the other
stop-forwarding structs around the other awaitable implementations) so none keep
raw pointers to a stop_source.
In `@include/boost/corosio/detail/timeout_coro.hpp`:
- Around line 58-62: The unhandled_exception() currently swallows exceptions
silently in the timeout coroutine; change its implementation in the coroutine
promise (function unhandled_exception) to terminate or surface the error (e.g.,
call std::terminate() or your project's fatal/assert/log hook) so exceptions do
not get silently dropped and can’t mask timeout logic failures; update the
promise type (where initial_suspend(), final_suspend(), return_void(),
unhandled_exception() are defined) to invoke the chosen termination/logging
action.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
test/unit/cancel.cppis excluded by!**/test/**
📒 Files selected for processing (6)
include/boost/corosio.hppinclude/boost/corosio/cancel.hppinclude/boost/corosio/detail/cancel_at_awaitable.hppinclude/boost/corosio/detail/timeout_coro.hppinclude/boost/corosio/native/native.hppinclude/boost/corosio/native/native_cancel.hpp
| auto cancel_after( | ||
| capy::IoAwaitable auto&& op, | ||
| timer& t, | ||
| timer::duration timeout) | ||
| { | ||
| return cancel_at( | ||
| std::forward<decltype(op)>(op), t, | ||
| timer::clock_type::now() + timeout); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and examine timer.hpp to understand the type definitions
fd -a 'timer\.hpp$' include/boost/corosioRepository: cppalliance/corosio
Length of output: 235
🏁 Script executed:
# Examine the cancel_after function and surrounding context
cat -n include/boost/corosio/cancel.hpp | head -100Repository: cppalliance/corosio
Length of output: 3232
🏁 Script executed:
# Search for timer type definitions (clock_type, duration, time_point)
rg -n "using.*clock_type|using.*time_point|using.*duration" include/boost/corosio/timer.hpp -A 2Repository: cppalliance/corosio
Length of output: 45
🏁 Script executed:
# Examine timer.hpp to find type definitions
cat -n include/boost/corosio/timer.hppRepository: cppalliance/corosio
Length of output: 7475
🏁 Script executed:
# Also check the public header structure
head -50 include/boost/corosio/timer.hppRepository: cppalliance/corosio
Length of output: 1677
🏁 Script executed:
# Examine io_timer.hpp to find the actual type definitions
cat -n include/boost/corosio/io/io_timer.hpp | head -150Repository: cppalliance/corosio
Length of output: 5601
🏁 Script executed:
# Search for type aliases in io_timer.hpp
rg -n "clock_type|time_point|duration" include/boost/corosio/io/io_timer.hpp -B 2 -A 1Repository: cppalliance/corosio
Length of output: 1047
Add documentation or guard against chrono overflow in cancel_after.
The expression timer::clock_type::now() + timeout can overflow for extreme timeout values (e.g., durations near the limits of std::chrono::steady_clock's ~292-year range). Although rare in practice, cancel_after is a public API and should either document a precondition on timeout or clamp the result to avoid undefined behavior. The companion method expires_after in timer.hpp shows awareness of extreme values; consistency suggests this should too.
Consider documenting the valid range for timeout in the docstring (e.g., @note The timeout duration must not cause integer overflow when added to the current time), or alternatively, implement saturating arithmetic to clamp the deadline to [time_point::min(), time_point::max()].
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@include/boost/corosio/cancel.hpp` around lines 79 - 87, The public helper
cancel_after (which computes timer::clock_type::now() + timeout) can overflow
for extreme timeout durations; update cancel_after to either document the valid
timeout range or, better, perform saturating arithmetic when computing the
deadline: obtain now = timer::clock_type::now(), compute a safe deadline by
clamping now + timeout into [timer::clock_type::time_point::min(),
timer::clock_type::time_point::max()] (or reuse the same logic as expires_after
in timer.hpp), then call cancel_at with that clamped time_point; mention the
change in the function docstring if you add a precondition instead of clamping.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #180 +/- ##
===========================================
+ Coverage 75.99% 76.43% +0.43%
===========================================
Files 94 98 +4
Lines 10385 10523 +138
Branches 2371 2371
===========================================
+ Hits 7892 8043 +151
+ Misses 1777 1757 -20
- Partials 716 723 +7
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 8 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
94f14b6 to
306dfb9
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
include/boost/corosio/cancel.hpp (1)
84-86:⚠️ Potential issue | 🟠 MajorGuard deadline calculation against chrono overflow in
cancel_after.Line 86 uses
timer::clock_type::now() + timeoutdirectly. With extreme duration values, that addition can overflow. Please clamp or enforce/document a strict precondition.Proposed fix (clamp positive overflow, treat non-positive as immediate deadline)
auto cancel_after( capy::IoAwaitable auto&& op, timer& t, timer::duration timeout) { + auto const now = timer::clock_type::now(); + auto deadline = now; + if (timeout > timer::duration::zero()) + { + auto const max_delta = timer::time_point::max() - now; + deadline = (timeout > max_delta) + ? timer::time_point::max() + : now + timeout; + } return cancel_at( - std::forward<decltype(op)>(op), t, - timer::clock_type::now() + timeout); + std::forward<decltype(op)>(op), t, deadline); }#!/bin/bash # Verify current implementation and compare with timer deadline-handling patterns. cat -n include/boost/corosio/cancel.hpp | sed -n '75,95p' rg -n "expires_after|time_point::max|clock_type::now\\(\\) \\+" include/boost/corosio/timer.hpp -C3🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@include/boost/corosio/cancel.hpp` around lines 84 - 86, The call in cancel_after that computes a deadline as timer::clock_type::now() + timeout can overflow for extreme timeout values; modify cancel_after to first check timeout: if timeout <= decltype(timeout)::zero() pass an immediate deadline (now()), otherwise compute now + timeout but clamp to timer::clock_type::time_point::max() on positive overflow (and optionally to time_point::min() for large negative durations), then forward that clamped time_point to cancel_at; update the implementation in cancel_after and reference timer::clock_type::now(), the timeout parameter, cancel_at, and timer::clock_type::time_point::max() when making the change.
🧹 Nitpick comments (1)
include/boost/corosio/detail/timeout_coro.hpp (1)
163-172: Tightenmake_timeoutdocblock with return/completion details.Please add explicit
@returnsemantics and completion/error-condition notes (timer fired vs timer canceled/error) so this helper has a complete behavioral contract.As per coding guidelines: “@return documentation — Describe the returned aggregate and its elements … Error conditions — Document the notable error_code values or conditions … Completion conditions — Bulleted
@lilist …”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@include/boost/corosio/detail/timeout_coro.hpp` around lines 163 - 172, Update the docblock for make_timeout to include an explicit `@return` describing the returned fire-and-forget coroutine handle/aggregate and its behavior (e.g., no value, cancellation-only side-effect), and add completion and error-condition notes: bulleted `@li` entries stating the coroutine completes when the timer fires (and then it calls src.request_stop()/signals the stop source), when the timer is canceled (no stop requested), or when the timer reports an error (document notable error_code values or that errors propagate/log as applicable); place these additions in the comment above make_timeout and reference the Timer parameter behavior (timer fired vs timer canceled/error) and the StopSource/stop_source side-effect so the behavioral contract is complete.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@include/boost/corosio/cancel.hpp`:
- Around line 22-49: Update the public docs for cancel_at and cancel_after to
include the required contract sections: add a "Completion conditions" bulleted
list describing exact cases when the returned awaitable resumes (inner op
completes successfully, inner op completes with error, deadline expires and op
is canceled, caller stop token cancels), a "Concurrency and overlap" paragraph
specifying how parent cancellation is forwarded and that the timer and inner
operation races (timer is cancelled if op completes first; reusing the same
timer requires synchronization), an "Error conditions" list describing which
errors may be produced (propagated op errors, timer errors, and cancellation
resulting in an error equal to capy::cond::canceled), and an "@throws" entry
describing exceptions the functions may throw (e.g., if timer enforcement or
parameter validation throws). Reference the functions by name (cancel_at,
cancel_after), mention the inner operation and timer/stop_token interactions,
and keep the descriptions precise and actionable to satisfy the coding guideline
checklist.
---
Duplicate comments:
In `@include/boost/corosio/cancel.hpp`:
- Around line 84-86: The call in cancel_after that computes a deadline as
timer::clock_type::now() + timeout can overflow for extreme timeout values;
modify cancel_after to first check timeout: if timeout <=
decltype(timeout)::zero() pass an immediate deadline (now()), otherwise compute
now + timeout but clamp to timer::clock_type::time_point::max() on positive
overflow (and optionally to time_point::min() for large negative durations),
then forward that clamped time_point to cancel_at; update the implementation in
cancel_after and reference timer::clock_type::now(), the timeout parameter,
cancel_at, and timer::clock_type::time_point::max() when making the change.
---
Nitpick comments:
In `@include/boost/corosio/detail/timeout_coro.hpp`:
- Around line 163-172: Update the docblock for make_timeout to include an
explicit `@return` describing the returned fire-and-forget coroutine
handle/aggregate and its behavior (e.g., no value, cancellation-only
side-effect), and add completion and error-condition notes: bulleted `@li` entries
stating the coroutine completes when the timer fires (and then it calls
src.request_stop()/signals the stop source), when the timer is canceled (no stop
requested), or when the timer reports an error (document notable error_code
values or that errors propagate/log as applicable); place these additions in the
comment above make_timeout and reference the Timer parameter behavior (timer
fired vs timer canceled/error) and the StopSource/stop_source side-effect so the
behavioral contract is complete.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
test/unit/cancel.cppis excluded by!**/test/**
📒 Files selected for processing (6)
include/boost/corosio.hppinclude/boost/corosio/cancel.hppinclude/boost/corosio/detail/cancel_at_awaitable.hppinclude/boost/corosio/detail/timeout_coro.hppinclude/boost/corosio/native/native.hppinclude/boost/corosio/native/native_cancel.hpp
🚧 Files skipped from review as they are similar to previous changes (2)
- include/boost/corosio/native/native_cancel.hpp
- include/boost/corosio/detail/cancel_at_awaitable.hpp
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.
306dfb9 to
e7d1711
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (2)
include/boost/corosio/native/native_cancel.hpp (1)
120-129: Consider documenting or guarding against duration overflow.The expression
timer::clock_type::now() + timeoutcan overflow for extremetimeoutvalues. For consistency with thetimeroverloads incancel.hpp, consider either documenting a precondition ontimeoutor implementing saturating arithmetic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@include/boost/corosio/native/native_cancel.hpp` around lines 120 - 129, The cancel_after function currently computes timer::clock_type::now() + timeout which can overflow for very large timeout values; update cancel_after (template<auto Backend>) to guard against overflow by clamping/saturating the target time_point (e.g., if timeout would push now() past timer::clock_type::time_point::max() then use time_point::max()) or document a precondition on timeout to match the behavior in cancel.hpp; locate the computation in cancel_after and implement the saturating addition or explicit range check before calling cancel_at(native_timer<Backend>& t, ...).include/boost/corosio/cancel.hpp (1)
113-121: Consider documenting or guarding against duration overflow.The expression
timer::clock_type::now() + timeoutcan overflow for extremetimeoutvalues near the limits ofsteady_clock's range. Consider either:
- Documenting a precondition on
timeout(e.g.,@pre timeout must not cause integer overflow when added to the current time), or- Implementing saturating arithmetic to clamp the deadline.
This applies equally to the owning overload at lines 201-208.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@include/boost/corosio/cancel.hpp` around lines 113 - 121, The call in cancel_after that computes timer::clock_type::now() + timeout (and the similar owning overload) can overflow for extreme timeout values; update cancel_after and the owning overload to either document a precondition that timeout must not overflow when added to timer::clock_type::now() or implement saturating arithmetic that clamps the resulting deadline to timer::clock_type::time_point::max() (and min if applicable) before calling cancel_at; reference the functions cancel_after, cancel_at and the timer/timer::clock_type::now() usage when making the change so the guard or clamp is applied at the moment the deadline is computed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@include/boost/corosio/cancel.hpp`:
- Around line 113-121: The call in cancel_after that computes
timer::clock_type::now() + timeout (and the similar owning overload) can
overflow for extreme timeout values; update cancel_after and the owning overload
to either document a precondition that timeout must not overflow when added to
timer::clock_type::now() or implement saturating arithmetic that clamps the
resulting deadline to timer::clock_type::time_point::max() (and min if
applicable) before calling cancel_at; reference the functions cancel_after,
cancel_at and the timer/timer::clock_type::now() usage when making the change so
the guard or clamp is applied at the moment the deadline is computed.
In `@include/boost/corosio/native/native_cancel.hpp`:
- Around line 120-129: The cancel_after function currently computes
timer::clock_type::now() + timeout which can overflow for very large timeout
values; update cancel_after (template<auto Backend>) to guard against overflow
by clamping/saturating the target time_point (e.g., if timeout would push now()
past timer::clock_type::time_point::max() then use time_point::max()) or
document a precondition on timeout to match the behavior in cancel.hpp; locate
the computation in cancel_after and implement the saturating addition or
explicit range check before calling cancel_at(native_timer<Backend>& t, ...).
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
test/unit/cancel.cppis excluded by!**/test/**test/unit/native/native_cancel.cppis excluded by!**/test/**
📒 Files selected for processing (6)
include/boost/corosio.hppinclude/boost/corosio/cancel.hppinclude/boost/corosio/detail/cancel_at_awaitable.hppinclude/boost/corosio/detail/timeout_coro.hppinclude/boost/corosio/native/native.hppinclude/boost/corosio/native/native_cancel.hpp
🚧 Files skipped from review as they are similar to previous changes (1)
- include/boost/corosio/native/native.hpp
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.
Resolves #119.
Summary by CodeRabbit