fix(spanner): fail transactionIdFuture when inline-begin R/W statement fails to start (P0-4)#4
Open
fornwall wants to merge 1 commit into
Open
Conversation
…ils to start A read-write TransactionContextImpl overrides onError/onDone but not onStartFailed. When the begin-carrying first statement of an inline-begin read-write transaction fails to even start its RPC (client closed, executor rejected the task, interceptor threw), AbstractReadContext#startStream calls onStartFailed(withBeginTransaction, t) — a no-op in the base class — rather than onError/onDone. The transactionIdFuture that getTransactionSelector() created for the inline begin therefore never completes, so a subsequent rollback() (and commit()) blocks forever on its unbounded get() of that future, hanging the application thread. The read-only MultiUseReadOnlyTransaction already handles this via onStartFailed. Override onStartFailed in TransactionContextImpl to complete the future exceptionally, mirroring the existing onError/onDone handling, so waiters and rollback/commit are released. Adds a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TS3yFcC6F1aDapSL26q7yb
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.
Issue (review P0-4)
A read-write
TransactionRunnerImpl.TransactionContextImploverridesonError/onDonebut notonStartFailed(the baseAbstractReadContext.onStartFailedis a no-op).When the begin-carrying first statement of an inline-begin read-write transaction fails to even start its streaming RPC — e.g. the client was closed, the executor rejected the task, or an interceptor threw —
AbstractReadContext#startStreamreports it throughonStartFailed(withBeginTransaction, t), not throughonError/onDone:Because
onStartFailedis a no-op for the R/W path, thetransactionIdFuturethatgetTransactionSelector()created for the inline begin never completes. The synchronous throw propagates out of the user'sTransactionCallable, andrunInternalthen callstxn.rollback().rollbackAsync()waits on the never-completingtransactionIdFuture, androllback()'s unboundedget()blocks the application thread forever.commit()'s waiter has the same exposure.The read-only
MultiUseReadOnlyTransactionalready handles this exact case by overridingonStartFailedto fail itstransactionIdFuture.Fix
Override
onStartFailedinTransactionContextImplto completetransactionIdFutureexceptionally whenwithBeginTransactionis set, mirroring the existingonError/onDonehandling. Waiters (androllback()/commit()) are then released. Since no transaction was ever begun on the backend,rollbackAsync()short-circuits to an empty result via its existingcatching(...)and issues no Rollback RPC.Test
Adds
TransactionContextImplTest#testRollbackDoesNotHangWhenInlineBeginStatementFailsToStart, which reproduces the production sequence (getTransactionSelector()→onStartFailed(true, t)) on an inline-begin context and asserts:transactionIdFutureis completed exceptionally with the start failure, androllbackAsync()returns promptly (empty result, no Rollback RPC sent).Without the fix the test fails deterministically at the
isDone()assertion (androllback()would otherwise hang). Verified failing on unmodified code and passing with the fix.TransactionContextImplTest,TransactionRunnerImplTest, andInlineBeginTransactionTest(109 tests) all pass; both changed files passgoogle-java-format.🤖 Generated with Claude Code