Skip to content

Storage Retry Logic #1545

@Dima1224

Description

@Dima1224

Hi there. I'm using com.google.cloud.storage.Storage.writer() method of v0.8.0 of the google-cloud lib. I'm getting "503 backend error" from the GCS service when trying to upload files (stack trace below). I expect the retry logic in com.google.cloud.storage.BlobWriteChannel to retry the chunk, but it doesn't do that.

  @Override
  protected void flushBuffer(final int length, final boolean last) {
    try {
      runWithRetries(callable(new Runnable() {
        @Override
        public void run() {
          getOptions().getRpc().write(getUploadId(), getBuffer(), 0, getPosition(), length, last);
        }
      }), getOptions().getRetryParams(), StorageImpl.EXCEPTION_HANDLER, getOptions().getClock());
    } catch (RetryHelper.RetryHelperException e) {
      throw StorageException.translateAndThrow(e);
    }
  }

The problem seems to lie in the logic for converting the com.google.api.client.http.HttpResponseException to a com.google.cloud.storage.StorageException

public BaseServiceException(IOException exception, boolean idempotent) {
    super(message(exception), exception);
    int code = UNKNOWN_CODE;
    String reason = null;
    String location = null;
    String debugInfo = null;
    Boolean retryable = null;
    if (exception instanceof GoogleJsonResponseException) {
      GoogleJsonError jsonError = ((GoogleJsonResponseException) exception).getDetails();
      if (jsonError != null) {
        Error error = new Error(jsonError.getCode(), reason(jsonError));
        code = error.code;
        reason = error.reason;
        retryable = isRetryable(idempotent, error);
        if (reason != null) {
          GoogleJsonError.ErrorInfo errorInfo = jsonError.getErrors().get(0);
          location = errorInfo.getLocation();
          debugInfo = (String) errorInfo.get("debugInfo");
        }
      } else {
        code = ((GoogleJsonResponseException) exception).getStatusCode();
      }
    }
    this.retryable = MoreObjects.firstNonNull(retryable, isRetryable(idempotent, exception));
    this.code = code;
    this.reason = reason;
    this.idempotent = idempotent;
    this.location = location;
    this.debugInfo = debugInfo;
  }

Since the HttpResponseException is not an instanceof GoogleJsonResponseException, we end up with a code of 0, which the retry logic sees as a non-retryable exception. Here's a code snippet to easily reproduce the issue:

HttpResponseException httpEx = new HttpResponseException.Builder(503, "Service Unavailable", new HttpHeaders()).build();
StorageException storageEx = new StorageException(httpEx);
System.out.println(storageEx.getCode());
System.out.println(storageEx.getMessage());

Furthermore, the stacktrace I end up with doesn't indicate that the code and message of the ServiceException weren't set (I think the toString() method can be improved to show this info).

Exception in thread "main" com.google.cloud.storage.StorageException: 503 Service Unavailable
{
"error": {
"errors": [
{
"domain": "global",
"reason": "backendError",
"message": "Backend Error"
}
],
"code": 503,
"message": "Backend Error"
}
}

at com.google.cloud.storage.spi.DefaultStorageRpc.translate(DefaultStorageRpc.java:202)
at com.google.cloud.storage.spi.DefaultStorageRpc.write(DefaultStorageRpc.java:582)
at com.google.cloud.storage.BlobWriteChannel$1.run(BlobWriteChannel.java:50)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at com.google.cloud.RetryHelper.doRetry(RetryHelper.java:179)
at com.google.cloud.RetryHelper.runWithRetries(RetryHelper.java:244)
at com.google.cloud.storage.BlobWriteChannel.flushBuffer(BlobWriteChannel.java:47)
at com.google.cloud.BaseWriteChannel.close(BaseWriteChannel.java:199)
at com.spins.google.api.CloudStorage.upload(CloudStorage.java:338)
at com.spins.google.api.CloudStorage.upload(CloudStorage.java:325)
at com.spins.google.api.CloudStorage.upload(CloudStorage.java:315)
at Random.main(Random.java:69)

Caused by: com.google.api.client.http.HttpResponseException: 503 Service Unavailable
{
"error": {
"errors": [
{
"domain": "global",
"reason": "backendError",
"message": "Backend Error"
}
],
"code": 503,
"message": "Backend Error"
}
}

at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1061)
at com.google.cloud.storage.spi.DefaultStorageRpc.write(DefaultStorageRpc.java:564)
... 10 more

Metadata

Metadata

Labels

api: storageIssues related to the Cloud Storage API.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions