Repository navigation
feat: bound the whole call with WithTotalTimeout - #5
Merged
Merged
Conversation
Timeout bounds a single attempt, which reads like a call budget and is not one. The transport request carrying it is built once, before the retry loop, so every attempt gets the same allowance and the caller waits for all of them plus the backoff between. Five attempts at thirty seconds is over two and a half minutes, nothing capped it, and nothing in the configuration looked wrong. Timeout's own documentation never said "per attempt" either; it does now. WithTotalTimeout is the budget a caller actually experiences. Implemented as a linked token rather than a timeout handed to the transport, because the budget has to cover the waits between attempts and those happen in the executor rather than inside a send. The translation sits outside the retry loop on purpose. Thrown inside, a TransportException would be caught by the loop's own retry guard and the budget would buy further attempts instead of ending the call — the exact opposite of its job. Exceeding the budget throws TransportException rather than surfacing as a cancellation, matching how a per-attempt timeout is already reported: "the deadline passed" and "the caller changed their mind" lead to different retry and alerting decisions. A caller's own cancellation still surfaces as a cancellation, and a test pins that the budget does not relabel it. Unbounded by default, since adding a budget nobody asked for would change the behaviour of every existing client on upgrade. 1386 tests; build clean with warnings-as-errors, format clean, native AOT publishes and runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Katyara11
force-pushed
the
fix/total-call-deadline
branch
from
November 7, 2026 11:03
a8f108f to
6ab7f8b
Compare
3 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WithTimeoutbounds a single attempt, not the call. With retries on, the caller waits thatallowance times the attempt count plus the backoff — five attempts at thirty seconds is over two
and a half minutes, and nothing capped it.
Changes
RunaxClientOptions.TotalTimeoutandWithTotalTimeout(TimeSpan).Timeout's documentation now says "per attempt", which it never did.retries.md,configuration.md, package README,CHANGELOG.Type
Checklist
Notes for review
A linked token, not a transport timeout. The budget has to cover the waits between attempts,
and those happen in the executor rather than inside a send.
The translation sits outside the retry loop deliberately. Thrown inside, a
TransportExceptionwould be caught by the loop's own retry guard and the budget would buy further attempts instead
of ending the call — the exact opposite of its job.
It throws rather than cancelling. Consistent with how a per-attempt timeout is already
reported: "the deadline passed" and "the caller changed their mind" lead to different retry and
alerting decisions. A caller's own cancellation still surfaces as a cancellation, and a test pins
that the budget does not relabel it.
Unbounded by default, because a budget nobody asked for would change the behaviour of every
existing client on upgrade.
The retry-count test asserts "bounded", not a precise elapsed time — CI timing is not a precision
instrument.
Verification
1386 tests;
--warnaserrorclean,dotnet formatclean, native AOT publishes and runs.