Repository navigation
Rethrow the last real exception when RPC retries are cancelled with an OperationCanceledException - #1341
Merged
Conversation
…erationCanceledException When script execution was cancelled at the moment a retry sleep finished, Polly threw a plain OperationCanceledException, not a TaskCanceledException. RpcCallRetryHandler did not catch it, so the last connection exception was hidden. StartScript then could not tell that the request had never connected. It assumed the script might have started and called CancelScript, which retries for the full retry duration against an offline Tentacle. This caused intermittent 120s timeouts in DuringStartScript_ForListeningTentacle_ThatIsRetryingTheRpc_AndConnecting_ScriptExecutionCanBeCancelled (LEV-2242). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012a7MWUaLVzQjSTGFiJYka7
gb-8
force-pushed
the
lev-2242/fix-cancellation-race-condition
branch
from
October 30, 2026 06:06
900abf0 to
bafcad3
Compare
akirayamamoto
approved these changes
Oct 30, 2026
akirayamamoto
left a comment
Contributor
There was a problem hiding this comment.
Interesting edge case, LGTM.
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.
Background
The integration test
ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.DuringStartScript_ForListeningTentacle_ThatIsRetryingTheRpc_AndConnecting_ScriptExecutionCanBeCancelledsometimes times out after 120s. It happened about once in 100 runs, most recently on Amazon Linux 2023 in build 24907377, which failed Full Chain 13.0.4673. The failure is tracked in LEV-2242. LEV-869, for the same test on Amazon Linux 2, is very likely the same problem.The cause is a race in the client's cancellation handling, not the infrastructure.
StartScriptis retrying because Tentacle is offline and every attempt fails with a connectingHalibutClientException.TaskCanceledException,RpcCallRetryHandlercatches it and rethrows the last real exception (the connecting exception).StartScriptthen correctly concludes that the request never connected and exits without callingCancelScript.OperationCanceledException(fromThrowIfCancellationRequested) instead.RpcCallRetryHandler's catch filter only matchedTaskCanceledException, so theOperationCanceledExceptionescaped, hiding the connecting exception.StartScript's catch,IsConnectionException()returned false. The client took the safe "script may have started" path and calledCancelScript.CancelScriptruns withCancellationToken.Noneand the full retry duration (1 hour in the test), against a Tentacle that is offline, so the call hangs.This is a real client bug, not only a test bug. A real client cancelling at that moment against an offline Tentacle would keep retrying
CancelScriptfor the whole retry duration instead of stopping promptly.Results
RpcCallRetryHandlernow catchesOperationCanceledException.TaskCanceledExceptionderives from it, so the existing behaviour is kept. The last real exception is now rethrown wherever in the retry loop the cancellation is observed.I added
RpcCallRetryHandlerFixture.ThrowsTheLastExceptionWhenCancelledWithAnOperationCanceledExceptionDuringARetry. It fails without the fix (it receivesSystem.OperationCanceledException) and passes with it.Before
From the failed build: cancellation was requested at 00:00:12.613,
StartScriptwas never attempted a third time, and the client went intoCancelScriptAsyncretries until the test timed out:After
Cancellation during a
StartScriptthat is still trying to connect now always surfaces the connecting exception. Script execution is cancelled without callingCancelScript.How to review this PR
Quality ✔️
RpcCallRetryHandler. It now also catches anOperationCanceledExceptionthat is not aTaskCanceledException. One case to think about: an action that throws such an exception without the caller cancelling, after an earlier retry failed, now surfaces the last real exception.TaskCanceledExceptionalready behaved this way.RpcCallRetryHandlerFixturetests pass locally. I haven't run the full client test project or the integration tests locally; CI covers those.Pre-requisites
Generated by Claude Code