Skip to content

Commit 21821da

Browse files
authored
fix: update implementation of readAllBytes and downloadTo to be more robust to retryable errors (#2305)
Additional small changes to bring http and grpc implementation into conformance with each other. Much of this also serves as pre-work to the grpc retry conformance tests enablement after the next release of testbench.
1 parent 29f4ea6 commit 21821da

File tree

9 files changed

+112
-219
lines changed

9 files changed

+112
-219
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,10 @@ public final synchronized int write(ByteBuffer src) throws IOException {
101101
}
102102
int write = tmp.write(src);
103103
return write;
104-
} catch (StorageException e) {
105-
throw new IOException(e);
106104
} catch (IOException e) {
107105
throw e;
108106
} catch (Exception e) {
109-
throw new IOException(StorageException.coalesce(e));
107+
throw StorageException.coalesce(e);
110108
}
111109
}
112110

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

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,34 @@ public Blob create(
250250

251251
@Override
252252
public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) {
253-
try {
254-
return createFrom(blobInfo, content, options);
255-
} catch (IOException e) {
253+
requireNonNull(blobInfo, "blobInfo must be non null");
254+
255+
Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts);
256+
GrpcCallContext grpcCallContext =
257+
opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
258+
WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts);
259+
260+
UnbufferedWritableByteChannelSession<WriteObjectResponse> session =
261+
ResumableMedia.gapic()
262+
.write()
263+
.byteChannel(
264+
storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext))
265+
.setHasher(Hasher.enabled())
266+
.setByteStringStrategy(ByteStringStrategy.noCopy())
267+
.direct()
268+
.unbuffered()
269+
.setRequest(req)
270+
.build();
271+
272+
// Specifically not in the try-with, so we don't close the provided stream
273+
ReadableByteChannel src =
274+
Channels.newChannel(firstNonNull(content, new ByteArrayInputStream(ZERO_BYTES)));
275+
try (UnbufferedWritableByteChannel dst = session.open()) {
276+
ByteStreams.copy(src, dst);
277+
} catch (Exception e) {
256278
throw StorageException.coalesce(e);
257279
}
280+
return getBlob(session.getResult());
258281
}
259282

260283
@Override
@@ -309,7 +332,7 @@ public Blob internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOpt> o
309332
}
310333
return codecs.blobInfo().decode(object).asBlob(this);
311334
} catch (InterruptedException | ExecutionException e) {
312-
throw StorageException.coalesce(e);
335+
throw StorageException.coalesce(e.getCause());
313336
}
314337
}
315338

@@ -359,7 +382,14 @@ public Blob createFrom(
359382
@Override
360383
public Bucket get(String bucket, BucketGetOption... options) {
361384
Opts<BucketSourceOpt> unwrap = Opts.unwrap(options);
362-
return internalBucketGet(bucket, unwrap);
385+
try {
386+
return internalBucketGet(bucket, unwrap);
387+
} catch (StorageException e) {
388+
if (e.getCode() == 404) {
389+
return null;
390+
}
391+
throw e;
392+
}
363393
}
364394

365395
@Override

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

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import static com.google.common.base.Preconditions.checkArgument;
2222
import static com.google.common.base.Preconditions.checkState;
2323
import static java.nio.charset.StandardCharsets.UTF_8;
24-
import static java.util.concurrent.Executors.callable;
2524

2625
import com.google.api.core.ApiFuture;
2726
import com.google.api.gax.paging.Page;
@@ -37,12 +36,14 @@
3736
import com.google.cloud.Policy;
3837
import com.google.cloud.WriteChannel;
3938
import com.google.cloud.storage.Acl.Entity;
39+
import com.google.cloud.storage.ApiaryUnbufferedReadableByteChannel.ApiaryReadRequest;
4040
import com.google.cloud.storage.BlobReadChannelV2.BlobReadChannelContext;
4141
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
4242
import com.google.cloud.storage.PostPolicyV4.ConditionV4Type;
4343
import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
4444
import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
4545
import com.google.cloud.storage.PostPolicyV4.PostPolicyV4Document;
46+
import com.google.cloud.storage.UnbufferedReadableByteChannelSession.UnbufferedReadableByteChannel;
4647
import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt;
4748
import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
4849
import com.google.cloud.storage.UnifiedOpts.Opts;
@@ -59,9 +60,10 @@
5960
import com.google.common.collect.Maps;
6061
import com.google.common.hash.Hashing;
6162
import com.google.common.io.BaseEncoding;
62-
import com.google.common.io.CountingOutputStream;
63+
import com.google.common.io.ByteStreams;
6364
import com.google.common.primitives.Ints;
6465
import java.io.ByteArrayInputStream;
66+
import java.io.ByteArrayOutputStream;
6567
import java.io.IOException;
6668
import java.io.InputStream;
6769
import java.io.OutputStream;
@@ -73,6 +75,7 @@
7375
import java.nio.ByteBuffer;
7476
import java.nio.channels.Channels;
7577
import java.nio.channels.ReadableByteChannel;
78+
import java.nio.channels.WritableByteChannel;
7679
import java.nio.file.Files;
7780
import java.nio.file.Path;
7881
import java.text.SimpleDateFormat;
@@ -604,9 +607,25 @@ public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) {
604607
Opts<ObjectSourceOpt> unwrap = Opts.unwrap(options);
605608
Opts<ObjectSourceOpt> resolve = unwrap.resolveFrom(blob);
606609
ImmutableMap<StorageRpc.Option, ?> optionsMap = resolve.getRpcOptions();
607-
ResultRetryAlgorithm<?> algorithm =
608-
retryAlgorithmManager.getForObjectsGet(storageObject, optionsMap);
609-
return run(algorithm, () -> storageRpc.load(storageObject, optionsMap), Function.identity());
610+
boolean autoGzipDecompression =
611+
Utils.isAutoGzipDecompression(resolve, /*defaultWhenUndefined=*/ true);
612+
UnbufferedReadableByteChannelSession<StorageObject> session =
613+
ResumableMedia.http()
614+
.read()
615+
.byteChannel(BlobReadChannelContext.from(this))
616+
.setAutoGzipDecompression(autoGzipDecompression)
617+
.unbuffered()
618+
.setApiaryReadRequest(
619+
new ApiaryReadRequest(storageObject, optionsMap, ByteRangeSpec.nullRange()))
620+
.build();
621+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
622+
try (UnbufferedReadableByteChannel r = session.open();
623+
WritableByteChannel w = Channels.newChannel(baos)) {
624+
ByteStreams.copy(r, w);
625+
} catch (IOException e) {
626+
throw StorageException.translate(e);
627+
}
628+
return baos.toByteArray();
610629
}
611630

