diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java index e70ee390df1..2fe19e8fc88 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java @@ -301,8 +301,10 @@ public CommitResponse writeAtLeastOnceWithOptions( ISpan span = tracer.spanBuilder(SpannerImpl.COMMIT); try (IScope s = tracer.withSpan(span)) { - return SpannerRetryHelper.runTxWithRetriesOnAborted( - () -> new CommitResponse(spanner.getRpc().commit(request, getOptions()))); + return spanner + .getTransactionRetryHelper() + .runTxWithRetriesOnAborted( + () -> new CommitResponse(spanner.getRpc().commit(request, getOptions()))); } catch (RuntimeException e) { span.setStatus(e); throw e; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java index c201924dfbe..a68322db17b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java @@ -116,6 +116,7 @@ private static String nextDatabaseClientId(DatabaseId databaseId) { private final DatabaseAdminClient dbAdminClient; private final InstanceAdminClient instanceClient; + private final TransactionRetryHelper transactionRetryHelper; /** * Exception class used to track the stack trace at the point when a Spanner instance is closed. @@ -145,6 +146,8 @@ static final class ClosedException extends RuntimeException { this.dbAdminClient = new DatabaseAdminClientImpl(options.getProjectId(), gapicRpc); this.instanceClient = new InstanceAdminClientImpl(options.getProjectId(), gapicRpc, dbAdminClient); + this.transactionRetryHelper = + new TransactionRetryHelper(options.getDefaultTransactionRetrySettings()); logSpannerOptions(options); } @@ -200,6 +203,10 @@ SpannerRpc getRpc() { return gapicRpc; } + TransactionRetryHelper getTransactionRetryHelper() { + return transactionRetryHelper; + } + /** Returns the default setting for prefetchChunks of this {@link SpannerImpl} instance. */ int getDefaultPrefetchChunks() { return getOptions().getPrefetchChunks(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 9eea86ab599..cd614d21fc4 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -58,6 +58,7 @@ import com.google.cloud.spanner.spi.v1.GapicSpannerRpc; import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.cloud.spanner.v1.SpannerSettings; +import com.google.cloud.spanner.v1.stub.SpannerStub; import com.google.cloud.spanner.v1.stub.SpannerStubSettings; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; @@ -70,6 +71,7 @@ import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions; import com.google.spanner.v1.RequestOptions; +import com.google.spanner.v1.RollbackRequest; import com.google.spanner.v1.SpannerGrpc; import com.google.spanner.v1.TransactionOptions; import com.google.spanner.v1.TransactionOptions.IsolationLevel; @@ -204,6 +206,25 @@ public static GcpChannelPoolOptions createDefaultDynamicChannelPoolOptions() { .build(); } + /** + * Use the same {@link RetrySettings} for retrying an aborted transaction as for retrying a {@link + * RollbackRequest}. The {@link RollbackRequest} automatically uses the default retry settings + * defined for the {@link SpannerStub}. By referencing these settings, the retry settings for + * retrying aborted transactions will also automatically be updated if the default retry settings + * are updated. + * + *
A read/write transaction should not time out while retrying. The total timeout of the retry + * settings is therefore set to 24 hours and there is no max attempts value. + * + *
These default {@link RetrySettings} are only used if no retry information is returned by the + * {@link AbortedException}. + */ + public static final RetrySettings DEFAULT_TRANSACTION_RETRY_SETTINGS = + SpannerStubSettings.newBuilder().rollbackSettings().getRetrySettings().toBuilder() + .setTotalTimeoutDuration(Duration.ofHours(24L)) + .setMaxAttempts(0) + .build(); + private final TransportChannelProvider channelProvider; private final ChannelEndpointCacheFactory channelEndpointCacheFactory; @@ -264,6 +285,7 @@ public static GcpChannelPoolOptions createDefaultDynamicChannelPoolOptions() { private final boolean enableEndToEndTracing; private final String monitoringHost; private final TransactionOptions defaultTransactionOptions; + private final RetrySettings defaultTransactionRetrySettings; private final RequestOptions.ClientContext clientContext; enum TracingFramework { @@ -934,6 +956,7 @@ protected SpannerOptions(Builder builder) { enableEndToEndTracing = builder.enableEndToEndTracing; monitoringHost = builder.monitoringHost; defaultTransactionOptions = builder.defaultTransactionOptions; + defaultTransactionRetrySettings = builder.defaultTransactionRetrySettings; clientContext = builder.clientContext; } @@ -1196,6 +1219,8 @@ public static class Builder private String experimentalHost = null; private boolean usePlainText = false; private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance(); + private RetrySettings defaultTransactionRetrySettings = + TransactionRetryHelper.DEFAULT_TRANSACTION_RETRY_SETTINGS; private RequestOptions.ClientContext clientContext; private static String createCustomClientLibToken(String token) { @@ -1302,6 +1327,7 @@ protected Builder() { this.enableEndToEndTracing = options.enableEndToEndTracing; this.monitoringHost = options.monitoringHost; this.defaultTransactionOptions = options.defaultTransactionOptions; + this.defaultTransactionRetrySettings = options.defaultTransactionRetrySettings; this.clientContext = options.clientContext; } @@ -2055,6 +2081,18 @@ public Builder setDefaultTransactionOptions( return this; } + /** + * Sets the default {@link RetrySettings} for all read/write transactions that are executed + * using this client. These settings are used when the client automatically retries an aborted + * read/write transaction. The default is to retry for up to 24 hours without a limit for the + * maximum number of attempts. + */ + public Builder setDefaultTransactionRetrySettings(RetrySettings retrySettings) { + Preconditions.checkNotNull(retrySettings, "RetrySettings cannot be null"); + this.defaultTransactionRetrySettings = retrySettings; + return this; + } + /** Sets the default {@link RequestOptions.ClientContext} for all requests. */ public Builder setDefaultClientContext(RequestOptions.ClientContext clientContext) { this.clientContext = clientContext; @@ -2481,6 +2519,10 @@ public TransactionOptions getDefaultTransactionOptions() { return defaultTransactionOptions; } + public RetrySettings getDefaultTransactionRetrySettings() { + return this.defaultTransactionRetrySettings; + } + @BetaApi public boolean isUseVirtualThreads() { return useVirtualThreads; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerRetryHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRetryHelper.java similarity index 69% rename from google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerRetryHelper.java rename to google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRetryHelper.java index 0dabcbd0094..e0ca79c09a0 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerRetryHelper.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRetryHelper.java @@ -24,11 +24,8 @@ import com.google.cloud.RetryHelper; import com.google.cloud.RetryHelper.RetryHelperException; import com.google.cloud.spanner.ErrorHandler.DefaultErrorHandler; -import com.google.cloud.spanner.v1.stub.SpannerStub; -import com.google.cloud.spanner.v1.stub.SpannerStubSettings; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; -import com.google.spanner.v1.RollbackRequest; import io.grpc.Context; import java.time.Duration; import java.util.concurrent.Callable; @@ -39,36 +36,21 @@ * that uses specific settings to only retry on aborted transactions, without a timeout and without * a cap on the number of retries. */ -class SpannerRetryHelper { +class TransactionRetryHelper { + private final RetrySettings retrySettings; - /** - * Use the same {@link RetrySettings} for retrying an aborted transaction as for retrying a {@link - * RollbackRequest}. The {@link RollbackRequest} automatically uses the default retry settings - * defined for the {@link SpannerStub}. By referencing these settings, the retry settings for - * retrying aborted transactions will also automatically be updated if the default retry settings - * are updated. - * - *
A read/write transaction should not timeout while retrying. The total timeout of the retry - * settings is therefore set to 24 hours and there is no max attempts value. - * - *
These default {@link RetrySettings} are only used if no retry information is returned by the
- * {@link AbortedException}.
- */
- @VisibleForTesting
- static final RetrySettings txRetrySettings =
- SpannerStubSettings.newBuilder().rollbackSettings().getRetrySettings().toBuilder()
- .setTotalTimeoutDuration(Duration.ofHours(24L))
- .setMaxAttempts(0)
- .build();
+ TransactionRetryHelper(RetrySettings retrySettings) {
+ this.retrySettings = retrySettings;
+ }
/** Executes the {@link Callable} and retries if it fails with an {@link AbortedException}. */
- static