Skip to content

feat(gax): add rewindable stream buffer for chunk recovery - #14423

Draft
whowes wants to merge 1 commit into
whowes/resumable-upload-chunk-retryfrom
whowes/resumable-upload-buffer-window
Draft

whowes wants to merge 1 commit into
whowes/resumable-upload-chunk-retryfrom
whowes/resumable-upload-buffer-window

Conversation

@whowes

@whowes whowes commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Introduces RewindableStreamBuffer to manage a reusable single-chunk buffer over an InputStream. Supports compacting unacknowledged bytes forward in-place and topping up from the stream when realigning to a server-committed offset.

@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from ba8840b to 6d89325 Compare September 17, 2026 22:08

@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 introduces RewindableStreamBuffer to manage a single-chunk buffer over an InputStream for resumable uploads, supporting forward compaction and topping up during recovery realignment. ResumableUploadChunkCoordinator and ChunkUploadRequest are updated to utilize this buffer to avoid unnecessary array copies. The review feedback identifies two key issues: first, unconditionally calling streamBuffer.fill(currentOffset) on every chunk transmission can overwrite compacted bytes and cause silent data corruption; second, the realignTo method in RewindableStreamBuffer is currently implemented and tested but never actually integrated into the recovery path of the coordinator.

Comment on lines 238 to 243
try {
bytesRead = ByteStreams.read(payload, buffer, 0, chunkSize);
streamBuffer.fill(currentOffset);
} catch (IOException e) {
finish(null, e);
return;
}

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.

critical

Unconditionally calling streamBuffer.fill(currentOffset) on every chunk transmission will overwrite the compacted and topped-up bytes in the buffer if the buffer was previously realigned (e.g., during recovery realignment). This leads to silent data corruption because the stream will be read from its current position (which is ahead of currentOffset), resulting in incorrect bytes being uploaded at currentOffset. We should only call fill if the buffer does not already contain the data for currentOffset.

Suggested change
try {
bytesRead = ByteStreams.read(payload, buffer, 0, chunkSize);
streamBuffer.fill(currentOffset);
} catch (IOException e) {
finish(null, e);
return;
}
try {
if (streamBuffer.getBufferBaseOffset() != currentOffset || streamBuffer.isEmpty()) {
streamBuffer.fill(currentOffset);
}
} catch (IOException e) {
finish(null, e);
return;
}

* offset or beyond the current buffer window
* @throws IOException if reading from the stream fails
*/
void realignTo(long committedOffset) throws IOException {

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.

high

The realignTo method is implemented and thoroughly tested, but it is never actually called within ResumableUploadChunkCoordinator or any other production code in this pull request. Since buffer is a private field in ResumableUploadChunkCoordinator and has no package-private getter, the recovery realignment logic remains completely unused. Please ensure that realignTo is integrated into the recovery/retry path (e.g., when handling server-committed offsets after a recoverable failure) so that the rewindable stream buffer is actually utilized for chunk recovery.

@whowes
whowes added this pull request to stack #14429 September 17, 2026 22:16
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 6d89325 to da1275c Compare September 18, 2026 02:23
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch 2 times, most recently from 589ef8e to f130ccc Compare September 18, 2026 03:21
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from f130ccc to 0e94df8 Compare September 18, 2026 15:05
Introduce RewindableStreamBuffer managing a single-chunk buffer over an
InputStream, supporting forward compaction and topping up upon recovery
realignment without mark()/reset(). Enforces boundaries by throwing
FailedPreconditionException when a server offset is below the base
offset or beyond the current buffer window.

Use payloadLength in ChunkUploadRequest to avoid allocating temporary
byte arrays for full-sized chunks while reusing a single backing array.
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 0e94df8 to 00f7932 Compare September 19, 2026 01:36
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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