-
Notifications
You must be signed in to change notification settings - Fork 199
SNOW-3745081: fix retry in GCS storage client #2688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sfc-gh-dszmolka
merged 5 commits into
master
from
SNOW-3745081-fix-gcs-storageexception-retry
Jul 7, 2026
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
3350fcf
fix retry in GCS storage client
sfc-gh-dszmolka 7d57b20
fix retry in GCS storage client
sfc-gh-dszmolka 6ed90f8
fix retry in GCS storage client
sfc-gh-dszmolka 62ddf1a
AWS strategy to honour JDBC settings instead of only the SDK-default …
sfc-gh-dszmolka 69b2fa5
addressing Driver team review comments
sfc-gh-dszmolka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
103 changes: 103 additions & 0 deletions
103
...t/java/net/snowflake/client/internal/jdbc/cloud/storage/GCSDefaultAccessStrategyTest.java
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| package net.snowflake.client.internal.jdbc.cloud.storage; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||
| import static org.mockito.Mockito.CALLS_REAL_METHODS; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.when; | ||
| import static org.mockito.Mockito.withSettings; | ||
|
|
||
| import com.google.cloud.storage.StorageException; | ||
| import java.util.HashMap; | ||
| import net.snowflake.client.api.exception.SnowflakeSQLException; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class GCSDefaultAccessStrategyTest { | ||
|
|
||
| private GCSDefaultAccessStrategy strategy; | ||
| private SnowflakeGCSClient mockGcsClient; | ||
|
|
||
| @BeforeEach | ||
| void setUp() throws SnowflakeSQLException { | ||
| // Use empty credentials (no GCS_ACCESS_TOKEN) so the constructor falls back to anonymous auth, | ||
| // which does not probe any real GCS endpoint. | ||
| StageInfo stage = | ||
| StageInfo.createStageInfo( | ||
| "GCS", "test-bucket/path", new HashMap<>(), "US-CENTRAL1", null, null, true); | ||
| strategy = new GCSDefaultAccessStrategy(stage, /* session= */ null); | ||
|
|
||
| // Mock SnowflakeGCSClient to avoid real network calls and to control retry parameters. | ||
| // getRetryBackoffMin is set to 0 so tests are not slowed down by exponential backoff. | ||
| mockGcsClient = | ||
| mock(SnowflakeGCSClient.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); | ||
| when(mockGcsClient.getMaxRetries()).thenReturn(1); | ||
| when(mockGcsClient.getRetryBackoffMin()).thenReturn(0); | ||
| when(mockGcsClient.getRetryBackoffMaxExponent()).thenReturn(0); | ||
| } | ||
|
|
||
| // A direct StorageException(503) below the max retry threshold must not be thrown — the handler | ||
| // should perform a backoff and signal to the caller that a retry should proceed. | ||
| @Test | ||
| void testHandleStorageExceptionDirect503BelowMaxRetries() { | ||
| StorageException storageException = new StorageException(503, "Service Unavailable"); | ||
|
|
||
| assertDoesNotThrow( | ||
| () -> | ||
| strategy.handleStorageException( | ||
| storageException, | ||
| /* retryCount= */ 1, | ||
| "upload", | ||
| /* session= */ null, | ||
| /* command= */ null, | ||
| /* queryId= */ null, | ||
| mockGcsClient)); | ||
| } | ||
|
|
||
| // A StorageException(503) wrapped inside another exception must also be detected via cause-chain | ||
| // walking and treated as retryable when below the max retry threshold. This is the key | ||
| // regression scenario: uploadWithDownScopedToken wraps the original StorageException inside a | ||
| // SnowflakeSQLException before re-throwing it to the outer upload() retry loop. | ||
| @Test | ||
| void testHandleStorageExceptionWrapped503BelowMaxRetries() { | ||
| StorageException storageException = new StorageException(503, "Service Unavailable"); | ||
| RuntimeException wrappedException = | ||
| new RuntimeException( | ||
| "Encountered exception during upload: " + storageException.getMessage(), | ||
| storageException); | ||
|
|
||
| assertDoesNotThrow( | ||
| () -> | ||
| strategy.handleStorageException( | ||
| wrappedException, | ||
| /* retryCount= */ 1, | ||
| "upload", | ||
| /* session= */ null, | ||
| /* command= */ null, | ||
| /* queryId= */ null, | ||
| mockGcsClient)); | ||
| } | ||
|
|
||
| // When a wrapped StorageException(503) arrives after the maximum number of retries has been | ||
| // exceeded the handler must surface it as a SnowflakeSQLException so the caller stops retrying. | ||
| @Test | ||
| void testHandleStorageExceptionWrapped503OverMaxRetries() { | ||
| StorageException storageException = new StorageException(503, "Service Unavailable"); | ||
| RuntimeException wrappedException = | ||
| new RuntimeException( | ||
| "Encountered exception during upload: " + storageException.getMessage(), | ||
| storageException); | ||
|
|
||
| assertThrows( | ||
| SnowflakeSQLException.class, | ||
| () -> | ||
| strategy.handleStorageException( | ||
| wrappedException, | ||
| /* retryCount= */ 2, | ||
| "upload", | ||
| /* session= */ null, | ||
| /* command= */ null, | ||
| /* queryId= */ null, | ||
| mockGcsClient)); | ||
| } | ||
| } |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.