Skip to content

Commit 3405611

Browse files
authored
feat: allow specifying an expected object size for resumable operations. (#2661)
Update resumable upload failure detection to be more specific about classifying a request as SCENARIO_5 Fixes #2511
1 parent 380057b commit 3405611

17 files changed

+347
-32
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/BidiBlobWriteSessionConfig.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public WritableByteChannelSession<?, BlobInfo> writeSession(
114114
BidiWriteObjectRequest req = grpc.getBidiWriteObjectRequest(info, opts);
115115

116116
ApiFuture<BidiResumableWrite> startResumableWrite =
117-
grpc.startResumableWrite(grpcCallContext, req);
117+
grpc.startResumableWrite(grpcCallContext, req, opts);
118118
return ResumableMedia.gapic()
119119
.write()
120120
.bidiByteChannel(grpc.storageClient.bidiWriteObjectCallable())

google-cloud-storage/src/main/java/com/google/cloud/storage/DefaultBlobWriteSessionConfig.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public WritableByteChannelSession<?, BlobInfo> writeSession(
156156
WriteObjectRequest req = grpc.getWriteObjectRequest(info, opts);
157157

158158
ApiFuture<ResumableWrite> startResumableWrite =
159-
grpc.startResumableWrite(grpcCallContext, req);
159+
grpc.startResumableWrite(grpcCallContext, req, opts);
160160
return ResumableMedia.gapic()
161161
.write()
162162
.byteChannel(

google-cloud-storage/src/main/java/com/google/cloud/storage/GapicBidiUnbufferedWritableByteChannel.java

+12-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.api.gax.rpc.ApiException;
2525
import com.google.api.gax.rpc.ApiStreamObserver;
2626
import com.google.api.gax.rpc.BidiStreamingCallable;
27+
import com.google.api.gax.rpc.ErrorDetails;
2728
import com.google.api.gax.rpc.OutOfRangeException;
2829
import com.google.cloud.storage.ChunkSegmenter.ChunkSegment;
2930
import com.google.cloud.storage.Conversions.Decoder;
@@ -345,10 +346,17 @@ public void onNext(BidiWriteObjectResponse value) {
345346
public void onError(Throwable t) {
346347
if (t instanceof OutOfRangeException) {
347348
OutOfRangeException oore = (OutOfRangeException) t;
348-
clientDetectedError(
349-
ResumableSessionFailureScenario.SCENARIO_5.toStorageException(
350-
ImmutableList.of(lastWrittenRequest), null, context, oore));
351-
} else if (t instanceof ApiException) {
349+
ErrorDetails ed = oore.getErrorDetails();
350+
if (!(ed != null
351+
&& ed.getErrorInfo() != null
352+
&& ed.getErrorInfo().getReason().equals("GRPC_MISMATCHED_UPLOAD_SIZE"))) {
353+
clientDetectedError(
354+
ResumableSessionFailureScenario.SCENARIO_5.toStorageException(
355+
ImmutableList.of(lastWrittenRequest), null, context, oore));
356+
return;
357+
}
358+
}
359+
if (t instanceof ApiException) {
352360
// use StorageExceptions logic to translate from ApiException to our status codes ensuring
353361
// things fall in line with our retry handlers.
354362
// This is suboptimal, as it will initialize a second exception, however this is the

google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedChunkedResumableWritableByteChannel.java

+13-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.api.gax.rpc.ApiException;
2525
import com.google.api.gax.rpc.ApiStreamObserver;
2626
import com.google.api.gax.rpc.ClientStreamingCallable;
27+
import com.google.api.gax.rpc.ErrorDetails;
2728
import com.google.api.gax.rpc.OutOfRangeException;
2829
import com.google.cloud.storage.ChunkSegmenter.ChunkSegment;
2930
import com.google.cloud.storage.Conversions.Decoder;
@@ -267,11 +268,18 @@ public void onError(Throwable t) {
267268
if (t instanceof OutOfRangeException) {
268269
OutOfRangeException oore = (OutOfRangeException) t;
269270
open = false;
270-
StorageException storageException =
271-
ResumableSessionFailureScenario.SCENARIO_5.toStorageException(
272-
segments, null, context, oore);
273-
invocationHandle.setException(storageException);
274-
} else if (t instanceof ApiException) {
271+
ErrorDetails ed = oore.getErrorDetails();
272+
if (!(ed != null
273+
&& ed.getErrorInfo() != null
274+
&& ed.getErrorInfo().getReason().equals("GRPC_MISMATCHED_UPLOAD_SIZE"))) {
275+
StorageException storageException =
276+
ResumableSessionFailureScenario.SCENARIO_5.toStorageException(
277+
segments, null, context, oore);
278+
invocationHandle.setException(storageException);
279+
return;
280+
}
281+
}
282+
if (t instanceof ApiException) {
275283
// use StorageExceptions logic to translate from ApiException to our status codes ensuring
276284
// things fall in line with our retry handlers.
277285
// This is suboptimal, as it will initialize a second exception, however this is the

google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.google.api.gax.rpc.BidiStreamingCallable;
2222
import com.google.api.gax.rpc.ClientStreamingCallable;
2323
import com.google.api.gax.rpc.UnaryCallable;
24+
import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
25+
import com.google.cloud.storage.UnifiedOpts.Opts;
2426
import com.google.common.util.concurrent.MoreExecutors;
2527
import com.google.storage.v2.BidiWriteObjectRequest;
2628
import com.google.storage.v2.BidiWriteObjectResponse;
@@ -50,7 +52,8 @@ GapicBidiWritableByteChannelSessionBuilder bidiByteChannel(
5052

5153
ApiFuture<ResumableWrite> resumableWrite(
5254
UnaryCallable<StartResumableWriteRequest, StartResumableWriteResponse> callable,
53-
WriteObjectRequest writeObjectRequest) {
55+
WriteObjectRequest writeObjectRequest,
56+
Opts<ObjectTargetOpt> opts) {
5457
StartResumableWriteRequest.Builder b = StartResumableWriteRequest.newBuilder();
5558
if (writeObjectRequest.hasWriteObjectSpec()) {
5659
b.setWriteObjectSpec(writeObjectRequest.getWriteObjectSpec());
@@ -61,7 +64,7 @@ ApiFuture<ResumableWrite> resumableWrite(
6164
if (writeObjectRequest.hasObjectChecksums()) {
6265
b.setObjectChecksums(writeObjectRequest.getObjectChecksums());
6366
}
64-
StartResumableWriteRequest req = b.build();
67+
StartResumableWriteRequest req = opts.startResumableWriteRequest().apply(b).build();
6568
Function<String, WriteObjectRequest> f =
6669
uploadId ->
6770
writeObjectRequest.toBuilder().clearWriteObjectSpec().setUploadId(uploadId).build();
@@ -80,7 +83,8 @@ ApiFuture<ResumableWrite> resumableWrite(
8083

8184
ApiFuture<BidiResumableWrite> bidiResumableWrite(
8285
UnaryCallable<StartResumableWriteRequest, StartResumableWriteResponse> x,
83-
BidiWriteObjectRequest writeObjectRequest) {
86+
BidiWriteObjectRequest writeObjectRequest,
87+
Opts<ObjectTargetOpt> opts) {
8488
StartResumableWriteRequest.Builder b = StartResumableWriteRequest.newBuilder();
8589
if (writeObjectRequest.hasWriteObjectSpec()) {
8690
b.setWriteObjectSpec(writeObjectRequest.getWriteObjectSpec());
@@ -91,7 +95,7 @@ ApiFuture<BidiResumableWrite> bidiResumableWrite(
9195
if (writeObjectRequest.hasObjectChecksums()) {
9296
b.setObjectChecksums(writeObjectRequest.getObjectChecksums());
9397
}
94-
StartResumableWriteRequest req = b.build();
98+
StartResumableWriteRequest req = opts.startResumableWriteRequest().apply(b).build();
9599
Function<String, BidiWriteObjectRequest> f =
96100
uploadId ->
97101
writeObjectRequest.toBuilder().clearWriteObjectSpec().setUploadId(uploadId).build();

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java

+9-7
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ public Blob internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOpt> o
320320
ClientStreamingCallable<WriteObjectRequest, WriteObjectResponse> write =
321321
storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext);
322322

323-
ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req);
323+
ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts);
324324
ApiFuture<GrpcResumableSession> session2 =
325325
ApiFutures.transform(
326326
start,
@@ -365,7 +365,7 @@ public Blob createFrom(
365365
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
366366
WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts);
367367

368-
ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req);
368+
ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts);
369369

370370
BufferedWritableByteChannelSession<WriteObjectResponse> session =
371371
ResumableMedia.gapic()
@@ -790,7 +790,7 @@ public GrpcBlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options
790790
// in JSON, the starting of the resumable session happens before the invocation of write can
791791
// happen. Emulate the same thing here.
792792
// 1. create the future
793-
ApiFuture<ResumableWrite> startResumableWrite = startResumableWrite(grpcCallContext, req);
793+
ApiFuture<ResumableWrite> startResumableWrite = startResumableWrite(grpcCallContext, req, opts);
794794
// 2. await the result of the future
795795
ResumableWrite resumableWrite = ApiFutureUtils.await(startResumableWrite);
796796
// 3. wrap the result in another future container before constructing the BlobWriteChannel
@@ -1919,7 +1919,7 @@ private UnbufferedReadableByteChannelSession<Object> unbufferedReadSession(
19191919

19201920
@VisibleForTesting
19211921
ApiFuture<ResumableWrite> startResumableWrite(
1922-
GrpcCallContext grpcCallContext, WriteObjectRequest req) {
1922+
GrpcCallContext grpcCallContext, WriteObjectRequest req, Opts<ObjectTargetOpt> opts) {
19231923
Set<StatusCode.Code> codes = resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(req));
19241924
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
19251925
return ResumableMedia.gapic()
@@ -1928,11 +1928,12 @@ ApiFuture<ResumableWrite> startResumableWrite(
19281928
storageClient
19291929
.startResumableWriteCallable()
19301930
.withDefaultCallContext(merge.withRetryableCodes(codes)),
1931-
req);
1931+
req,
1932+
opts);
19321933
}
19331934

19341935
ApiFuture<BidiResumableWrite> startResumableWrite(
1935-
GrpcCallContext grpcCallContext, BidiWriteObjectRequest req) {
1936+
GrpcCallContext grpcCallContext, BidiWriteObjectRequest req, Opts<ObjectTargetOpt> opts) {
19361937
Set<StatusCode.Code> codes = resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(req));
19371938
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
19381939
return ResumableMedia.gapic()
@@ -1941,7 +1942,8 @@ ApiFuture<BidiResumableWrite> startResumableWrite(
19411942
storageClient
19421943
.startResumableWriteCallable()
19431944
.withDefaultCallContext(merge.withRetryableCodes(codes)),
1944-
req);
1945+
req,
1946+
opts);
19451947
}
19461948

19471949
private SourceObject sourceObjectEncode(SourceBlob from) {

google-cloud-storage/src/main/java/com/google/cloud/storage/JournalingBlobWriteSessionConfig.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public WritableByteChannelSession<?, BlobInfo> writeSession(
190190
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
191191
ApiFuture<ResumableWrite> f =
192192
grpcStorage.startResumableWrite(
193-
grpcCallContext, grpcStorage.getWriteObjectRequest(info, opts));
193+
grpcCallContext, grpcStorage.getWriteObjectRequest(info, opts), opts);
194194
ApiFuture<WriteCtx<ResumableWrite>> start =
195195
ApiFutures.transform(f, WriteCtx::new, MoreExecutors.directExecutor());
196196

google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ public void rewindTo(long offset) {
205205
&& contentLength != null
206206
&& contentLength > 0) {
207207
String errorMessage = cause.getContent().toLowerCase(Locale.US);
208-
if (errorMessage.contains("content-range")) {
208+
if (errorMessage.contains("content-range")
209+
&& !errorMessage.contains("earlier")) { // TODO: exclude "earlier request"
209210
StorageException se =
210211
ResumableSessionFailureScenario.SCENARIO_5.toStorageException(
211212
uploadId, response, cause, cause::getContent);

google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt;
4444
import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
4545
import com.google.cloud.storage.UnifiedOpts.Opts;
46+
import com.google.cloud.storage.UnifiedOpts.ResumableUploadExpectedObjectSize;
4647
import com.google.cloud.storage.UnifiedOpts.SourceGenerationMatch;
4748
import com.google.cloud.storage.UnifiedOpts.SourceGenerationNotMatch;
4849
import com.google.cloud.storage.UnifiedOpts.SourceMetagenerationMatch;
@@ -102,7 +103,8 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab
102103
|| o instanceof SourceMetagenerationMatch
103104
|| o instanceof SourceMetagenerationNotMatch
104105
|| o instanceof Crc32cMatch
105-
|| o instanceof Md5Match;
106+
|| o instanceof Md5Match
107+
|| o instanceof ResumableUploadExpectedObjectSize;
106108
TO_EXCLUDE_FROM_PARTS = tmp.negate();
107109
}
108110

google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java

+17
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,23 @@ public static BlobWriteOption detectContentType() {
13521352
return new BlobWriteOption(UnifiedOpts.detectContentType());
13531353
}
13541354

1355+
/**
1356+
* Set a precondition on the number of bytes that GCS should expect for a resumable upload. See
1357+
* the docs for <a
1358+
* href="https://cloud.google.com/storage/docs/json_api/v1/parameters#xuploadcontentlength">X-Upload-Content-Length</a>
1359+
* for more detail.
1360+
*
1361+
* <p>If the method invoked with this option does not perform a resumable upload, this option
1362+
* will be ignored.
1363+
*
1364+
* @since 2.42.0
1365+
*/
1366+
@BetaApi
1367+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
1368+
public static BlobWriteOption expectedObjectSize(long objectContentSize) {
1369+
return new BlobWriteOption(UnifiedOpts.resumableUploadExpectedObjectSize(objectContentSize));
1370+
}
1371+
13551372
/**
13561373
* Deduplicate any options which are the same parameter. The value which comes last in {@code
13571374
* os} will be the value included in the return.

google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java

+34
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import com.google.storage.v2.ReadObjectRequest;
5656
import com.google.storage.v2.RestoreObjectRequest;
5757
import com.google.storage.v2.RewriteObjectRequest;
58+
import com.google.storage.v2.StartResumableWriteRequest;
5859
import com.google.storage.v2.UpdateBucketRequest;
5960
import com.google.storage.v2.UpdateHmacKeyRequest;
6061
import com.google.storage.v2.UpdateObjectRequest;
@@ -196,6 +197,10 @@ default Mapper<ComposeObjectRequest.Builder> composeObject() {
196197
default Mapper<RewriteObjectRequest.Builder> rewriteObject() {
197198
return Mapper.identity();
198199
}
200+
201+
default Mapper<StartResumableWriteRequest.Builder> startResumableWrite() {
202+
return Mapper.identity();
203+
}
199204
}
200205

201206
/** Base interface for those Opts which are applicable to Bucket List operations */
@@ -487,6 +492,12 @@ static Projection projection(@NonNull String projection) {
487492
return new Projection(projection);
488493
}
489494

495+
static ResumableUploadExpectedObjectSize resumableUploadExpectedObjectSize(
496+
long expectedObjectSize) {
497+
checkArgument(expectedObjectSize >= 0, "expectedObjectSize >= 0 (%s >= 0)", expectedObjectSize);
498+
return new ResumableUploadExpectedObjectSize(expectedObjectSize);
499+
}
500+
490501
static SoftDeleted softDeleted(boolean softDeleted) {
491502
return new SoftDeleted(softDeleted);
492503
}
@@ -1832,6 +1843,25 @@ public Mapper<UpdateObjectRequest.Builder> updateObject() {
18321843
}
18331844
}
18341845

1846+
static final class ResumableUploadExpectedObjectSize extends RpcOptVal<@NonNull Long>
1847+
implements ObjectTargetOpt {
1848+
private static final long serialVersionUID = 3640126281492196357L;
1849+
1850+
private ResumableUploadExpectedObjectSize(@NonNull Long val) {
1851+
super(StorageRpc.Option.X_UPLOAD_CONTENT_LENGTH, val);
1852+
}
1853+
1854+
@Override
1855+
public Mapper<StartResumableWriteRequest.Builder> startResumableWrite() {
1856+
return b -> {
1857+
if (val > 0) {
1858+
b.getWriteObjectSpecBuilder().setObjectSize(val);
1859+
}
1860+
return b;
1861+
};
1862+
}
1863+
}
1864+
18351865
static final class ShowDeletedKeys extends RpcOptVal<@NonNull Boolean> implements HmacKeyListOpt {
18361866
private static final long serialVersionUID = -6604176744362903487L;
18371867

@@ -2426,6 +2456,10 @@ Mapper<BidiWriteObjectRequest.Builder> bidiWriteObjectRequest() {
24262456
return fuseMappers(ObjectTargetOpt.class, ObjectTargetOpt::bidiWriteObject);
24272457
}
24282458

2459+
Mapper<StartResumableWriteRequest.Builder> startResumableWriteRequest() {
2460+
return fuseMappers(ObjectTargetOpt.class, ObjectTargetOpt::startResumableWrite);
2461+
}
2462+
24292463
Mapper<GetObjectRequest.Builder> getObjectsRequest() {
24302464
return fuseMappers(ObjectSourceOpt.class, ObjectSourceOpt::getObject);
24312465
}

google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java

+4
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,10 @@ public String open(StorageObject object, Map<Option, ?> options) {
10901090
requestFactory.buildPostRequest(url, new JsonHttpContent(jsonFactory, object));
10911091
HttpHeaders requestHeaders = httpRequest.getHeaders();
10921092
requestHeaders.set("X-Upload-Content-Type", detectContentType(object, options));
1093+
Long xUploadContentLength = Option.X_UPLOAD_CONTENT_LENGTH.getLong(options);
1094+
if (xUploadContentLength != null) {
1095+
requestHeaders.set("X-Upload-Content-Length", xUploadContentLength);
1096+
}
10931097
setEncryptionHeaders(requestHeaders, "x-goog-encryption-", options);
10941098
HttpResponse response = httpRequest.execute();
10951099
if (response.getStatusCode() != 200) {

google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ enum Option {
7777
SOFT_DELETED("softDeleted"),
7878
COPY_SOURCE_ACL("copySourceAcl"),
7979
GENERATION("generation"),
80-
INCLUDE_FOLDERS_AS_PREFIXES("includeFoldersAsPrefixes");
80+
INCLUDE_FOLDERS_AS_PREFIXES("includeFoldersAsPrefixes"),
81+
X_UPLOAD_CONTENT_LENGTH("x-upload-content-length");
8182

8283
private final String value;
8384

google-cloud-storage/src/test/java/com/google/cloud/storage/GapicUploadSessionBuilderSyntaxTest.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.api.core.ApiFutures;
2424
import com.google.api.gax.rpc.ClientStreamingCallable;
2525
import com.google.api.gax.rpc.UnaryCallable;
26+
import com.google.cloud.storage.UnifiedOpts.Opts;
2627
import com.google.storage.v2.StartResumableWriteRequest;
2728
import com.google.storage.v2.StartResumableWriteResponse;
2829
import com.google.storage.v2.WriteObjectRequest;
@@ -94,7 +95,7 @@ public void syntax_directBuffered_fluent() {
9495
@Test
9596
public void syntax_resumableUnbuffered_fluent() {
9697
ApiFuture<ResumableWrite> startAsync =
97-
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req);
98+
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty());
9899
UnbufferedWritableByteChannelSession<WriteObjectResponse> session =
99100
ResumableMedia.gapic()
100101
.write()
@@ -110,7 +111,7 @@ public void syntax_resumableUnbuffered_fluent() {
110111
@Test
111112
public void syntax_resumableBuffered_fluent() {
112113
ApiFuture<ResumableWrite> startAsync =
113-
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req);
114+
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty());
114115
BufferedWritableByteChannelSession<WriteObjectResponse> session =
115116
ResumableMedia.gapic()
116117
.write()
@@ -150,7 +151,7 @@ public void syntax_directBuffered_incremental() {
150151
@Test
151152
public void syntax_resumableUnbuffered_incremental() {
152153
ApiFuture<ResumableWrite> startAsync =
153-
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req);
154+
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty());
154155
GapicWritableByteChannelSessionBuilder b1 =
155156
ResumableMedia.gapic()
156157
.write()
@@ -164,7 +165,7 @@ public void syntax_resumableUnbuffered_incremental() {
164165
@Test
165166
public void syntax_resumableBuffered_incremental() {
166167
ApiFuture<ResumableWrite> startAsync =
167-
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req);
168+
ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty());
168169
GapicWritableByteChannelSessionBuilder b1 =
169170
ResumableMedia.gapic()
170171
.write()

google-cloud-storage/src/test/java/com/google/cloud/storage/ITSyncAndUploadUnbufferedWritableByteChannelPropertyTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,9 @@ void testUploads(@ForAll("scenario") Scenario s) throws Exception {
310310

311311
ApiFuture<ResumableWrite> f =
312312
storage.startResumableWrite(
313-
GrpcCallContext.createDefault(), storage.getWriteObjectRequest(info, Opts.empty()));
313+
GrpcCallContext.createDefault(),
314+
storage.getWriteObjectRequest(info, Opts.empty()),
315+
Opts.empty());
314316
ResumableWrite resumableWrite = ApiExceptions.callAndTranslateApiException(f);
315317

316318
UploadCtx uploadCtx =

0 commit comments

Comments
 (0)