Skip to content

feat(gax): add recoverable error query and buffer realignment loop - #14424

Draft
whowes wants to merge 1 commit into
whowes/resumable-upload-buffer-windowfrom
whowes/resumable-upload-recovery
Draft

whowes wants to merge 1 commit into
whowes/resumable-upload-buffer-windowfrom
whowes/resumable-upload-recovery

Conversation

@whowes

@whowes whowes commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Introduces ChunkAttemptCallable with a query-and-realign loop when encountering recoverable protocol errors. Queries the server for the committed offset and adjusts the buffer window before resuming chunk transmission.

@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from f7dacda to ea2e309 Compare September 17, 2026 22:08
@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 implements Category 2 (recoverable) error recovery for resumable uploads in GAX Java, updating ChunkAttemptCallable to query session status, realign the buffer window, and dispatch chunk uploads upon encountering recoverable errors or missing status headers. The review feedback highlights critical improvement opportunities in ChunkAttemptCallable, including addressing a potential infinite loop by correctly classifying query status failures under UploadCommand.QUERY rather than UPLOAD, and preventing race conditions by checking if the retrying future is cancelled immediately after setting inFlightFuture for both query and chunk upload futures.

Comment on lines +151 to +153
public void onFailure(Throwable t) {
failAttempt(attemptFuture, t);
}

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

If the query status call fails with a non-transient error (such as 400 Bad Request or 403 Forbidden), classifying it under the chunk's upload command (e.g., UPLOAD) in UploadResultRetryAlgorithm would incorrectly classify it as RECOVERABLE (since 400 is recoverable for UPLOAD). This would trigger another recovery attempt, leading to an infinite loop of failing query status calls. We should classify the query failure using UploadCommand.QUERY and fail the attempt with a non-retryable exception if it is not transient.

          @Override
          public void onFailure(Throwable t) {
            UploadErrorCategory category = UploadErrorClassifier.classify(t, UploadCommand.QUERY);
            if (category == UploadErrorCategory.TRANSIENT) {
              failAttempt(attemptFuture, t);
            } else {
              failAttempt(
                  attemptFuture,
                  new IllegalStateException("Fatal error during query status: " + t.getMessage(), t));
            }
          }

attemptFuture, new IllegalStateException("queryStatusCallable returned a null future"));
return;
}
this.inFlightFuture = queryFuture;

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.

medium

To prevent a race condition where the retrying future is cancelled concurrently before inFlightFuture is set, we should check if currentRetryingFuture is cancelled immediately after setting inFlightFuture and propagate the cancellation if so.

    this.inFlightFuture = queryFuture;
    if (currentRetryingFuture.isCancelled()) {
      queryFuture.cancel(true);
    }


ApiFuture<ChunkUploadResponse<ResponseT>> chunkFuture =
uploadChunkCallable.futureCall(currentRequest, attemptContext);
this.inFlightFuture = chunkFuture;

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.

medium

To prevent a race condition where the retrying future is cancelled concurrently before inFlightFuture is set, we should check if currentRetryingFuture is cancelled immediately after setting inFlightFuture and propagate the cancellation if so.

    this.inFlightFuture = chunkFuture;
    if (currentRetryingFuture.isCancelled()) {
      chunkFuture.cancel(true);
    }

@whowes
whowes added this pull request to stack #14429 September 17, 2026 22:16
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from ea2e309 to e197ee8 Compare September 17, 2026 22:22
@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-recovery branch from e197ee8 to 12cd1cb Compare September 18, 2026 02:23
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from da1275c to 589ef8e Compare September 18, 2026 03:01
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from 12cd1cb to dd1aaac Compare September 18, 2026 03:01
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 589ef8e to f130ccc Compare September 18, 2026 03:21
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from dd1aaac to 7fb2335 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
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from 7fb2335 to 4795631 Compare September 18, 2026 15:05
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 0e94df8 to 00f7932 Compare September 19, 2026 01:36
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from 4795631 to 1fe75dc Compare September 19, 2026 01:36
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 00f7932 to c6c28b1 Compare September 19, 2026 21:12
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from 1fe75dc to 8456db3 Compare September 19, 2026 21:12
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from c6c28b1 to 4a4b890 Compare September 19, 2026 22:57
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from 8456db3 to 04da457 Compare September 19, 2026 22:57
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 4a4b890 to 14f6204 Compare September 19, 2026 23:17
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from 04da457 to f60b76a Compare September 19, 2026 23:17
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 14f6204 to 0a389c4 Compare September 20, 2026 00:19
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from f60b76a to d5c48d6 Compare September 20, 2026 00:19
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 0a389c4 to 7d192ff Compare September 20, 2026 05:23
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from d5c48d6 to 599ab38 Compare September 20, 2026 05:24
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 7d192ff to 33b9862 Compare September 20, 2026 06:51
@whowes
whowes removed this pull request from stack #14429 September 20, 2026 07:20
Introduces ChunkAttemptCallable with a query-and-realign loop when encountering recoverable protocol errors. Queries the server for the committed offset and adjusts the buffer window before resuming chunk transmission.
@whowes
whowes force-pushed the whowes/resumable-upload-buffer-window branch from 33b9862 to 12f0b82 Compare September 20, 2026 07:45
@whowes
whowes force-pushed the whowes/resumable-upload-recovery branch from 599ab38 to 292921f Compare September 20, 2026 07:45
@sonarqubecloud

Copy link
Copy Markdown

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

Failed conditions
C 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%)
C 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