Skip to content

feat(gax): add ResumableUploadResultRetryAlgorithm - #14225

Draft
whowes wants to merge 1 commit into
whowes/rewindable-stream-bufferfrom
whowes/resumable-upload-retry-algorithm
Draft

feat(gax): add ResumableUploadResultRetryAlgorithm#14225
whowes wants to merge 1 commit into
whowes/rewindable-stream-bufferfrom
whowes/resumable-upload-retry-algorithm

Conversation

@whowes

@whowes whowes commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Work in progress - not ready for review

@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 the ResumableUploadResultRetryAlgorithm class and its corresponding unit tests to handle retries for resumable uploads based on the Unified Resumable Upload Protocol specification. The feedback identifies an issue where non-ApiException throwables (such as IOException) would still be retried even when the RetryingContext explicitly overrides and restricts the retryable codes. To ensure consistency, the algorithm should return false for non-ApiExceptions when custom retryable codes are provided in the context, and a corresponding test assertion should be added to verify this behavior.

Comment on lines +128 to +134
if (context.getRetryableCodes() != null) {
if (previousThrowable instanceof ApiException) {
return context
.getRetryableCodes()
.contains(((ApiException) previousThrowable).getStatusCode().getCode());
}
}

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

When context.getRetryableCodes() is not null, it indicates that the caller has explicitly overridden the retryable codes for this call (for example, by setting an empty set to disable retries).

In the current implementation, if previousThrowable is not an instance of ApiException (such as an IOException), the code falls through to shouldRetry(previousThrowable, previousResponse), which returns true for IOException. This means IOExceptions would still be retried even if retries were explicitly disabled or customized via the context.

To ensure consistency with other retry algorithms in GAX (like ApiExceptionRetryAlgorithm), we should return false for any non-ApiException when context.getRetryableCodes() is configured.

Suggested change
if (context.getRetryableCodes() != null) {
if (previousThrowable instanceof ApiException) {
return context
.getRetryableCodes()
.contains(((ApiException) previousThrowable).getStatusCode().getCode());
}
}
if (context.getRetryableCodes() != null) {
if (previousThrowable instanceof ApiException) {
return context
.getRetryableCodes()
.contains(((ApiException) previousThrowable).getStatusCode().getCode());
}
return false;
}

Comment on lines +157 to +159
// Default algorithm retries UNAVAILABLE, but context with empty codes forbids it
assertThat(algorithm.shouldRetry(contextWithEmptyCodes, unavailable, null)).isFalse();

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

Add an assertion to verify that IOException is not retried when the context overrides the retryable codes (e.g., with an empty set).

Suggested change
// Default algorithm retries UNAVAILABLE, but context with empty codes forbids it
assertThat(algorithm.shouldRetry(contextWithEmptyCodes, unavailable, null)).isFalse();
// Default algorithm retries UNAVAILABLE, but context with empty codes forbids it
assertThat(algorithm.shouldRetry(contextWithEmptyCodes, unavailable, null)).isFalse();
// IOException should also not be retried when retryable codes are overridden by the context
IOException ioException = new IOException("connection reset");
assertThat(algorithm.shouldRetry(contextWithEmptyCodes, ioException, null)).isFalse();

@whowes
whowes force-pushed the whowes/rewindable-stream-buffer branch from e73249a to 5407505 Compare September 1, 2026 06:29
@whowes
whowes force-pushed the whowes/resumable-upload-retry-algorithm branch from b8f7fc8 to 4a9482c Compare September 1, 2026 06:29
@whowes
whowes force-pushed the whowes/rewindable-stream-buffer branch from 5407505 to b706b52 Compare September 1, 2026 06:39
@whowes
whowes force-pushed the whowes/resumable-upload-retry-algorithm branch from 4a9482c to 28c4867 Compare September 1, 2026 06:39
@whowes
whowes force-pushed the whowes/rewindable-stream-buffer branch from b706b52 to 3d41927 Compare September 1, 2026 07:03
@whowes
whowes force-pushed the whowes/resumable-upload-retry-algorithm branch from 28c4867 to 19504ac Compare September 1, 2026 07:03
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

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

Failed conditions
76.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

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%)

See analysis details on SonarQube Cloud

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