Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.api.gax.paging.Page;
import com.google.api.gax.retrying.ResultRetryAlgorithm;
import com.google.api.services.bigquery.model.ErrorProto;
import com.google.api.services.bigquery.model.GetQueryResultsResponse;
import com.google.api.services.bigquery.model.QueryRequest;
Expand Down Expand Up @@ -396,7 +397,7 @@ public com.google.api.services.bigquery.model.Routine call() throws IOException
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -592,7 +593,7 @@ public com.google.api.services.bigquery.model.Dataset call() throws IOException
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -658,7 +659,7 @@ private static Page<Dataset> listDatasets(
}
},
serviceOptions.getRetrySettings(),
serviceOptions.getResultRetryAlgorithm(),
BigQueryRetryHelper.maybeWrapForHttpRetry(serviceOptions.getResultRetryAlgorithm()),
serviceOptions.getClock(),
EMPTY_RETRY_CONFIG,
serviceOptions.isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1131,7 +1132,7 @@ public com.google.api.services.bigquery.model.Table call() throws IOException {
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1190,7 +1191,7 @@ public com.google.api.services.bigquery.model.Model call() throws IOException {
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1249,7 +1250,7 @@ public com.google.api.services.bigquery.model.Routine call() throws IOException
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1467,7 +1468,7 @@ public Tuple<String, Iterable<com.google.api.services.bigquery.model.Table>> cal
}
},
serviceOptions.getRetrySettings(),
serviceOptions.getResultRetryAlgorithm(),
BigQueryRetryHelper.maybeWrapForHttpRetry(serviceOptions.getResultRetryAlgorithm()),
serviceOptions.getClock(),
EMPTY_RETRY_CONFIG,
serviceOptions.isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1508,7 +1509,7 @@ public Tuple<String, Iterable<com.google.api.services.bigquery.model.Model>> cal
}
},
serviceOptions.getRetrySettings(),
serviceOptions.getResultRetryAlgorithm(),
BigQueryRetryHelper.maybeWrapForHttpRetry(serviceOptions.getResultRetryAlgorithm()),
serviceOptions.getClock(),
EMPTY_RETRY_CONFIG,
serviceOptions.isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1549,7 +1550,7 @@ private static Page<Routine> listRoutines(
}
},
serviceOptions.getRetrySettings(),
serviceOptions.getResultRetryAlgorithm(),
BigQueryRetryHelper.maybeWrapForHttpRetry(serviceOptions.getResultRetryAlgorithm()),
serviceOptions.getClock(),
EMPTY_RETRY_CONFIG,
serviceOptions.isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1725,7 +1726,7 @@ public TableDataList call() throws IOException {
}
},
serviceOptions.getRetrySettings(),
serviceOptions.getResultRetryAlgorithm(),
BigQueryRetryHelper.maybeWrapForHttpRetry(serviceOptions.getResultRetryAlgorithm()),
serviceOptions.getClock(),
EMPTY_RETRY_CONFIG,
serviceOptions.isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1802,7 +1803,7 @@ public com.google.api.services.bigquery.model.Job call() throws IOException {
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1859,7 +1860,7 @@ public Tuple<String, Iterable<com.google.api.services.bigquery.model.Job>> call(
}
},
serviceOptions.getRetrySettings(),
serviceOptions.getResultRetryAlgorithm(),
BigQueryRetryHelper.maybeWrapForHttpRetry(serviceOptions.getResultRetryAlgorithm()),
serviceOptions.getClock(),
EMPTY_RETRY_CONFIG,
serviceOptions.isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -1914,7 +1915,7 @@ public Boolean call() throws IOException {
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -2169,7 +2170,7 @@ public GetQueryResultsResponse call() throws IOException {
}
},
serviceOptions.getRetrySettings(),
serviceOptions.getResultRetryAlgorithm(),
BigQueryRetryHelper.maybeWrapForHttpRetry(serviceOptions.getResultRetryAlgorithm()),
serviceOptions.getClock(),
DEFAULT_RETRY_CONFIG,
serviceOptions.isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -2240,7 +2241,7 @@ public com.google.api.services.bigquery.model.Policy call() throws IOException {
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -2334,7 +2335,7 @@ public com.google.api.services.bigquery.model.TestIamPermissionsResponse call()
}
},
getOptions().getRetrySettings(),
getOptions().getResultRetryAlgorithm(),
getRetryAlgorithmWithHttpRetry(),
getOptions().getClock(),
EMPTY_RETRY_CONFIG,
getOptions().isOpenTelemetryTracingEnabled(),
Expand Down Expand Up @@ -2411,4 +2412,16 @@ private static boolean isRetryErrorCodeHttpNotFound(BigQueryRetryHelperException
}
return false;
}

