Skip to content

fix(spanner): stop AsyncResultSetImpl producer when callback throws with a full buffer#13827

Open
fornwall wants to merge 5 commits into
googleapis:mainfrom
fornwall:fix/p0-2-async-result-set-busy-spin
Open

fix(spanner): stop AsyncResultSetImpl producer when callback throws with a full buffer#13827
fornwall wants to merge 5 commits into
googleapis:mainfrom
fornwall:fix/p0-2-async-result-set-busy-spin

Conversation

@fornwall

@fornwall fornwall commented Jul 18, 2026

Copy link
Copy Markdown

Check cursorReturnedDoneOrException when deciding if rows should
continue to be produced in AsyncResultSetImpl, to avoid busy-spinning
forever if a user-supplied ReadyCallback threw an exception while the
row buffer was full.

…ith a full buffer

When a user-supplied ReadyCallback throws an exception, CallbackRunnable
sets executionException and cursorReturnedDoneOrException, but leaves the
state at CONSUMING (shouldStop == false). If the producer was waiting for
the callback to consume rows from a full buffer at that moment, it
recomputed its stop condition solely from state.shouldStop and looped
again: startCallbackWithBufferLatchIfNecessary refuses to restart the
callback or re-arm bufferConsumptionLatch once
cursorReturnedDoneOrException is set, so the await returned immediately
on the already-drained latch. The result was an infinite busy-spin at
100% CPU, the delegate result set was never closed, and the ApiFuture
returned by setCallback()/toListAsync() never completed.

Include cursorReturnedDoneOrException in the producer's stop condition:
once it is set, no callback will ever consume rows again, so producing
more rows is always pointless.

Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
@fornwall
fornwall requested review from a team as code owners July 18, 2026 11:12

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request prevents the producer thread in AsyncResultSetImpl from spinning indefinitely when a callback throws an exception while the buffer is full, by checking cursorReturnedDoneOrException in the loop. It also adds a unit test to verify this behavior. The review feedback points out a potential thread leak in the new test because the created ExecutorService is not shut down, and suggests wrapping the test execution in a try-finally block to ensure proper resource cleanup.

fornwall added 4 commits July 18, 2026 16:30
Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
@fornwall
fornwall force-pushed the fix/p0-2-async-result-set-busy-spin branch from f425455 to 7f76ad6 Compare July 19, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant