Skip to content

Commit 438cda6

Browse files
authored
feat(gax): add globalTimeout settings field to ResumableUploadCallSettings (#14253)
This allows users of resumable upload -powered methods to set an overall timeout on the entire operation
1 parent d9a298b commit 438cda6

2 files changed

Lines changed: 90 additions & 17 deletions

File tree

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/ResumableUploadCallSettings.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,49 @@
3131

3232
import com.google.api.core.BetaApi;
3333
import com.google.auto.value.AutoValue;
34+
import com.google.common.base.Preconditions;
35+
import java.time.Duration;
36+
import org.jspecify.annotations.NullMarked;
37+
import org.jspecify.annotations.Nullable;
3438

3539
/**
3640
* A settings class to configure a {@link ResumableUploadCallable} for executing resumable uploads.
37-
* Encapsulates protocol options such as payload chunk size.
41+
* Encapsulates protocol options such as payload chunk size and global upload timeout.
3842
*/
3943
@BetaApi
4044
@AutoValue
45+
@NullMarked
4146
public abstract class ResumableUploadCallSettings {
4247
private static final int DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; // 8 MB
4348

4449
/** Returns the configured chunk size in bytes (defaults to 8 MB / 8,388,608 bytes). */
4550
public abstract int getChunkSize();
4651

52+
/**
53+
* Returns the global upload timeout governing the entire upload duration, or {@code null} if
54+
* disabled.
55+
*/
56+
public abstract @Nullable Duration getGlobalTimeout();
57+
4758
/**
4859
* Merges another {@code ResumableUploadCallSettings} instance with this one. Fields set in {@code
4960
* other} override fields in this instance.
5061
*
5162
* @param other settings to overlay; may be {@code null}
5263
* @return a new, resolved {@code ResumableUploadCallSettings} instance
5364
*/
54-
public ResumableUploadCallSettings merge(ResumableUploadCallSettings other) {
65+
public ResumableUploadCallSettings merge(@Nullable ResumableUploadCallSettings other) {
5566
if (other == null) {
5667
return this;
5768
}
58-
return toBuilder().setChunkSize(other.getChunkSize()).build();
69+
Builder builder = toBuilder();
70+
if (other.getChunkSize() > 0) {
71+
builder.setChunkSize(other.getChunkSize());
72+
}
73+
if (other.getGlobalTimeout() != null) {
74+
builder.setGlobalTimeout(other.getGlobalTimeout());
75+
}
76+
return builder.build();
5977
}
6078

6179
public abstract Builder toBuilder();
@@ -71,6 +89,20 @@ public abstract static class Builder {
7189

7290
public abstract int getChunkSize();
7391

74-
public abstract ResumableUploadCallSettings build();
92+
public abstract Builder setGlobalTimeout(@Nullable Duration globalTimeout);
93+
94+
public abstract @Nullable Duration getGlobalTimeout();
95+
96+
abstract ResumableUploadCallSettings autoBuild();
97+
98+
public ResumableUploadCallSettings build() {
99+
Preconditions.checkArgument(getChunkSize() > 0, "chunkSize must be > 0");
100+
if (getGlobalTimeout() != null) {
101+
Preconditions.checkArgument(
102+
!getGlobalTimeout().isNegative() && !getGlobalTimeout().isZero(),
103+
"globalTimeout must be positive");
104+
}
105+
return autoBuild();
106+
}
75107
}
76108
}

sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/ResumableUploadCallSettingsTest.java

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,47 +31,88 @@
3131

3232
import static org.junit.jupiter.api.Assertions.assertEquals;
3333
import static org.junit.jupiter.api.Assertions.assertSame;
34+
import static org.junit.jupiter.api.Assertions.assertThrows;
3435

36+
import java.time.Duration;
3537
import org.junit.jupiter.api.Test;
3638

3739
public class ResumableUploadCallSettingsTest {
3840

3941
@Test
40-
public void testDefaultChunkSizeInBuilder() {
41-
ResumableUploadCallSettings settings = ResumableUploadCallSettings.newBuilder().build();
42+
public void testCustomSettingsAndToBuilder() {
43+
ResumableUploadCallSettings settings =
44+
ResumableUploadCallSettings.newBuilder()
45+
.setChunkSize(16 * 1024 * 1024)
46+
.setGlobalTimeout(Duration.ofMinutes(15))
47+
.build();
48+
49+
assertEquals(16 * 1024 * 1024, settings.getChunkSize());
50+
assertEquals(Duration.ofMinutes(15), settings.getGlobalTimeout());
51+
assertEquals(settings, settings.toBuilder().build());
52+
}
4253

43-
assertEquals(8 * 1024 * 1024, settings.getChunkSize());
54+
@Test
55+
public void testInvalidChunkSize_throwsIllegalArgumentException() {
56+
assertThrows(
57+
IllegalArgumentException.class,
58+
() -> ResumableUploadCallSettings.newBuilder().setChunkSize(0).build());
59+
assertThrows(
60+
IllegalArgumentException.class,
61+
() -> ResumableUploadCallSettings.newBuilder().setChunkSize(-1).build());
62+
}
63+
64+
@Test
65+
public void testInvalidGlobalTimeout_throwsIllegalArgumentException() {
66+
assertThrows(
67+
IllegalArgumentException.class,
68+
() -> ResumableUploadCallSettings.newBuilder().setGlobalTimeout(Duration.ZERO).build());
69+
assertThrows(
70+
IllegalArgumentException.class,
71+
() ->
72+
ResumableUploadCallSettings.newBuilder()
73+
.setGlobalTimeout(Duration.ofSeconds(-5))
74+
.build());
4475
}
4576

4677
@Test
47-
public void testCustomInitialization() {
78+
public void testMerge_nullSettings_returnsSameInstance() {
4879
ResumableUploadCallSettings settings =
49-
ResumableUploadCallSettings.newBuilder().setChunkSize(16 * 1024 * 1024).build();
80+
ResumableUploadCallSettings.newBuilder().setChunkSize(4 * 1024 * 1024).build();
5081

51-
assertEquals(16 * 1024 * 1024, settings.getChunkSize());
82+
assertSame(settings, settings.merge(null));
5283
}
5384

5485
@Test
55-
public void testMerge_NullSettings() {
86+
public void testMerge_overridesChunkSizeAndGlobalTimeout() {
5687
ResumableUploadCallSettings stubSettings =
57-
ResumableUploadCallSettings.newBuilder().setChunkSize(4 * 1024 * 1024).build();
88+
ResumableUploadCallSettings.newBuilder()
89+
.setChunkSize(4 * 1024 * 1024)
90+
.setGlobalTimeout(Duration.ofMinutes(10))
91+
.build();
92+
93+
ResumableUploadCallSettings perRequestSettings =
94+
ResumableUploadCallSettings.newBuilder()
95+
.setChunkSize(32 * 1024 * 1024)
96+
.setGlobalTimeout(Duration.ofMinutes(30))
97+
.build();
5898

59-
ResumableUploadCallSettings merged = stubSettings.merge(null);
99+
ResumableUploadCallSettings merged = stubSettings.merge(perRequestSettings);
60100

61-
assertSame(stubSettings, merged);
101+
assertEquals(32 * 1024 * 1024, merged.getChunkSize());
102+
assertEquals(Duration.ofMinutes(30), merged.getGlobalTimeout());
62103
}
63104

64105
@Test
65-
public void testMerge_SettingsOverrides() {
106+
public void testMerge_nullGlobalTimeoutDoesNotOverride() {
66107
ResumableUploadCallSettings stubSettings =
67-
ResumableUploadCallSettings.newBuilder().setChunkSize(4 * 1024 * 1024).build();
108+
ResumableUploadCallSettings.newBuilder().setGlobalTimeout(Duration.ofMinutes(10)).build();
68109

69110
ResumableUploadCallSettings perRequestSettings =
70111
ResumableUploadCallSettings.newBuilder().setChunkSize(32 * 1024 * 1024).build();
71112

72113
ResumableUploadCallSettings merged = stubSettings.merge(perRequestSettings);
73114

74-
// Chunk size overridden by Tier-1 per-request settings
75115
assertEquals(32 * 1024 * 1024, merged.getChunkSize());
116+
assertEquals(Duration.ofMinutes(10), merged.getGlobalTimeout());
76117
}
77118
}

0 commit comments

Comments
 (0)