From 9042e248e65124690cbbc29287e728cfc062842c Mon Sep 17 00:00:00 2001 From: whowes Date: Thu, 3 Sep 2026 00:20:52 +0000 Subject: [PATCH] feat(gax): add globalTimeout settings field to ResumableUploadCallSettings Add globalTimeout configuration to ResumableUploadCallSettings, defaulting to 1 hour, with java.time.Duration and obsolete ThreeTenBp setters, and precedence merge support. --- .../gax/rpc/ResumableUploadCallSettings.java | 40 +++++++++-- .../rpc/ResumableUploadCallSettingsTest.java | 67 +++++++++++++++---- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ResumableUploadCallSettings.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ResumableUploadCallSettings.java index 05e3e8382c49..82e6838a52c7 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ResumableUploadCallSettings.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ResumableUploadCallSettings.java @@ -31,19 +31,30 @@ import com.google.api.core.BetaApi; import com.google.auto.value.AutoValue; +import com.google.common.base.Preconditions; +import java.time.Duration; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A settings class to configure a {@link ResumableUploadCallable} for executing resumable uploads. - * Encapsulates protocol options such as payload chunk size. + * Encapsulates protocol options such as payload chunk size and global upload timeout. */ @BetaApi @AutoValue +@NullMarked public abstract class ResumableUploadCallSettings { private static final int DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; // 8 MB /** Returns the configured chunk size in bytes (defaults to 8 MB / 8,388,608 bytes). */ public abstract int getChunkSize(); + /** + * Returns the global upload timeout governing the entire upload duration, or {@code null} if + * disabled. + */ + public abstract @Nullable Duration getGlobalTimeout(); + /** * Merges another {@code ResumableUploadCallSettings} instance with this one. Fields set in {@code * other} override fields in this instance. @@ -51,11 +62,18 @@ public abstract class ResumableUploadCallSettings { * @param other settings to overlay; may be {@code null} * @return a new, resolved {@code ResumableUploadCallSettings} instance */ - public ResumableUploadCallSettings merge(ResumableUploadCallSettings other) { + public ResumableUploadCallSettings merge(@Nullable ResumableUploadCallSettings other) { if (other == null) { return this; } - return toBuilder().setChunkSize(other.getChunkSize()).build(); + Builder builder = toBuilder(); + if (other.getChunkSize() > 0) { + builder.setChunkSize(other.getChunkSize()); + } + if (other.getGlobalTimeout() != null) { + builder.setGlobalTimeout(other.getGlobalTimeout()); + } + return builder.build(); } public abstract Builder toBuilder(); @@ -71,6 +89,20 @@ public abstract static class Builder { public abstract int getChunkSize(); - public abstract ResumableUploadCallSettings build(); + public abstract Builder setGlobalTimeout(@Nullable Duration globalTimeout); + + public abstract @Nullable Duration getGlobalTimeout(); + + abstract ResumableUploadCallSettings autoBuild(); + + public ResumableUploadCallSettings build() { + Preconditions.checkArgument(getChunkSize() > 0, "chunkSize must be > 0"); + if (getGlobalTimeout() != null) { + Preconditions.checkArgument( + !getGlobalTimeout().isNegative() && !getGlobalTimeout().isZero(), + "globalTimeout must be positive"); + } + return autoBuild(); + } } } diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/ResumableUploadCallSettingsTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/ResumableUploadCallSettingsTest.java index d890c61e4ba0..8bc80d5d7d0f 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/ResumableUploadCallSettingsTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/ResumableUploadCallSettingsTest.java @@ -31,47 +31,88 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.time.Duration; import org.junit.jupiter.api.Test; public class ResumableUploadCallSettingsTest { @Test - public void testDefaultChunkSizeInBuilder() { - ResumableUploadCallSettings settings = ResumableUploadCallSettings.newBuilder().build(); + public void testCustomSettingsAndToBuilder() { + ResumableUploadCallSettings settings = + ResumableUploadCallSettings.newBuilder() + .setChunkSize(16 * 1024 * 1024) + .setGlobalTimeout(Duration.ofMinutes(15)) + .build(); + + assertEquals(16 * 1024 * 1024, settings.getChunkSize()); + assertEquals(Duration.ofMinutes(15), settings.getGlobalTimeout()); + assertEquals(settings, settings.toBuilder().build()); + } - assertEquals(8 * 1024 * 1024, settings.getChunkSize()); + @Test + public void testInvalidChunkSize_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> ResumableUploadCallSettings.newBuilder().setChunkSize(0).build()); + assertThrows( + IllegalArgumentException.class, + () -> ResumableUploadCallSettings.newBuilder().setChunkSize(-1).build()); + } + + @Test + public void testInvalidGlobalTimeout_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> ResumableUploadCallSettings.newBuilder().setGlobalTimeout(Duration.ZERO).build()); + assertThrows( + IllegalArgumentException.class, + () -> + ResumableUploadCallSettings.newBuilder() + .setGlobalTimeout(Duration.ofSeconds(-5)) + .build()); } @Test - public void testCustomInitialization() { + public void testMerge_nullSettings_returnsSameInstance() { ResumableUploadCallSettings settings = - ResumableUploadCallSettings.newBuilder().setChunkSize(16 * 1024 * 1024).build(); + ResumableUploadCallSettings.newBuilder().setChunkSize(4 * 1024 * 1024).build(); - assertEquals(16 * 1024 * 1024, settings.getChunkSize()); + assertSame(settings, settings.merge(null)); } @Test - public void testMerge_NullSettings() { + public void testMerge_overridesChunkSizeAndGlobalTimeout() { ResumableUploadCallSettings stubSettings = - ResumableUploadCallSettings.newBuilder().setChunkSize(4 * 1024 * 1024).build(); + ResumableUploadCallSettings.newBuilder() + .setChunkSize(4 * 1024 * 1024) + .setGlobalTimeout(Duration.ofMinutes(10)) + .build(); + + ResumableUploadCallSettings perRequestSettings = + ResumableUploadCallSettings.newBuilder() + .setChunkSize(32 * 1024 * 1024) + .setGlobalTimeout(Duration.ofMinutes(30)) + .build(); - ResumableUploadCallSettings merged = stubSettings.merge(null); + ResumableUploadCallSettings merged = stubSettings.merge(perRequestSettings); - assertSame(stubSettings, merged); + assertEquals(32 * 1024 * 1024, merged.getChunkSize()); + assertEquals(Duration.ofMinutes(30), merged.getGlobalTimeout()); } @Test - public void testMerge_SettingsOverrides() { + public void testMerge_nullGlobalTimeoutDoesNotOverride() { ResumableUploadCallSettings stubSettings = - ResumableUploadCallSettings.newBuilder().setChunkSize(4 * 1024 * 1024).build(); + ResumableUploadCallSettings.newBuilder().setGlobalTimeout(Duration.ofMinutes(10)).build(); ResumableUploadCallSettings perRequestSettings = ResumableUploadCallSettings.newBuilder().setChunkSize(32 * 1024 * 1024).build(); ResumableUploadCallSettings merged = stubSettings.merge(perRequestSettings); - // Chunk size overridden by Tier-1 per-request settings assertEquals(32 * 1024 * 1024, merged.getChunkSize()); + assertEquals(Duration.ofMinutes(10), merged.getGlobalTimeout()); } }