/**
* Helper to retrieve the retry algorithm wrapped for HTTP error retries.
*
* <p>This delegates to {@link BigQueryRetryHelper#maybeWrapForHttpRetry} to ensure safe
* conditional wrapping of the default algorithm while leaving custom user algorithms untouched.
*/
@SuppressWarnings("unchecked")
private <V> ResultRetryAlgorithm<V> getRetryAlgorithmWithHttpRetry() {
return BigQueryRetryHelper.maybeWrapForHttpRetry(
(ResultRetryAlgorithm<V>) getOptions().getResultRetryAlgorithm());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.cloud.bigquery;

import com.google.api.client.http.HttpResponseException;
import com.google.api.core.ApiClock;
import com.google.api.gax.retrying.DirectRetryingExecutor;
import com.google.api.gax.retrying.ExponentialRetryAlgorithm;
Expand All @@ -23,6 +24,7 @@
import com.google.api.gax.retrying.RetrySettings;
import com.google.api.gax.retrying.RetryingExecutor;
import com.google.api.gax.retrying.RetryingFuture;
import com.google.api.gax.retrying.TimedAttemptSettings;
import com.google.api.gax.retrying.TimedRetryAlgorithm;
import com.google.cloud.RetryHelper;
import io.opentelemetry.api.trace.Span;
Expand Down Expand Up @@ -119,6 +121,49 @@ private static <V> V run(
return retryingFuture.get();
}

/**
* Conditionally wraps the provided retry algorithm with a wrapper that retries on transient HTTP
* errors.
*
* <p>Wrapping only occurs if the provided algorithm is the default {@link
* BigQueryBaseService#DEFAULT_BIGQUERY_EXCEPTION_HANDLER}. Custom user-defined retry algorithms
* are returned unmodified to preserve custom retry policies.
*/
static <V> ResultRetryAlgorithm<V> maybeWrapForHttpRetry(ResultRetryAlgorithm<V> algorithm) {
if (algorithm == BigQueryBaseService.DEFAULT_BIGQUERY_EXCEPTION_HANDLER) {
return wrapDefaultAlgorithm(algorithm);
}
return algorithm;
}

/**
* Wraps the default retry algorithm to additionally retry on transient HTTP status codes 500,
* 502, 503, and 504. Other retry decisions and timing logic are delegated back to the default
* algorithm.
*/
private static <V> ResultRetryAlgorithm<V> wrapDefaultAlgorithm(
ResultRetryAlgorithm<V> defaultAlgorithm) {
return new ResultRetryAlgorithm<V>() {
@Override
public TimedAttemptSettings createNextAttempt(
Throwable previousThrowable, V previousResponse, TimedAttemptSettings previousSettings) {
return defaultAlgorithm.createNextAttempt(
previousThrowable, previousResponse, previousSettings);
}
Comment thread
jinseopkim0 marked this conversation as resolved.

@Override
public boolean shouldRetry(Throwable previousThrowable, V previousResponse) {
if (previousThrowable instanceof HttpResponseException) {
int statusCode = ((HttpResponseException) previousThrowable).getStatusCode();
if (statusCode == 500 || statusCode == 502 || statusCode == 503 || statusCode == 504) {
return true;
}
}
return defaultAlgorithm.shouldRetry(previousThrowable, previousResponse);
}
};
}

public static class BigQueryRetryHelperException extends RuntimeException {

private static final long serialVersionUID = -8519852520090965314L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpResponseException;
import com.google.api.gax.paging.Page;
import com.google.api.services.bigquery.model.ErrorProto;
import com.google.api.services.bigquery.model.GetQueryResultsResponse;
Expand Down Expand Up @@ -935,6 +939,37 @@ void testGetTable() throws IOException {
.getTableSkipExceptionTranslation(PROJECT, DATASET, TABLE, EMPTY_RPC_OPTIONS);
}

@Test
void testGetTableFailureShouldRetryServerErrors() throws IOException {
GoogleJsonError error = new GoogleJsonError();
error.setMessage("Visibility check was unavailable. Please retry the request");
error.setCode(503);
GoogleJsonError.ErrorInfo errorInfo = new GoogleJsonError.ErrorInfo();
errorInfo.setReason("backendError");
error.setErrors(ImmutableList.of(errorInfo));

when(bigqueryRpcMock.getTableSkipExceptionTranslation(
PROJECT, DATASET, TABLE, EMPTY_RPC_OPTIONS))
.thenThrow(new GoogleJsonResponseException(serverErrorResponse(), error))
.thenReturn(TABLE_INFO_WITH_PROJECT.toPb());

bigquery =
options.toBuilder()
.setRetrySettings(ServiceOptions.getDefaultRetrySettings())
.build()
.getService();

Table table = bigquery.getTable(DATASET, TABLE);

assertEquals(new Table(bigquery, new TableInfo.BuilderImpl(TABLE_INFO_WITH_PROJECT)), table);
verify(bigqueryRpcMock, times(2))
.getTableSkipExceptionTranslation(PROJECT, DATASET, TABLE, EMPTY_RPC_OPTIONS);
}

private static HttpResponseException.Builder serverErrorResponse() {
return new HttpResponseException.Builder(503, "Service Unavailable", new HttpHeaders());
}

@Test
void testGetModel() throws IOException {
when(bigqueryRpcMock.getModelSkipExceptionTranslation(
Expand Down
Loading