Skip to content

Commit 27bccda

Browse files
authored
fix: connection closed prematurely in BlobReadChannel & ConnectionReset (#173)
* fix: connection closed prematurely * add connection reset retry * remove integration test * format * clean up tests * change design of transient retry strategy * add documentation * format docs * address feedback * address feedback
1 parent ef5f2c6 commit 27bccda

File tree

4 files changed

+55
-4
lines changed

4 files changed

+55
-4
lines changed

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

+29-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
*/
3434
@InternalApi
3535
public final class StorageException extends BaseHttpServiceException {
36+
private static final String INTERNAL_ERROR = "internalError";
37+
private static final String CONNECTION_CLOSED_PREMATURELY = "connectionClosedPrematurely";
38+
private static final String CONNECTION_RESET = "connectionReset";
3639

3740
// see: https://cloud.google.com/storage/docs/resumable-uploads-xml#practices
3841
private static final Set<Error> RETRYABLE_ERRORS =
@@ -43,7 +46,9 @@ public final class StorageException extends BaseHttpServiceException {
4346
new Error(500, null),
4447
new Error(429, null),
4548
new Error(408, null),
46-
new Error(null, "internalError"));
49+
new Error(null, INTERNAL_ERROR),
50+
new Error(null, CONNECTION_CLOSED_PREMATURELY),
51+
new Error(null, CONNECTION_RESET));
4752

4853
private static final long serialVersionUID = -4168430271327813063L;
4954

@@ -55,6 +60,10 @@ public StorageException(int code, String message, Throwable cause) {
5560
super(code, message, null, true, RETRYABLE_ERRORS, cause);
5661
}
5762

63+
public StorageException(int code, String message, String reason, Throwable cause) {
64+
super(code, message, reason, true, RETRYABLE_ERRORS, cause);
65+
}
66+
5867
public StorageException(IOException exception) {
5968
super(exception, true, RETRYABLE_ERRORS);
6069
}
@@ -73,4 +82,23 @@ public static StorageException translateAndThrow(RetryHelperException ex) {
7382
BaseServiceException.translate(ex);
7483
throw new StorageException(UNKNOWN_CODE, ex.getMessage(), ex.getCause());
7584
}
85+
86+
/**
87+
* Translate IOException to a StorageException representing the cause of the error. This method
88+
* defaults to idempotent always being {@code true}. Additionally, this method translates
89+
* transient issues Connection Closed Prematurely and Connection Reset as retryable errors.
90+
*
91+
* @returns {@code StorageException}
92+
*/
93+
public static StorageException translate(IOException exception) {
94+
if (exception.getMessage().contains("Connection closed prematurely")) {
95+
return new StorageException(
96+
0, exception.getMessage(), CONNECTION_CLOSED_PREMATURELY, exception);
97+
} else if (exception.getMessage().contains("Connection reset")) {
98+
return new StorageException(0, exception.getMessage(), CONNECTION_RESET, exception);
99+
} else {
100+
// default
101+
return new StorageException(exception);
102+
}
103+
}
76104
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ public Tuple<String, byte[]> read(
701701
return Tuple.of(etag, output.toByteArray());
702702
} catch (IOException ex) {
703703
span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage()));
704-
StorageException serviceException = translate(ex);
704+
StorageException serviceException = StorageException.translate(ex);
705705
if (serviceException.getCode() == SC_REQUESTED_RANGE_NOT_SATISFIABLE) {
706706
return Tuple.of(null, new byte[0]);
707707
}

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

-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
import com.google.cloud.ReadChannel;
3030
import com.google.cloud.RestorableState;
31-
import com.google.cloud.ServiceOptions;
3231
import com.google.cloud.Tuple;
3332
import com.google.cloud.storage.spi.StorageRpcFactory;
3433
import com.google.cloud.storage.spi.v1.StorageRpc;
@@ -68,7 +67,6 @@ public void setUp() {
6867
StorageOptions.newBuilder()
6968
.setProjectId("projectId")
7069
.setServiceRpcFactory(rpcFactoryMock)
71-
.setRetrySettings(ServiceOptions.getNoRetrySettings())
7270
.build();
7371
}
7472

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

+25
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import com.google.cloud.BaseServiceException;
3333
import com.google.cloud.RetryHelper.RetryHelperException;
3434
import java.io.IOException;
35+
import java.net.SocketException;
3536
import java.net.SocketTimeoutException;
37+
import javax.net.ssl.SSLException;
3638
import org.junit.Test;
3739

3840
public class StorageExceptionTest {
@@ -135,6 +137,29 @@ public void testStorageException() {
135137
assertTrue(exception.isRetryable());
136138
}
137139

140+
@Test
141+
public void testTranslateConnectionReset() {
142+
StorageException exception =
143+
StorageException.translate(
144+
new IOException(
145+
"Connection has been shutdown: "
146+
+ new SSLException(new SocketException("Connection reset"))));
147+
assertEquals(0, exception.getCode());
148+
assertEquals("connectionReset", exception.getReason());
149+
assertTrue(exception.isRetryable());
150+
}
151+
152+
@Test
153+
public void testTranslateConnectionClosedPrematurely() {
154+
StorageException exception =
155+
StorageException.translate(
156+
new IOException(
157+
"Connection closed prematurely: bytesRead = 1114112, Content-Length = 10485760"));
158+
assertEquals(0, exception.getCode());
159+
assertEquals("connectionClosedPrematurely", exception.getReason());
160+
assertTrue(exception.isRetryable());
161+
}
162+
138163
@Test
139164
public void testTranslateAndThrow() throws Exception {
140165
Exception cause = new StorageException(503, "message");

0 commit comments

Comments
 (0)