612631
@Override
@@ -638,19 +657,26 @@ public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) {
638657

639658
@Override
640659
public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) {
641-
final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream);
642660
final StorageObject pb = codecs.blobId().encode(blob);
643-
ImmutableMap<StorageRpc.Option, ?> optionsMap =
644-
Opts.unwrap(options).resolveFrom(blob).getRpcOptions();
645-
ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, optionsMap);
646-
run(
647-
algorithm,
648-
callable(
649-
() -> {
650-
storageRpc.read(
651-
pb, optionsMap, countingOutputStream.getCount(), countingOutputStream);
652-
}),
653-
Function.identity());
661+
Opts<ObjectSourceOpt> resolve = Opts.unwrap(options).resolveFrom(blob);
662+
ImmutableMap<StorageRpc.Option, ?> optionsMap = resolve.getRpcOptions();
663+
boolean autoGzipDecompression =
664+
Utils.isAutoGzipDecompression(resolve, /*defaultWhenUndefined=*/ true);
665+
UnbufferedReadableByteChannelSession<StorageObject> session =
666+
ResumableMedia.http()
667+
.read()
668+
.byteChannel(BlobReadChannelContext.from(this))
669+
.setAutoGzipDecompression(autoGzipDecompression)
670+
.unbuffered()
671+
.setApiaryReadRequest(new ApiaryReadRequest(pb, optionsMap, ByteRangeSpec.nullRange()))
672+
.build();
673+
// don't close the provided stream
674+
WritableByteChannel w = Channels.newChannel(outputStream);
675+
try (UnbufferedReadableByteChannel r = session.open()) {
676+
ByteStreams.copy(r, w);
677+
} catch (IOException e) {
678+
throw StorageException.translate(e);
679+
}
654680
}
655681

656682
@Override

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.cloud.storage.ByteSizeConstants._2MiB;
2020
import static com.google.common.truth.Truth.assertThat;
21+
import static org.junit.Assert.assertThrows;
2122

2223
import com.google.api.core.ApiFuture;
2324
import com.google.api.gax.grpc.GrpcCallContext;
@@ -81,16 +82,19 @@ public void create_bytes() throws Exception {
8182

8283
@Test
8384
public void create_inputStream() throws Exception {
84-
Resumable.FakeService service = Resumable.FakeService.create();
85+
Direct.FakeService service = Direct.FakeService.create();
8586
try (TmpFile tmpFile = DataGenerator.base64Characters().tempFile(baseDir, objectContentSize);
8687
FakeServer server = FakeServer.of(service);
8788
Storage s = server.getGrpcStorageOptions().getService();
8889
InputStream in = Channels.newInputStream(tmpFile.reader())) {
8990
BlobInfo info = BlobInfo.newBuilder("buck", "obj").build();
90-
s.create(info, in, BlobWriteOption.doesNotExist());
91+
// create uses a direct upload, once the stream is consumed there is no means for us to retry
92+
// if an error happens it should be surfaced
93+
StorageException se =
94+
assertThrows(
95+
StorageException.class, () -> s.create(info, in, BlobWriteOption.doesNotExist()));
96+
assertThat(se.getCode()).isEqualTo(500);
9197
}
92-
93-
assertThat(service.returnError.get()).isFalse();
9498
}
9599

96100
@Test

0 commit comments

Comments
 (0)