From 8eeefdcd0ccba1259d6ae95e112bc50259ae7bec Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Mon, 9 Nov 2020 18:49:59 +0100 Subject: [PATCH 01/23] feat: support retry settings and retryable codes in call context Allows applications to supply retry settings and retryabe codes for individual RPCs through ApiCallContext. Fixes #1197 --- .../google/api/gax/grpc/GrpcCallContext.java | 120 +++++- .../api/gax/grpc/GrpcCallContextTest.java | 23 +- .../api/gax/httpjson/HttpJsonCallContext.java | 94 ++++- .../gax/httpjson/HttpJsonCallContextTest.java | 22 ++ .../retrying/BasicResultRetryAlgorithm.java | 31 +- .../api/gax/retrying/BasicRetryingFuture.java | 40 +- .../ContextAwareBasicRetryingFuture.java | 62 +++ ...ntextAwareCallbackChainRetryingFuture.java | 78 ++++ .../ContextAwareDirectRetryingExecutor.java | 47 +++ .../ContextAwareResultRetryAlgorithm.java | 56 +++ .../retrying/ContextAwareRetryAlgorithm.java | 126 +++++++ ...ContextAwareScheduledRetryingExecutor.java | 63 ++++ .../ContextAwareTimedRetryAlgorithm.java | 77 ++++ .../retrying/ExponentialRetryAlgorithm.java | 76 +++- .../api/gax/retrying/NoopRetryingContext.java | 18 + .../api/gax/retrying/RetryingContext.java | 17 + .../google/api/gax/rpc/ApiCallContext.java | 36 ++ .../api/gax/rpc/ApiResultRetryAlgorithm.java | 12 + .../com/google/api/gax/rpc/Callables.java | 10 +- .../AbstractRetryingExecutorTest.java | 45 ++- .../ContextAwareBasicRetryingFutureTest.java | 90 +++++ ...ontextAwareDirectRetryingExecutorTest.java | 66 ++++ ...extAwareScheduledRetryingExecutorTest.java | 354 ++++++++++++++++++ .../retrying/DirectRetryingExecutorTest.java | 6 + .../ExponentialRetryAlgorithmTest.java | 47 +++ .../api/gax/retrying/FailingCallable.java | 12 + .../ScheduledRetryingExecutorTest.java | 5 + .../com/google/api/gax/rpc/RetryingTest.java | 144 +++++++ .../google/api/gax/rpc/UnaryCallableTest.java | 16 +- .../api/gax/rpc/testing/FakeCallContext.java | 90 ++++- 30 files changed, 1812 insertions(+), 71 deletions(-) create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java create mode 100644 gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java create mode 100644 gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java create mode 100644 gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index 786c782db..7a6491b40 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -31,7 +31,9 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; +import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.internal.Headers; import com.google.api.gax.tracing.ApiTracer; @@ -39,6 +41,7 @@ import com.google.auth.Credentials; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import io.grpc.CallCredentials; import io.grpc.CallOptions; import io.grpc.CallOptions.Key; @@ -49,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.threeten.bp.Duration; @@ -72,18 +76,36 @@ public final class GrpcCallContext implements ApiCallContext { @Nullable private final Duration streamWaitTimeout; @Nullable private final Duration streamIdleTimeout; @Nullable private final Integer channelAffinity; + @Nullable private final RetrySettings retrySettings; + @Nullable private final ImmutableSet retryableCodes; private final ImmutableMap> extraHeaders; /** Returns an empty instance with a null channel and default {@link CallOptions}. */ public static GrpcCallContext createDefault() { return new GrpcCallContext( - null, CallOptions.DEFAULT, null, null, null, null, ImmutableMap.>of()); + null, + CallOptions.DEFAULT, + null, + null, + null, + null, + ImmutableMap.>of(), + null, + null); } /** Returns an instance with the given channel and {@link CallOptions}. */ public static GrpcCallContext of(Channel channel, CallOptions callOptions) { return new GrpcCallContext( - channel, callOptions, null, null, null, null, ImmutableMap.>of()); + channel, + callOptions, + null, + null, + null, + null, + ImmutableMap.>of(), + null, + null); } private GrpcCallContext( @@ -93,7 +115,9 @@ private GrpcCallContext( @Nullable Duration streamWaitTimeout, @Nullable Duration streamIdleTimeout, @Nullable Integer channelAffinity, - ImmutableMap> extraHeaders) { + ImmutableMap> extraHeaders, + @Nullable RetrySettings retrySettings, + @Nullable Set retryableCodes) { this.channel = channel; this.callOptions = Preconditions.checkNotNull(callOptions); this.timeout = timeout; @@ -101,6 +125,8 @@ private GrpcCallContext( this.streamIdleTimeout = streamIdleTimeout; this.channelAffinity = channelAffinity; this.extraHeaders = Preconditions.checkNotNull(extraHeaders); + this.retrySettings = retrySettings; + this.retryableCodes = retryableCodes == null ? null : ImmutableSet.copyOf(retryableCodes); } /** @@ -162,7 +188,9 @@ public GrpcCallContext withTimeout(@Nullable Duration timeout) { this.streamWaitTimeout, this.streamIdleTimeout, this.channelAffinity, - this.extraHeaders); + this.extraHeaders, + retrySettings, + this.retryableCodes); } @Nullable @@ -185,7 +213,9 @@ public GrpcCallContext withStreamWaitTimeout(@Nullable Duration streamWaitTimeou streamWaitTimeout, streamIdleTimeout, channelAffinity, - extraHeaders); + extraHeaders, + retrySettings, + retryableCodes); } @Override @@ -202,7 +232,9 @@ public GrpcCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeou streamWaitTimeout, streamIdleTimeout, channelAffinity, - extraHeaders); + extraHeaders, + retrySettings, + retryableCodes); } @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") @@ -214,7 +246,9 @@ public GrpcCallContext withChannelAffinity(@Nullable Integer affinity) { streamWaitTimeout, streamIdleTimeout, affinity, - extraHeaders); + extraHeaders, + retrySettings, + retryableCodes); } @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @@ -230,7 +264,47 @@ public GrpcCallContext withExtraHeaders(Map> extraHeaders) streamWaitTimeout, streamIdleTimeout, channelAffinity, - newExtraHeaders); + newExtraHeaders, + retrySettings, + retryableCodes); + } + + @Override + public RetrySettings getRetrySettings() { + return this.retrySettings; + } + + @Override + public GrpcCallContext withRetrySettings(RetrySettings retrySettings) { + return new GrpcCallContext( + this.channel, + this.callOptions, + this.timeout, + this.streamWaitTimeout, + this.streamIdleTimeout, + this.channelAffinity, + this.extraHeaders, + retrySettings, + retryableCodes); + } + + @Override + public Set getRetryableCodes() { + return this.retryableCodes; + } + + @Override + public GrpcCallContext withRetryableCodes(Set retryableCodes) { + return new GrpcCallContext( + this.channel, + this.callOptions, + this.timeout, + this.streamWaitTimeout, + this.streamIdleTimeout, + this.channelAffinity, + this.extraHeaders, + this.retrySettings, + retryableCodes); } @Override @@ -285,6 +359,16 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { newChannelAffinity = this.channelAffinity; } + RetrySettings newRetrySettings = grpcCallContext.retrySettings; + if (newRetrySettings == null) { + newRetrySettings = this.retrySettings; + } + + Set newRetryableCodes = grpcCallContext.retryableCodes; + if (newRetryableCodes == null) { + newRetryableCodes = this.retryableCodes; + } + ImmutableMap> newExtraHeaders = Headers.mergeHeaders(extraHeaders, grpcCallContext.extraHeaders); @@ -305,7 +389,9 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { newStreamWaitTimeout, newStreamIdleTimeout, newChannelAffinity, - newExtraHeaders); + newExtraHeaders, + newRetrySettings, + newRetryableCodes); } /** The {@link Channel} set on this context. */ @@ -363,7 +449,9 @@ public GrpcCallContext withChannel(Channel newChannel) { this.streamWaitTimeout, this.streamIdleTimeout, this.channelAffinity, - this.extraHeaders); + this.extraHeaders, + this.retrySettings, + this.retryableCodes); } /** Returns a new instance with the call options set to the given call options. */ @@ -375,7 +463,9 @@ public GrpcCallContext withCallOptions(CallOptions newCallOptions) { this.streamWaitTimeout, this.streamIdleTimeout, this.channelAffinity, - this.extraHeaders); + this.extraHeaders, + this.retrySettings, + this.retryableCodes); } public GrpcCallContext withRequestParamsDynamicHeaderOption(String requestParams) { @@ -412,7 +502,9 @@ public int hashCode() { streamWaitTimeout, streamIdleTimeout, channelAffinity, - extraHeaders); + extraHeaders, + retrySettings, + retryableCodes); } @Override @@ -431,7 +523,9 @@ public boolean equals(Object o) { && Objects.equals(streamWaitTimeout, that.streamWaitTimeout) && Objects.equals(streamIdleTimeout, that.streamIdleTimeout) && Objects.equals(channelAffinity, that.channelAffinity) - && Objects.equals(extraHeaders, that.extraHeaders); + && Objects.equals(extraHeaders, that.extraHeaders) + && Objects.equals(retrySettings, that.retrySettings) + && Objects.equals(retryableCodes, that.retryableCodes); } Metadata getMetadata() { diff --git a/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java index 69c938f7d..a7a6430fe 100644 --- a/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java +++ b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java @@ -30,8 +30,9 @@ package com.google.api.gax.grpc; import static org.junit.Assert.assertEquals; - +import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.rpc.testing.FakeChannel; import com.google.api.gax.rpc.testing.FakeTransportChannel; @@ -43,9 +44,11 @@ import io.grpc.ManagedChannel; import io.grpc.Metadata.Key; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -334,6 +337,24 @@ public void testMergeWithTracer() { .isSameInstanceAs(defaultTracer); } + @Test + public void testWithRetrySettings() { + RetrySettings retrySettings = Mockito.mock(RetrySettings.class); + GrpcCallContext emptyContext = GrpcCallContext.createDefault(); + Truth.assertThat(emptyContext.getRetrySettings()).isNull(); + GrpcCallContext context = emptyContext.withRetrySettings(retrySettings); + Truth.assertThat(context.getRetrySettings()).isNotNull(); + } + + @Test + public void testWithRetryableCodes() { + Set codes = Collections.singleton(StatusCode.Code.UNAVAILABLE); + GrpcCallContext emptyContext = GrpcCallContext.createDefault(); + Truth.assertThat(emptyContext.getRetryableCodes()).isNull(); + GrpcCallContext context = emptyContext.withRetryableCodes(codes); + Truth.assertThat(context.getRetryableCodes()).isNotNull(); + } + private static Map> createTestExtraHeaders(String... keyValues) { Map> extraHeaders = new HashMap<>(); for (int i = 0; i < keyValues.length; i += 2) { diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index ebe11b599..bb58cecbc 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -31,7 +31,9 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; +import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.internal.Headers; import com.google.api.gax.tracing.ApiTracer; @@ -39,9 +41,11 @@ import com.google.auth.Credentials; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.threeten.bp.Duration; @@ -64,11 +68,13 @@ public final class HttpJsonCallContext implements ApiCallContext { private final Credentials credentials; private final ImmutableMap> extraHeaders; private final ApiTracer tracer; + private final RetrySettings retrySettings; + private final ImmutableSet retryableCodes; /** Returns an empty instance. */ public static HttpJsonCallContext createDefault() { - return new HttpJsonCallContext( - null, null, null, null, ImmutableMap.>of(), null); + return new HttpJsonCallContext(null, null, null, null, ImmutableMap.>of(), + null, null, null); } private HttpJsonCallContext( @@ -77,13 +83,17 @@ private HttpJsonCallContext( Instant deadline, Credentials credentials, ImmutableMap> extraHeaders, - ApiTracer tracer) { + ApiTracer tracer, + RetrySettings retrySettings, + Set retryableCodes) { this.channel = channel; this.timeout = timeout; this.deadline = deadline; this.credentials = credentials; this.extraHeaders = extraHeaders; this.tracer = tracer; + this.retrySettings = retrySettings; + this.retryableCodes = retryableCodes == null ? null : ImmutableSet.copyOf(retryableCodes); } /** @@ -148,14 +158,38 @@ public HttpJsonCallContext merge(ApiCallContext inputCallContext) { newTracer = this.tracer; } + RetrySettings newRetrySettings = httpJsonCallContext.retrySettings; + if (newRetrySettings == null) { + newRetrySettings = this.retrySettings; + } + + Set newRetryableCodes = httpJsonCallContext.retryableCodes; + if (newRetryableCodes == null) { + newRetryableCodes = this.retryableCodes; + } + return new HttpJsonCallContext( - newChannel, newTimeout, newDeadline, newCredentials, newExtraHeaders, newTracer); + newChannel, + newTimeout, + newDeadline, + newCredentials, + newExtraHeaders, + newTracer, + newRetrySettings, + newRetryableCodes); } @Override public HttpJsonCallContext withCredentials(Credentials newCredentials) { return new HttpJsonCallContext( - this.channel, this.timeout, this.deadline, newCredentials, this.extraHeaders, this.tracer); + this.channel, + this.timeout, + this.deadline, + newCredentials, + this.extraHeaders, + this.tracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -182,7 +216,14 @@ public HttpJsonCallContext withTimeout(Duration timeout) { } return new HttpJsonCallContext( - this.channel, timeout, this.deadline, this.credentials, this.extraHeaders, this.tracer); + this.channel, + timeout, + this.deadline, + this.credentials, + this.extraHeaders, + this.tracer, + this.retrySettings, + this.retryableCodes); } @Nullable @@ -219,8 +260,8 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); ImmutableMap> newExtraHeaders = Headers.mergeHeaders(this.extraHeaders, extraHeaders); - return new HttpJsonCallContext( - channel, timeout, deadline, credentials, newExtraHeaders, this.tracer); + return new HttpJsonCallContext(channel, timeout, deadline, credentials, newExtraHeaders, + this.tracer, this.retrySettings, this.retryableCodes); } @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @@ -241,14 +282,32 @@ public Credentials getCredentials() { return credentials; } + public RetrySettings getRetrySettings() { + return retrySettings; + } + + public HttpJsonCallContext withRetrySettings(RetrySettings retrySettings) { + return new HttpJsonCallContext(channel, timeout, deadline, credentials, extraHeaders, + this.tracer, retrySettings, retryableCodes); + } + + public Set getRetryableCodes() { + return retryableCodes; + } + + public HttpJsonCallContext withRetryableCodes(Set retryableCodes) { + return new HttpJsonCallContext(this.channel, this.timeout, this.deadline, this.credentials, + this.extraHeaders, this.tracer, this.retrySettings, retryableCodes); + } + public HttpJsonCallContext withChannel(HttpJsonChannel newChannel) { - return new HttpJsonCallContext( - newChannel, timeout, deadline, credentials, extraHeaders, this.tracer); + return new HttpJsonCallContext(newChannel, timeout, deadline, credentials, extraHeaders, + this.tracer, this.retrySettings, this.retryableCodes); } public HttpJsonCallContext withDeadline(Instant newDeadline) { - return new HttpJsonCallContext( - channel, timeout, newDeadline, credentials, extraHeaders, this.tracer); + return new HttpJsonCallContext(channel, timeout, newDeadline, credentials, extraHeaders, + this.tracer, this.retrySettings, this.retryableCodes); } @Nonnull @@ -265,8 +324,8 @@ public ApiTracer getTracer() { public HttpJsonCallContext withTracer(@Nonnull ApiTracer newTracer) { Preconditions.checkNotNull(newTracer); - return new HttpJsonCallContext( - channel, timeout, deadline, credentials, extraHeaders, newTracer); + return new HttpJsonCallContext(channel, timeout, deadline, credentials, extraHeaders, newTracer, + this.retrySettings, this.retryableCodes); } @Override @@ -283,11 +342,14 @@ public boolean equals(Object o) { && Objects.equals(deadline, that.deadline) && Objects.equals(credentials, that.credentials) && Objects.equals(extraHeaders, that.extraHeaders) - && Objects.equals(tracer, that.tracer); + && Objects.equals(tracer, that.tracer) + && Objects.equals(retrySettings, that.retrySettings) + && Objects.equals(retryableCodes, that.retryableCodes); } @Override public int hashCode() { - return Objects.hash(channel, timeout, deadline, credentials, extraHeaders, tracer); + return Objects.hash(channel, timeout, deadline, credentials, extraHeaders, tracer, + retrySettings, retryableCodes); } } diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java index e3412d2b6..2b2a59ed9 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java @@ -29,12 +29,16 @@ */ package com.google.api.gax.httpjson; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.rpc.testing.FakeChannel; import com.google.api.gax.rpc.testing.FakeTransportChannel; import com.google.api.gax.tracing.ApiTracer; import com.google.auth.Credentials; import com.google.common.truth.Truth; +import java.util.Collections; +import java.util.Set; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -190,4 +194,22 @@ public void testMergeWithTracer() { Truth.assertThat(ctxWithDefaultTracer.merge(HttpJsonCallContext.createDefault()).getTracer()) .isSameInstanceAs(defaultTracer); } + + @Test + public void testWithRetrySettings() { + RetrySettings retrySettings = Mockito.mock(RetrySettings.class); + HttpJsonCallContext emptyContext = HttpJsonCallContext.createDefault(); + Truth.assertThat(emptyContext.getRetrySettings()).isNull(); + HttpJsonCallContext context = emptyContext.withRetrySettings(retrySettings); + Truth.assertThat(context.getRetrySettings()).isNotNull(); + } + + @Test + public void testWithRetryableCodes() { + Set codes = Collections.singleton(StatusCode.Code.UNAVAILABLE); + HttpJsonCallContext emptyContext = HttpJsonCallContext.createDefault(); + Truth.assertThat(emptyContext.getRetryableCodes()).isNull(); + HttpJsonCallContext context = emptyContext.withRetryableCodes(codes); + Truth.assertThat(context.getRetryableCodes()).isNotNull(); + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index d494aba02..7ec5f411a 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -29,6 +29,8 @@ */ package com.google.api.gax.retrying; +import java.util.concurrent.CancellationException; + /** * A basic implementation of {@link ResultRetryAlgorithm}. Using this implementation would mean that * all exceptions should be retried, all responses should be accepted (including {@code null}) and @@ -36,7 +38,7 @@ * * @param attempt response type */ -public class BasicResultRetryAlgorithm implements ResultRetryAlgorithm { +public class BasicResultRetryAlgorithm implements ContextAwareResultRetryAlgorithm { /** * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. @@ -51,6 +53,20 @@ public TimedAttemptSettings createNextAttempt( return null; } + /** + * Always returns null, indicating that this algorithm does not provide any specific settings for + * the next attempt. + * + * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param prevResponse response returned by the previous attempt + * @param prevSettings previous attempt settings + */ + @Override + public TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable prevThrowable, + ResponseT prevResponse, TimedAttemptSettings prevSettings) { + return createNextAttempt(prevThrowable, prevResponse, prevSettings); + } + /** * Returns {@code true} if an exception was thrown ({@code prevThrowable != null}), {@code false} * otherwise. @@ -62,4 +78,17 @@ public TimedAttemptSettings createNextAttempt( public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) { return prevThrowable != null; } + + /** + * Returns {@code true} if an exception was thrown ({@code prevThrowable != null}), {@code false} + * otherwise. + * + * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param prevResponse response returned by the previous attempt + */ + @Override + public boolean shouldRetry(RetryingContext context, Throwable prevThrowable, + ResponseT prevResponse) throws CancellationException { + return shouldRetry(prevThrowable, prevResponse); + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index b96fd2003..bb8786bd5 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -77,7 +77,7 @@ class BasicRetryingFuture extends AbstractFuture this.retryAlgorithm = checkNotNull(retryAlgorithm); this.retryingContext = checkNotNull(context); - this.attemptSettings = retryAlgorithm.createFirstAttempt(); + this.attemptSettings = createFirstAttempt(retryingContext); // A micro crime, letting "this" reference to escape from constructor before initialization is // completed (via internal non-static class CompletionListener). But it is guaranteed to be ok, @@ -87,6 +87,10 @@ class BasicRetryingFuture extends AbstractFuture // here. super.addListener(new CompletionListener(), MoreExecutors.directExecutor()); } + + RetryAlgorithm getRetryAlgorithm() { + return this.retryAlgorithm; + } @Override public void setAttemptFuture(ApiFuture attemptFuture) { @@ -166,9 +170,8 @@ void handleAttempt(Throwable throwable, ResponseT response) { return; } - TimedAttemptSettings nextAttemptSettings = - retryAlgorithm.createNextAttempt(throwable, response, attemptSettings); - boolean shouldRetry = retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings); + TimedAttemptSettings nextAttemptSettings = createNextAttempt(retryingContext, throwable, response); + boolean shouldRetry = shouldRetry(retryingContext, throwable, response, nextAttemptSettings); if (shouldRetry) { // Log retry info if (LOG.isLoggable(Level.FINEST)) { @@ -214,6 +217,35 @@ void handleAttempt(Throwable throwable, ResponseT response) { } } + // Calls retryAlgorithm.createFirstAttempt() for the basic implementation. May be overridden by + // subclasses that can use the RetryingContext to determine the initial attempt settings. + TimedAttemptSettings createFirstAttempt(RetryingContext context) { + return retryAlgorithm.createFirstAttempt(); + } + + // Calls retryAlgorithm.createNextAttempt(throwable, response, attemptSettings) for the basic + // implementation. May be overridden by subclasses that can use the RetryingContext to determine + // the next attempt settings. + TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable throwable, + ResponseT response) { + return retryAlgorithm.createNextAttempt(throwable, response, attemptSettings); + } + + // Calls retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings) for the basic + // implementation. May be overridden by subclasses that can use the RetryingContext to determine + // whether the call should be retried. + boolean shouldRetry(RetryingContext context, Throwable throwable, ResponseT response, + TimedAttemptSettings nextAttemptSettings) { + return retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings); + } + + // Calls retryAlgorithm.getResultAlgorithm().shouldRetry(throwable, response) for the basic + // implementation. May be overridden by subclasses that can use the RetryingContext to determine + // whether the call should be retried. + boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { + return retryAlgorithm.getResultAlgorithm().shouldRetry(throwable, response); + } + // Sets attempt result futures. Note the "attempt result future" and "attempt future" are not same // things because there are more attempt futures than attempt result futures. // See AttemptCallable.call() for an example of such condition. diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java new file mode 100644 index 000000000..5639a3829 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.Callable; + +/** + * {@link BasicRetryingFuture} implementation that will use {@link RetrySettings} and retryable + * codes from the {@link RetryingContext} if they have been set. + */ +class ContextAwareBasicRetryingFuture extends BasicRetryingFuture { + + ContextAwareBasicRetryingFuture(Callable callable, + ContextAwareRetryAlgorithm retryAlgorithm, RetryingContext context) { + super(callable, retryAlgorithm, context); + } + + ContextAwareRetryAlgorithm getRetryAlgorithm() { + return (ContextAwareRetryAlgorithm) super.getRetryAlgorithm(); + } + + @Override + TimedAttemptSettings createFirstAttempt(RetryingContext context) { + return getRetryAlgorithm().createFirstAttempt(context); + } + + @Override + TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable throwable, + ResponseT response) { + return getRetryAlgorithm().createNextAttempt(context, throwable, response, + getAttemptSettings()); + } + + boolean shouldRetry(RetryingContext context, Throwable throwable, ResponseT response, + TimedAttemptSettings nextAttemptSettings) { + return getRetryAlgorithm().shouldRetry(context, throwable, response, nextAttemptSettings); + } + + boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { + return getRetryAlgorithm().getResultAlgorithm().shouldRetry(context, throwable, response); + } +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java new file mode 100644 index 000000000..17a54158d --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.Callable; + +/** + * {@link CallbackChainRetryingFuture} implementation that will use {@link RetrySettings} and + * retryable codes from the {@link RetryingContext} if they have been set. + */ +public class ContextAwareCallbackChainRetryingFuture + extends CallbackChainRetryingFuture { + + ContextAwareCallbackChainRetryingFuture(Callable callable, + ContextAwareRetryAlgorithm retryAlgorithm, + ScheduledRetryingExecutor retryingExecutor, RetryingContext context) { + super(callable, retryAlgorithm, retryingExecutor, context); + } + + @Override + ContextAwareRetryAlgorithm getRetryAlgorithm() { + return (ContextAwareRetryAlgorithm) super.getRetryAlgorithm(); + } + + /** Creates the first attempt settings using the given {@link RetryingContext}. */ + @Override + TimedAttemptSettings createFirstAttempt(RetryingContext context) { + return getRetryAlgorithm().createFirstAttempt(context); + } + + /** Creates the next attempt settings using the given {@link RetryingContext}. */ + @Override + TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable throwable, + ResponseT response) { + return getRetryAlgorithm().createNextAttempt(context, throwable, response, + getAttemptSettings()); + } + + /** + * Determines whether to schedule another attempt based on the given {@link RetryingContext} and + * the result and attempt settings of the previous attempt. + */ + @Override + boolean shouldRetry(RetryingContext context, Throwable throwable, ResponseT response, + TimedAttemptSettings nextAttemptSettings) { + return getRetryAlgorithm().shouldRetry(context, throwable, response, nextAttemptSettings); + } + + /** + * Determines whether to schedule another attempt based on the given {@link RetryingContext} and + * the result of the previous attempt. + */ + @Override + boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { + return getRetryAlgorithm().getResultAlgorithm().shouldRetry(context, throwable, response); + } + +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java new file mode 100644 index 000000000..31fd648f3 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.Callable; + +/** + * {@link DirectRetryingExecutor} implementation that will use {@link RetrySettings} and retryable + * codes from the {@link RetryingContext} if they have been set. + */ +public class ContextAwareDirectRetryingExecutor + extends DirectRetryingExecutor { + + private final ContextAwareRetryAlgorithm retryAlgorithm; + + public ContextAwareDirectRetryingExecutor(ContextAwareRetryAlgorithm retryAlgorithm) { + super(retryAlgorithm); + this.retryAlgorithm = retryAlgorithm; + } + + @Override + public RetryingFuture createFuture(Callable callable, + RetryingContext context) { + return new ContextAwareBasicRetryingFuture<>(callable, retryAlgorithm, context); + } + +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java new file mode 100644 index 000000000..f7f1adddd --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.CancellationException; + +/** + * A {@link ResultRetryAlgorithm} that can use a {@link RetryingContext} to determine whether to + * retry a call. + */ +public interface ContextAwareResultRetryAlgorithm + extends ResultRetryAlgorithm { + /** + * Creates a next attempt {@link TimedAttemptSettings}. + * + * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param prevResponse response returned by the previous attempt + * @param prevSettings previous attempt settings + * @return next attempt settings or {@code null}, if the implementing algorithm does not provide + * specific settings for the next attempt + */ + TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable prevThrowable, + ResponseT prevResponse, TimedAttemptSettings prevSettings); + + /** + * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * + * @param context the {@link RetryingContext} that may be used to determine whether the call + * should be retried + * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param prevResponse response returned by the previous attempt + * @throws CancellationException if the retrying process should be canceled + */ + boolean shouldRetry(RetryingContext context, Throwable prevThrowable, ResponseT prevResponse) + throws CancellationException; +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java new file mode 100644 index 000000000..f9d4da630 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java @@ -0,0 +1,126 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import com.google.api.core.BetaApi; +import java.util.concurrent.CancellationException; + +/** + * The retry algorithm, which makes decision based either on the thrown exception or the returned + * response, the execution time settings of the previous attempt, and the {@link RetrySettings} and + * retryable codes supplied by a {@link RetryingContext}. + * + *

+ * This class is thread-safe. + * + * @param response type + */ +public class ContextAwareRetryAlgorithm extends RetryAlgorithm { + /** + * Creates a new retry algorithm instance, which uses thrown exception or returned response and + * timed algorithms to make a decision. The result algorithm has higher priority than the timed + * algorithm. + * + * @param resultAlgorithm result algorithm to use + * @param timedAlgorithm timed algorithm to use + */ + public ContextAwareRetryAlgorithm(ContextAwareResultRetryAlgorithm resultAlgorithm, + ContextAwareTimedRetryAlgorithm timedAlgorithm) { + super(resultAlgorithm, timedAlgorithm); + } + + + /** + * Creates a first attempt {@link TimedAttemptSettings}. + * + * @param context the {@link RetryingContext} that can be used to get the initial + * {@link RetrySettings}. + * @return first attempt settings + */ + public TimedAttemptSettings createFirstAttempt(RetryingContext context) { + return getTimedAlgorithm().createFirstAttempt(context); + } + + /** + * Creates a next attempt {@link TimedAttemptSettings}. This method will return first non-null + * value, returned by either result or timed retry algorithms in that particular order. + * + * @param context the {@link RetryingContext} that can be used to determine the + * {@link RetrySettings} for the next attempt + * @param prevThrowable exception thrown by the previous attempt or null if a result was returned + * instead + * @param prevResponse response returned by the previous attempt or null if an exception was + * thrown instead + * @param prevSettings previous attempt settings + * @return next attempt settings, can be {@code null}, if no there should be no new attempt + */ + public TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable prevThrowable, + ResponseT prevResponse, TimedAttemptSettings prevSettings) { + // a small optimization, which allows to avoid calling relatively heavy methods + // like timedAlgorithm.createNextAttempt(), when it is not necessary. + if (!getResultAlgorithm().shouldRetry(context, prevThrowable, prevResponse)) { + return null; + } + + TimedAttemptSettings newSettings = + getResultAlgorithm().createNextAttempt(context, prevThrowable, prevResponse, prevSettings); + if (newSettings == null) { + newSettings = getTimedAlgorithm().createNextAttempt(context, prevSettings); + } + return newSettings; + } + + /** + * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * + * @param context the {@link RetryingContext} that can be used to determine whether another + * attempt should be made. + * @param prevThrowable exception thrown by the previous attempt or null if a result was returned + * instead + * @param prevResponse response returned by the previous attempt or null if an exception was + * thrown instead + * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if + * accepted + * @throws CancellationException if the retrying process should be canceled + * @return {@code true} if another attempt should be made, or {@code false} otherwise + */ + public boolean shouldRetry(RetryingContext context, Throwable prevThrowable, + ResponseT prevResponse, TimedAttemptSettings nextAttemptSettings) + throws CancellationException { + return getResultAlgorithm().shouldRetry(context, prevThrowable, prevResponse) + && nextAttemptSettings != null + && getTimedAlgorithm().shouldRetry(context, nextAttemptSettings); + } + + @Override + @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + public ContextAwareResultRetryAlgorithm getResultAlgorithm() { + return (ContextAwareResultRetryAlgorithm) super.getResultAlgorithm(); + } + + @Override + @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + public ContextAwareTimedRetryAlgorithm getTimedAlgorithm() { + return (ContextAwareTimedRetryAlgorithm) super.getTimedAlgorithm(); + } +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java new file mode 100644 index 000000000..2522cbd57 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; + +/** + * The retry executor which uses {@link ScheduledExecutorService} to schedule an attempt tasks. + * + *

+ * This implementation does not manage the lifecycle of the underlying + * {@link ScheduledExecutorService}, so it should be managed outside of this class (like calling the + * {@link ScheduledExecutorService#shutdown()} when the pool is not needed anymore). In a typical + * usage pattern there are usually multiple instances of this class sharing same instance of the + * underlying {@link ScheduledExecutorService}. + * + * The executor will use a {@link ContextAwareRetryAlgorithm} to create attempt settings and to + * determine whether to retry an attempt. + * + *

+ * This class is thread-safe. + * + * @param response type + */ +public class ContextAwareScheduledRetryingExecutor + extends ScheduledRetryingExecutor { + + private final ContextAwareRetryAlgorithm retryAlgorithm; + + public ContextAwareScheduledRetryingExecutor(ContextAwareRetryAlgorithm retryAlgorithm, + ScheduledExecutorService scheduler) { + super(retryAlgorithm, scheduler); + this.retryAlgorithm = retryAlgorithm; + } + + @Override + public RetryingFuture createFuture(Callable callable, + RetryingContext context) { + return new ContextAwareCallbackChainRetryingFuture<>(callable, retryAlgorithm, this, context); + } + +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java new file mode 100644 index 000000000..7c9879f2c --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.retrying; + +import java.util.concurrent.CancellationException; + +/** + * A timed retry algorithm is responsible for the following operations, based on the previous + * attempt settings, {@link RetryingContext} and current time: + * + *

    + *
  1. Creating first attempt {@link TimedAttemptSettings}. + *
  2. Accepting a task for retry so another attempt will be made. + *
  3. Canceling retrying process so the related {@link java.util.concurrent.Future} will be + * canceled. + *
  4. Creating {@link TimedAttemptSettings} for each subsequent retry attempt. + *
+ * + * Implementations of this interface must be be thread-safe. + */ +public interface ContextAwareTimedRetryAlgorithm extends TimedRetryAlgorithm { + + /** + * Creates a first attempt {@link TimedAttemptSettings}. + * + * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and + * retryable codes. + * @return first attempt settings + */ + TimedAttemptSettings createFirstAttempt(RetryingContext context); + + /** + * Creates a next attempt {@link TimedAttemptSettings}, which defines properties of the next + * attempt. + * + * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and + * retryable codes. + * @param prevSettings previous attempt settings + * @return next attempt settings or {@code null} if the implementing algorithm does not provide + * specific settings for the next attempt + */ + TimedAttemptSettings createNextAttempt(RetryingContext context, + TimedAttemptSettings prevSettings); + + /** + * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * + * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and + * retryable codes. + * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if + * accepted + * @throws CancellationException if the retrying process should be canceled + */ + boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) + throws CancellationException; +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index ecc91bb9e..7ad57ea3c 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -41,7 +41,7 @@ * *

This class is thread-safe. */ -public class ExponentialRetryAlgorithm implements TimedRetryAlgorithm { +public class ExponentialRetryAlgorithm implements ContextAwareTimedRetryAlgorithm { private final RetrySettings globalSettings; private final ApiClock clock; @@ -77,6 +77,35 @@ public TimedAttemptSettings createFirstAttempt() { .build(); } + /** + * Creates a first attempt {@link TimedAttemptSettings}. The first attempt is configured to be + * executed immediately. + * + * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and + * retryable codes. + * @return first attempt settings + */ + @Override + public TimedAttemptSettings createFirstAttempt(RetryingContext context) { + if (context.getRetrySettings() == null) { + return createFirstAttempt(); + } + + RetrySettings retrySettings = context.getRetrySettings(); + return TimedAttemptSettings.newBuilder() + // Use the given retrySettings rather than the settings this was created with. + // Attempts created using the TimedAttemptSettings built here will use these + // retrySettings, but a new call will not (unless overridden again). + .setGlobalSettings(retrySettings) + .setRpcTimeout(retrySettings.getInitialRpcTimeout()) + .setRetryDelay(Duration.ZERO) + .setRandomizedRetryDelay(Duration.ZERO) + .setAttemptCount(0) + .setOverallAttemptCount(0) + .setFirstAttemptStartTimeNanos(clock.nanoTime()) + .build(); + } + /** * Creates a next attempt {@link TimedAttemptSettings}. The implementation increments the current * attempt count and uses randomized exponential backoff factor for calculating next attempt @@ -100,7 +129,7 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) (long) (settings.getRetryDelayMultiplier() * prevSettings.getRetryDelay().toMillis()); newRetryDelay = Math.min(newRetryDelay, settings.getMaxRetryDelay().toMillis()); } - Duration randomDelay = Duration.ofMillis(nextRandomLong(newRetryDelay)); + Duration randomDelay = Duration.ofMillis(nextRandomLong(newRetryDelay, settings.isJittered())); // The rpc timeout is determined as follows: // attempt #0 - use the initialRpcTimeout; @@ -117,7 +146,7 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) Duration timeElapsed = Duration.ofNanos(clock.nanoTime()) .minus(Duration.ofNanos(prevSettings.getFirstAttemptStartTimeNanos())); - Duration timeLeft = globalSettings.getTotalTimeout().minus(timeElapsed).minus(randomDelay); + Duration timeLeft = settings.getTotalTimeout().minus(timeElapsed).minus(randomDelay); // If timeLeft at this point is < 0, the shouldRetry logic will prevent // the attempt from being made as it would exceed the totalTimeout. A negative RPC timeout @@ -137,6 +166,24 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) .build(); } + /** + * Creates a next attempt {@link TimedAttemptSettings}. The implementation increments the current + * attempt count and uses randomized exponential backoff factor for calculating next attempt + * execution time. + * + * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and + * retryable codes. + * @param prevSettings previous attempt settings + * @return next attempt settings + */ + @Override + public TimedAttemptSettings createNextAttempt(RetryingContext context, + TimedAttemptSettings prevSettings) { + // The RetrySettings from the context are not used here, as they have already been set as the + // global settings during the creation of the initial attempt. + return createNextAttempt(prevSettings); + } + /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * @@ -185,10 +232,25 @@ public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { return true; } + /** + * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * + * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and + * retryable codes. + * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if + * accepted + * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or + * totalTimeout limit, or {@code false} otherwise + */ + @Override + public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { + // The RetrySettings from the context are not used here, as they have already been set as the + // global settings during the creation of the initial attempt. + return shouldRetry(nextAttemptSettings); + } + // Injecting Random is not possible here, as Random does not provide nextLong(long bound) method - protected long nextRandomLong(long bound) { - return bound > 0 && globalSettings.isJittered() - ? ThreadLocalRandom.current().nextLong(bound) - : bound; + protected long nextRandomLong(long bound, boolean withJitter) { + return bound > 0 && withJitter ? ThreadLocalRandom.current().nextLong(bound) : bound; } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java b/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java index c2649d995..21a85a9ab 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java @@ -29,12 +29,16 @@ */ package com.google.api.gax.retrying; +import com.google.api.gax.rpc.StatusCode.Code; + // TODO(igorbernstein2): Remove this class once RetryingExecutor#createFuture(Callable) is // deprecated and removed. import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.NoopApiTracer; +import java.util.Set; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Backwards compatibility class to aid in transition to adding operation state to {@link @@ -51,4 +55,18 @@ public static RetryingContext create() { public ApiTracer getTracer() { return NoopApiTracer.getInstance(); } + + /** {@inheritDoc} */ + @Nullable + @Override + public RetrySettings getRetrySettings() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public Set getRetryableCodes() { + return null; + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java index 04f4bb929..6db53c4e2 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java @@ -30,8 +30,11 @@ package com.google.api.gax.retrying; import com.google.api.core.BetaApi; +import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.tracing.ApiTracer; +import java.util.Set; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Context for a retryable operation. @@ -43,4 +46,18 @@ public interface RetryingContext { /** Returns the {@link ApiTracer} associated with the current operation. */ @Nonnull ApiTracer getTracer(); + + /** + * Returns the {@link RetrySettings} to use with this context, or null if the default + * {@link RetrySettings} should be used. + */ + @Nullable + RetrySettings getRetrySettings(); + + /** + * Returns the retryable codes to use with this context, or null if the default + * retryable codes should be used. + */ + @Nullable + Set getRetryableCodes(); } diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index 1115027d6..0d101b2aa 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -31,11 +31,14 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; +import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.retrying.RetryingContext; +import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.tracing.ApiTracer; import com.google.auth.Credentials; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.threeten.bp.Duration; @@ -153,6 +156,39 @@ public interface ApiCallContext extends RetryingContext { @BetaApi("The surface for tracing is not stable yet and may change in the future") ApiCallContext withTracer(@Nonnull ApiTracer tracer); + /** The {@link RetrySettings} that were previously set for this context. */ + @Nullable + RetrySettings getRetrySettings(); + + /** + * Returns a new ApiCallContext with the given {@link RetrySettings} set. + * + *

+ * This sets the {@link RetrySettings} to use for the RPC. These settings will work in combination + * with either the default retryable codes for the RPC, or the retryable codes supplied through + * {@link #withRetryableCodes(Set)}. Calling {@link #withRetrySettings(RetrySettings)} on an RPC + * that does not include {@link Code#DEADLINE_EXCEEDED} as one of its retryable codes (or without + * calling {@link #withRetryableCodes(Set)} with a set that includes at least + * {@link Code#DEADLINE_EXCEEDED}) will effectively only set a simple timeout that is equal to + * {@link RetrySettings#getInitialRpcTimeout()}. It is better to use + * {@link #withTimeout(Duration)} if that is the intended behavior. + */ + ApiCallContext withRetrySettings(RetrySettings retrySettings); + + /** The retryable codes that were previously set for this context. */ + @Nullable + Set getRetryableCodes(); + + /** + * Returns a new ApiCallContext with the given retryable codes set. + * + *

+ * This sets the retryable codes to use for the RPC. These settings will work in combination with + * either the default {@link RetrySettings} for the RPC, or the {@link RetrySettings} supplied + * through {@link #withRetrySettings(RetrySettings)}. + */ + ApiCallContext withRetryableCodes(Set retryableCodes); + /** If inputContext is not null, returns it; if it is null, returns the present instance. */ ApiCallContext nullToSelf(ApiCallContext inputContext); diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java index caa0c2f7c..2eecd1204 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java @@ -30,6 +30,7 @@ package com.google.api.gax.rpc; import com.google.api.gax.retrying.BasicResultRetryAlgorithm; +import com.google.api.gax.retrying.RetryingContext; /* Package-private for internal use. */ class ApiResultRetryAlgorithm extends BasicResultRetryAlgorithm { @@ -38,4 +39,15 @@ class ApiResultRetryAlgorithm extends BasicResultRetryAlgorithm UnaryCallable retrying( .withTimeout(callSettings.getRetrySettings().getTotalTimeout())); } - RetryAlgorithm retryAlgorithm = - new RetryAlgorithm<>( + ContextAwareRetryAlgorithm retryAlgorithm = + new ContextAwareRetryAlgorithm<>( new ApiResultRetryAlgorithm(), new ExponentialRetryAlgorithm( callSettings.getRetrySettings(), clientContext.getClock())); - ScheduledRetryingExecutor retryingExecutor = - new ScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); + ContextAwareScheduledRetryingExecutor retryingExecutor = + new ContextAwareScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); return new RetryingCallable<>( clientContext.getDefaultCallContext(), innerCallable, retryingExecutor); } diff --git a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java index cb2e6bf9c..db7d8eadd 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java @@ -74,6 +74,8 @@ protected abstract RetryingExecutorWithContext getExecutor( protected abstract RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException); + + protected abstract RetrySettings getDefaultRetrySettings(); @Before public void setUp() { @@ -84,7 +86,7 @@ public void setUp() { public void testSuccess() throws Exception { FailingCallable callable = new FailingCallable(0, "SUCCESS", tracer); RetryingExecutorWithContext executor = - getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); + getExecutor(getAlgorithm(getDefaultRetrySettings(), 0, null)); RetryingFuture future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); @@ -100,7 +102,7 @@ public void testSuccess() throws Exception { public void testSuccessWithFailures() throws Exception { FailingCallable callable = new FailingCallable(5, "SUCCESS", tracer); RetryingExecutorWithContext executor = - getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); + getExecutor(getAlgorithm(getDefaultRetrySettings(), 0, null)); RetryingFuture future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); @@ -117,7 +119,7 @@ public void testSuccessWithFailures() throws Exception { public void testSuccessWithFailuresPeekGetAttempt() throws Exception { FailingCallable callable = new FailingCallable(5, "SUCCESS", tracer); RetryingExecutorWithContext executor = - getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); + getExecutor(getAlgorithm(getDefaultRetrySettings(), 0, null)); RetryingFuture future = executor.createFuture(callable, retryingContext); assertNull(future.peekAttemptResult()); @@ -143,7 +145,7 @@ public void testSuccessWithFailuresPeekGetAttempt() throws Exception { public void testMaxRetriesExceeded() throws Exception { FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingExecutorWithContext executor = - getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); + getExecutor(getAlgorithm(getDefaultRetrySettings(), 0, null)); RetryingFuture future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); @@ -164,10 +166,17 @@ public void testTotalTimeoutExceeded() throws Exception { .setInitialRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .setMaxRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .build(); - RetryingExecutorWithContext executor = - getExecutor(getAlgorithm(retrySettings, 0, null)); + boolean useContextRetrySettings = retryingContext.getRetrySettings() != null; + RetryingExecutorWithContext executor = getExecutor( + getAlgorithm(useContextRetrySettings ? getDefaultRetrySettings() : retrySettings, 0, null)); FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); - RetryingFuture future = executor.createFuture(callable, retryingContext); + RetryingContext context; + if (useContextRetrySettings) { + context = FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings); + } else { + context = FakeCallContext.createDefault().withTracer(tracer); + } + RetryingFuture future = executor.createFuture(callable, context); future.setAttemptFuture(executor.submit(future)); assertFutureFail(future, CustomException.class); @@ -208,7 +217,7 @@ public void testCancelOuterFutureBeforeStart() throws Exception { public void testCancelByRetryingAlgorithm() throws Exception { FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingExecutorWithContext executor = - getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 5, new CancellationException())); + getExecutor(getAlgorithm(getDefaultRetrySettings(), 5, new CancellationException())); RetryingFuture future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); @@ -227,7 +236,7 @@ public void testCancelByRetryingAlgorithm() throws Exception { public void testUnexpectedExceptionFromRetryAlgorithm() throws Exception { FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingExecutorWithContext executor = - getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 5, new RuntimeException())); + getExecutor(getAlgorithm(getDefaultRetrySettings(), 5, new RuntimeException())); RetryingFuture future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); @@ -250,15 +259,23 @@ public void testPollExceptionByPollAlgorithm() throws Exception { .setInitialRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .setMaxRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .build(); + boolean useContextRetrySettings = retryingContext.getRetrySettings() != null; - RetryAlgorithm retryAlgorithm = - new RetryAlgorithm<>( - new TestResultRetryAlgorithm(0, null), - new ExponentialPollAlgorithm(retrySettings, NanoClock.getDefaultClock())); + ContextAwareRetryAlgorithm retryAlgorithm = + new ContextAwareRetryAlgorithm<>(new TestResultRetryAlgorithm(0, null), + new ExponentialPollAlgorithm( + useContextRetrySettings ? getDefaultRetrySettings() : retrySettings, + NanoClock.getDefaultClock())); RetryingExecutorWithContext executor = getExecutor(retryAlgorithm); FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); - RetryingFuture future = executor.createFuture(callable, retryingContext); + RetryingContext context; + if (useContextRetrySettings) { + context = FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings); + } else { + context = FakeCallContext.createDefault().withTracer(tracer); + } + RetryingFuture future = executor.createFuture(callable, context); future.setAttemptFuture(executor.submit(future)); assertFutureFail(future, PollException.class); diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java new file mode 100644 index 000000000..16a9a878e --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import static org.mockito.Mockito.mock; + +import com.google.api.gax.tracing.ApiTracer; +import java.util.concurrent.Callable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + +@RunWith(JUnit4.class) +public class ContextAwareBasicRetryingFutureTest { + + @Test + public void testUsesRetryingContext() throws Exception { + @SuppressWarnings("unchecked") + Callable callable = mock(Callable.class); + @SuppressWarnings("unchecked") + ContextAwareRetryAlgorithm retryAlgorithm = mock(ContextAwareRetryAlgorithm.class); + RetryingContext retryingContext = mock(RetryingContext.class); + ApiTracer tracer = mock(ApiTracer.class); + TimedAttemptSettings timedAttemptSettings = mock(TimedAttemptSettings.class); + Mockito.when(retryingContext.getTracer()).thenReturn(tracer); + + Mockito.when(retryAlgorithm.createFirstAttempt(retryingContext)).thenReturn(timedAttemptSettings); + Mockito.when( + retryAlgorithm.createNextAttempt( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any())) + .thenReturn(timedAttemptSettings); + Mockito.when( + retryAlgorithm.shouldRetry( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any())) + .thenReturn(true); + + ContextAwareBasicRetryingFuture future = + new ContextAwareBasicRetryingFuture<>(callable, retryAlgorithm, retryingContext); + + future.handleAttempt(null, null); + + Mockito.verify(retryAlgorithm).createFirstAttempt(retryingContext); + Mockito.verify(retryAlgorithm).createNextAttempt( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any()); + Mockito.verify(retryAlgorithm).shouldRetry( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any()); + Mockito.verifyNoMoreInteractions(retryAlgorithm); + } +} diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java new file mode 100644 index 000000000..dc7c16a64 --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import static com.google.api.gax.retrying.FailingCallable.FAILING_RETRY_SETTINGS; +import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; +import com.google.api.core.CurrentMillisClock; +import com.google.api.gax.rpc.testing.FakeCallContext; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ContextAwareDirectRetryingExecutorTest extends AbstractRetryingExecutorTest { + + @Override + @Before + public void setUp() { + retryingContext = + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(FAST_RETRY_SETTINGS); + } + + @Override + protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { + return new ContextAwareDirectRetryingExecutor<>((ContextAwareRetryAlgorithm) retryAlgorithm); + } + + @Override + protected ContextAwareRetryAlgorithm getAlgorithm( + RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException) { + return new ContextAwareRetryAlgorithm<>( + new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), + new ExponentialRetryAlgorithm(retrySettings, CurrentMillisClock.getDefaultClock())); + } + + protected RetrySettings getDefaultRetrySettings() { + return FAILING_RETRY_SETTINGS; + } +} diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java new file mode 100644 index 000000000..ac08e9f41 --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java @@ -0,0 +1,354 @@ +/* + * Copyright 2017 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import static com.google.api.gax.retrying.FailingCallable.FAILING_RETRY_SETTINGS; +import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; +import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.api.core.ApiFuture; +import com.google.api.core.NanoClock; +import com.google.api.gax.retrying.FailingCallable.CustomException; +import com.google.api.gax.rpc.testing.FakeCallContext; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.threeten.bp.Duration; + +@RunWith(MockitoJUnitRunner.class) +public class ContextAwareScheduledRetryingExecutorTest extends AbstractRetryingExecutorTest { + private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + // Number of test runs, essential for multithreaded tests. + private static final int EXECUTIONS_COUNT = 5; + + @Override + @Before + public void setUp() { + retryingContext = + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(FAST_RETRY_SETTINGS); + } + + @Override + protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { + return getRetryingExecutor((ContextAwareRetryAlgorithm) retryAlgorithm, scheduler); + } + + @Override + protected ContextAwareRetryAlgorithm getAlgorithm( + RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException) { + return new ContextAwareRetryAlgorithm<>( + new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), + new ExponentialRetryAlgorithm(retrySettings, NanoClock.getDefaultClock())); + } + + @Override + protected RetrySettings getDefaultRetrySettings() { + return FAILING_RETRY_SETTINGS; + } + + private RetryingExecutorWithContext getRetryingExecutor( + ContextAwareRetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { + return new ContextAwareScheduledRetryingExecutor<>(retryAlgorithm, scheduler); + } + + @After + public void after() { + scheduler.shutdownNow(); + } + + @Test + public void testSuccessWithFailuresPeekAttempt() throws Exception { + for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { + final int maxRetries = 100; + + ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); + FailingCallable callable = new FailingCallable(15, "SUCCESS", tracer); + + RetrySettings retrySettings = + FAST_RETRY_SETTINGS + .toBuilder() + .setTotalTimeout(Duration.ofMillis(1000L)) + .setMaxAttempts(maxRetries) + .build(); + + RetryingExecutorWithContext executor = + getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); + RetryingFuture future = executor.createFuture(callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + + assertNull(future.peekAttemptResult()); + assertSame(future.peekAttemptResult(), future.peekAttemptResult()); + assertFalse(future.getAttemptResult().isDone()); + assertFalse(future.getAttemptResult().isCancelled()); + + future.setAttemptFuture(executor.submit(future)); + + int failedAttempts = 0; + while (!future.isDone()) { + ApiFuture attemptResult = future.peekAttemptResult(); + if (attemptResult != null) { + assertTrue(attemptResult.isDone()); + assertFalse(attemptResult.isCancelled()); + try { + attemptResult.get(); + } catch (ExecutionException e) { + if (e.getCause() instanceof CustomException) { + failedAttempts++; + } + } + } + Thread.sleep(0L, 100); + } + + assertFutureSuccess(future); + assertEquals(15, future.getAttemptSettings().getAttemptCount()); + assertTrue(failedAttempts > 0); + localExecutor.shutdownNow(); + } + } + + @Test + public void testSuccessWithFailuresGetAttempt() throws Exception { + for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { + final int maxRetries = 100; + + ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); + FailingCallable callable = new FailingCallable(15, "SUCCESS", tracer); + RetrySettings retrySettings = + FAST_RETRY_SETTINGS + .toBuilder() + .setTotalTimeout(Duration.ofMillis(1000L)) + .setMaxAttempts(maxRetries) + .build(); + + RetryingExecutorWithContext executor = + getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); + RetryingFuture future = executor.createFuture(callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + + assertNull(future.peekAttemptResult()); + assertSame(future.getAttemptResult(), future.getAttemptResult()); + assertFalse(future.getAttemptResult().isDone()); + assertFalse(future.getAttemptResult().isCancelled()); + + future.setAttemptFuture(executor.submit(future)); + + CustomException exception; + int checks = 0; + do { + exception = null; + checks++; + Future attemptResult = future.getAttemptResult(); + try { + // testing that the gotten attempt result is non-cancelable + assertFalse(attemptResult.cancel(false)); + assertFalse(attemptResult.cancel(true)); + attemptResult.get(); + assertNotNull(future.peekAttemptResult()); + } catch (ExecutionException e) { + exception = (CustomException) e.getCause(); + } + assertTrue(attemptResult.isDone()); + assertFalse(attemptResult.isCancelled()); + } while (exception != null && checks < maxRetries + 1); + + assertTrue(future.isDone()); + assertFutureSuccess(future); + assertEquals(15, future.getAttemptSettings().getAttemptCount()); + assertTrue("checks is equal to " + checks, checks > 1 && checks <= maxRetries); + localExecutor.shutdownNow(); + } + } + + @Test + public void testCancelGetAttempt() throws Exception { + for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { + ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); + final int maxRetries = 100; + + FailingCallable callable = new FailingCallable(maxRetries - 1, "SUCCESS", tracer); + RetrySettings retrySettings = + FAST_RETRY_SETTINGS + .toBuilder() + .setTotalTimeout(Duration.ofMillis(1000L)) + .setMaxAttempts(maxRetries) + .build(); + + RetryingExecutorWithContext executor = + getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); + RetryingFuture future = executor.createFuture(callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + + assertNull(future.peekAttemptResult()); + assertSame(future.getAttemptResult(), future.getAttemptResult()); + assertFalse(future.getAttemptResult().isDone()); + assertFalse(future.getAttemptResult().isCancelled()); + + future.setAttemptFuture(executor.submit(future)); + + CustomException exception; + CancellationException cancellationException = null; + int checks = 0; + int failedCancelations = 0; + do { + exception = null; + checks++; + Future attemptResult = future.getAttemptResult(); + try { + attemptResult.get(); + assertNotNull(future.peekAttemptResult()); + } catch (CancellationException e) { + cancellationException = e; + } catch (ExecutionException e) { + exception = (CustomException) e.getCause(); + } + assertTrue(attemptResult.isDone()); + if (!future.cancel(true)) { + failedCancelations++; + } + } while (exception != null && checks < maxRetries); + + assertTrue(future.isDone()); + assertNotNull(cancellationException); + // future.cancel(true) may return false sometimes, which is ok. Also, the every cancellation + // of + // an already cancelled future should return false (this is what -1 means here) + assertEquals(2, checks - (failedCancelations - 1)); + assertTrue(future.getAttemptSettings().getAttemptCount() > 0); + assertFutureCancel(future); + localExecutor.shutdownNow(); + } + } + + @Test + public void testCancelOuterFutureAfterStart() throws Exception { + for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { + ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); + FailingCallable callable = new FailingCallable(4, "SUCCESS", tracer); + RetrySettings retrySettings = + FAST_RETRY_SETTINGS + .toBuilder() + .setInitialRetryDelay(Duration.ofMillis(1_000L)) + .setMaxRetryDelay(Duration.ofMillis(1_000L)) + .setTotalTimeout(Duration.ofMillis(10_0000L)) + .build(); + RetryingExecutorWithContext executor = + getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); + RetryingFuture future = executor.createFuture(callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + future.setAttemptFuture(executor.submit(future)); + + Thread.sleep(30L); + + boolean res = future.cancel(false); + assertTrue(res); + assertFutureCancel(future); + assertTrue(future.getAttemptSettings().getAttemptCount() < 4); + localExecutor.shutdownNow(); + } + } + + @Test + public void testCancelIsTraced() throws Exception { + ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); + FailingCallable callable = new FailingCallable(4, "SUCCESS", tracer); + RetrySettings retrySettings = + FAST_RETRY_SETTINGS + .toBuilder() + .setInitialRetryDelay(Duration.ofMillis(1_000L)) + .setMaxRetryDelay(Duration.ofMillis(1_000L)) + .setTotalTimeout(Duration.ofMillis(10_0000L)) + .build(); + RetryingExecutorWithContext executor = + getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); + RetryingFuture future = executor.createFuture(callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + future.setAttemptFuture(executor.submit(future)); + + Thread.sleep(30L); + + boolean res = future.cancel(false); + assertTrue(res); + assertFutureCancel(future); + + Mockito.verify(tracer).attemptCancelled(); + localExecutor.shutdownNow(); + } + + @Test + public void testCancelProxiedFutureAfterStart() throws Exception { + // this is a heavy test, which takes a lot of time, so only few executions. + for (int executionsCount = 0; executionsCount < 2; executionsCount++) { + ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); + FailingCallable callable = new FailingCallable(5, "SUCCESS", tracer); + RetrySettings retrySettings = + FAST_RETRY_SETTINGS + .toBuilder() + .setInitialRetryDelay(Duration.ofMillis(1_000L)) + .setMaxRetryDelay(Duration.ofMillis(1_000L)) + .setTotalTimeout(Duration.ofMillis(10_0000L)) + .build(); + RetryingExecutorWithContext executor = + getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); + RetryingFuture future = executor.createFuture(callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + future.setAttemptFuture(executor.submit(future)); + + Thread.sleep(50L); + + // Note that shutdownNow() will not cancel internal FutureTasks automatically, which + // may potentially cause another thread handing on RetryingFuture#get() call forever. + // Canceling the tasks returned by shutdownNow() also does not help, because of missing + // feature + // in guava's ListenableScheduledFuture, which does not cancel itself, when its delegate is + // canceled. + // So only the graceful shutdown() is supported properly. + localExecutor.shutdown(); + + assertFutureFail(future, RejectedExecutionException.class); + localExecutor.shutdownNow(); + } + } +} diff --git a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java index b8a990fbb..2543e0139 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.retrying; +import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; import com.google.api.core.CurrentMillisClock; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -48,4 +49,9 @@ protected RetryAlgorithm getAlgorithm( new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), new ExponentialRetryAlgorithm(retrySettings, CurrentMillisClock.getDefaultClock())); } + + @Override + protected RetrySettings getDefaultRetrySettings() { + return FAST_RETRY_SETTINGS; + } } diff --git a/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java index da804a1b5..24f3db4ea 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java @@ -35,6 +35,7 @@ import static org.junit.Assert.assertTrue; import com.google.api.gax.core.FakeApiClock; +import com.google.api.gax.rpc.testing.FakeCallContext; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -57,6 +58,20 @@ public class ExponentialRetryAlgorithmTest { .build(); private final ExponentialRetryAlgorithm algorithm = new ExponentialRetryAlgorithm(retrySettings, clock); + private final RetrySettings retrySettingsOverride = + RetrySettings.newBuilder() + .setMaxAttempts(3) + .setInitialRetryDelay(Duration.ofMillis(2L)) + .setRetryDelayMultiplier(3.0) + .setMaxRetryDelay(Duration.ofMillis(18L)) + .setJittered(false) + .setInitialRpcTimeout(Duration.ofMillis(2L)) + .setRpcTimeoutMultiplier(3.0) + .setMaxRpcTimeout(Duration.ofMillis(18L)) + .setTotalTimeout(Duration.ofMillis(300L)) + .build(); + private final RetryingContext retryingContext = + FakeCallContext.createDefault().withRetrySettings(retrySettingsOverride); @Test public void testCreateFirstAttempt() { @@ -71,6 +86,19 @@ public void testCreateFirstAttempt() { assertEquals(Duration.ZERO, attempt.getRetryDelay()); } + @Test + public void testCreateFirstAttemptOverride() { + TimedAttemptSettings attempt = algorithm.createFirstAttempt(retryingContext); + + // Checking only the most core values, to not make this test too implementation specific. + assertEquals(0, attempt.getAttemptCount()); + assertEquals(0, attempt.getOverallAttemptCount()); + assertEquals(Duration.ZERO, attempt.getRetryDelay()); + assertEquals(Duration.ZERO, attempt.getRandomizedRetryDelay()); + assertEquals(retrySettingsOverride.getInitialRpcTimeout(), attempt.getRpcTimeout()); + assertEquals(Duration.ZERO, attempt.getRetryDelay()); + } + @Test public void testCreateNextAttempt() { TimedAttemptSettings firstAttempt = algorithm.createFirstAttempt(); @@ -90,6 +118,25 @@ public void testCreateNextAttempt() { assertEquals(Duration.ofMillis(4L), thirdAttempt.getRpcTimeout()); } + @Test + public void testCreateNextAttemptOverride() { + TimedAttemptSettings firstAttempt = algorithm.createFirstAttempt(retryingContext); + TimedAttemptSettings secondAttempt = algorithm.createNextAttempt(firstAttempt); + + // Checking only the most core values, to not make this test too implementation specific. + assertEquals(1, secondAttempt.getAttemptCount()); + assertEquals(1, secondAttempt.getOverallAttemptCount()); + assertEquals(Duration.ofMillis(2L), secondAttempt.getRetryDelay()); + assertEquals(Duration.ofMillis(2L), secondAttempt.getRandomizedRetryDelay()); + assertEquals(Duration.ofMillis(6L), secondAttempt.getRpcTimeout()); + + TimedAttemptSettings thirdAttempt = algorithm.createNextAttempt(secondAttempt); + assertEquals(2, thirdAttempt.getAttemptCount()); + assertEquals(Duration.ofMillis(6L), thirdAttempt.getRetryDelay()); + assertEquals(Duration.ofMillis(6L), thirdAttempt.getRandomizedRetryDelay()); + assertEquals(Duration.ofMillis(18L), thirdAttempt.getRpcTimeout()); + } + @Test public void testTruncateToTotalTimeout() { RetrySettings timeoutSettings = diff --git a/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java b/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java index a334d770e..4def04807 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java +++ b/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java @@ -47,6 +47,18 @@ class FailingCallable implements Callable { .setMaxRpcTimeout(Duration.ofMillis(8L)) .setTotalTimeout(Duration.ofMillis(400L)) .build(); + static final RetrySettings FAILING_RETRY_SETTINGS = + RetrySettings.newBuilder() + .setMaxAttempts(2) + .setInitialRetryDelay(Duration.ofNanos(1L)) + .setRetryDelayMultiplier(1) + .setMaxRetryDelay(Duration.ofNanos(1L)) + .setInitialRpcTimeout(Duration.ofNanos(1L)) + .setJittered(false) + .setRpcTimeoutMultiplier(1) + .setMaxRpcTimeout(Duration.ofNanos(1L)) + .setTotalTimeout(Duration.ofNanos(1L)) + .build(); private AtomicInteger attemptsCount = new AtomicInteger(0); private final ApiTracer tracer; diff --git a/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java index cbf760f2f..0e8e7e969 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java @@ -73,6 +73,11 @@ protected RetryAlgorithm getAlgorithm( new ExponentialRetryAlgorithm(retrySettings, NanoClock.getDefaultClock())); } + @Override + protected RetrySettings getDefaultRetrySettings() { + return FAST_RETRY_SETTINGS; + } + private RetryingExecutorWithContext getRetryingExecutor( RetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { diff --git a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java index aaff28b97..be98e8175 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java @@ -29,6 +29,8 @@ */ package com.google.api.gax.rpc; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.api.gax.core.FakeApiClock; @@ -51,6 +53,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; +import org.mockito.internal.util.collections.Sets; import org.threeten.bp.Duration; @RunWith(JUnit4.class) @@ -73,6 +76,17 @@ public class RetryingTest { .setMaxRpcTimeout(Duration.ofMillis(2L)) .setTotalTimeout(Duration.ofMillis(10L)) .build(); + private static final RetrySettings FAILING_RETRY_SETTINGS = + RetrySettings.newBuilder() + .setMaxAttempts(2) + .setInitialRetryDelay(Duration.ofMillis(0L)) + .setRetryDelayMultiplier(1) + .setMaxRetryDelay(Duration.ofMillis(0L)) + .setInitialRpcTimeout(Duration.ofMillis(1L)) + .setRpcTimeoutMultiplier(1) + .setMaxRpcTimeout(Duration.ofMillis(1L)) + .setTotalTimeout(Duration.ofMillis(1L)) + .build(); @Before public void resetClock() { @@ -109,6 +123,22 @@ public void retry() { assertRetrying(FAST_RETRY_SETTINGS); } + @Test + public void retryUsingContext() { + Throwable throwable = + new UnavailableException(null, FakeStatusCode.of(StatusCode.Code.INTERNAL), false); + Mockito.when(callInt.futureCall(Mockito.any(), Mockito.any())) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(ApiFutures.immediateFuture(2)); + + assertRetryingUsingContext(FAILING_RETRY_SETTINGS, + FakeCallContext.createDefault() + .withRetrySettings(FAST_RETRY_SETTINGS) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + } + @Test(expected = ApiException.class) public void retryTotalTimeoutExceeded() { Throwable throwable = @@ -127,6 +157,31 @@ public void retryTotalTimeoutExceeded() { assertRetrying(retrySettings); } + @Test + public void retryUsingContextTotalTimeoutExceeded() { + Throwable throwable = + new UnavailableException(null, FakeStatusCode.of(StatusCode.Code.INTERNAL), false); + Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(ApiFutures.immediateFuture(2)); + + RetrySettings retrySettings = + FAST_RETRY_SETTINGS + .toBuilder() + .setInitialRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) + .setMaxRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) + .build(); + + try { + assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() + .withRetrySettings(retrySettings) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + fail("missing expected exception"); + } catch (ApiException e) { + assertThat(e.getStatusCode().getCode()).isEqualTo(Code.INTERNAL); + } + } + @Test(expected = ApiException.class) public void retryMaxAttemptsExceeded() { Throwable throwable = @@ -139,6 +194,26 @@ public void retryMaxAttemptsExceeded() { assertRetrying(FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(2).build()); } + @Test + public void retryUsingContextMaxAttemptsExceeded() { + Throwable throwable = + new UnavailableException(null, FakeStatusCode.of(StatusCode.Code.INTERNAL), false); + Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(ApiFutures.immediateFuture(2)); + + try { + assertRetryingUsingContext(FAILING_RETRY_SETTINGS, + FakeCallContext.createDefault() + .withRetrySettings(FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(2).build()) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + fail("missing expected exception"); + } catch (ApiException e) { + assertThat(e.getStatusCode().getCode()).isEqualTo(Code.INTERNAL); + } + } + @Test public void retryWithinMaxAttempts() { Throwable throwable = @@ -151,6 +226,20 @@ public void retryWithinMaxAttempts() { assertRetrying(FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(3).build()); } + @Test + public void retryUsingContextWithinMaxAttempts() { + Throwable throwable = + new UnavailableException(null, FakeStatusCode.of(StatusCode.Code.INTERNAL), false); + Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(ApiFutures.immediateFuture(2)); + + assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() + .withRetrySettings(FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(3).build()) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + } + @Test public void retryWithOnlyMaxAttempts() { Throwable throwable = @@ -167,6 +256,24 @@ public void retryWithOnlyMaxAttempts() { .futureCall(Mockito.any(), Mockito.any()); } + @Test + public void retryUsingContextWithOnlyMaxAttempts() { + Throwable throwable = + new UnavailableException(null, FakeStatusCode.of(StatusCode.Code.INTERNAL), false); + Mockito.when(callInt.futureCall(Mockito.any(), Mockito.any())) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(ApiFutures.immediateFuture(2)); + + RetrySettings retrySettings = RetrySettings.newBuilder().setMaxAttempts(3).build(); + + assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() + .withRetrySettings(retrySettings) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + Mockito.verify(callInt, Mockito.times(3)) + .futureCall(Mockito.any(), Mockito.any()); + } + @Test public void retryWithoutRetrySettings() { Mockito.when(callInt.futureCall(Mockito.any(), Mockito.any())) @@ -178,6 +285,18 @@ public void retryWithoutRetrySettings() { Mockito.verify(callInt).futureCall(Mockito.any(), Mockito.any()); } + @Test + public void retryUsingContextWithoutRetrySettings() { + Mockito.when(callInt.futureCall(Mockito.any(), Mockito.any())) + .thenReturn(ApiFutures.immediateFuture(2)); + + RetrySettings retrySettings = RetrySettings.newBuilder().build(); + + assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() + .withRetrySettings(retrySettings)); + Mockito.verify(callInt).futureCall(Mockito.any(), Mockito.any()); + } + @Test public void retryOnStatusUnknown() { Throwable throwable = @@ -221,6 +340,24 @@ public void retryNoRecover() { } } + @Test + public void retryUsingContextNoRecover() { + Throwable throwable = + new FailedPreconditionException( + "foobar", null, FakeStatusCode.of(StatusCode.Code.FAILED_PRECONDITION), false); + Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + .thenReturn(RetryingTest.immediateFailedFuture(throwable)) + .thenReturn(ApiFutures.immediateFuture(2)); + try { + assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() + .withRetrySettings(FAST_RETRY_SETTINGS) + .withRetryableCodes(Sets.newSet(Code.UNAVAILABLE, Code.DEADLINE_EXCEEDED, Code.UNKNOWN))); + Assert.fail("Callable should have thrown an exception"); + } catch (ApiException expected) { + Truth.assertThat(expected).isSameInstanceAs(throwable); + } + } + @Test public void retryKeepFailing() { Throwable throwable = @@ -295,4 +432,11 @@ private void assertRetrying(RetrySettings retrySettings) { FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); Truth.assertThat(callable.call(1)).isEqualTo(2); } + + private void assertRetryingUsingContext(RetrySettings retrySettings, ApiCallContext context) { + UnaryCallSettings callSettings = createSettings(retrySettings); + UnaryCallable callable = + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); + Truth.assertThat(callable.call(1, context)).isEqualTo(2); + } } diff --git a/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java b/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java index ffc5185a7..13d3778cf 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java @@ -29,11 +29,14 @@ */ package com.google.api.gax.rpc; +import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.rpc.testing.FakeChannel; import com.google.api.gax.rpc.testing.FakeSimpleApi.StashCallable; import com.google.auth.Credentials; +import com.google.common.collect.ImmutableSet; import com.google.common.truth.Truth; +import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -71,8 +74,17 @@ public void call() throws Exception { public void callWithContext() throws Exception { FakeChannel channel = new FakeChannel(); Credentials credentials = Mockito.mock(Credentials.class); + RetrySettings retrySettings = Mockito.mock(RetrySettings.class); + Set retryableCodes = ImmutableSet.of( + StatusCode.Code.INTERNAL, + StatusCode.Code.UNAVAILABLE, + StatusCode.Code.DEADLINE_EXCEEDED); ApiCallContext context = - FakeCallContext.createDefault().withChannel(channel).withCredentials(credentials); + FakeCallContext.createDefault() + .withChannel(channel) + .withCredentials(credentials) + .withRetrySettings(retrySettings) + .withRetryableCodes(retryableCodes); StashCallable stashCallable = new StashCallable<>(1); UnaryCallable callable = stashCallable.withDefaultCallContext(FakeCallContext.createDefault()); @@ -82,5 +94,7 @@ public void callWithContext() throws Exception { FakeCallContext actualContext = (FakeCallContext) stashCallable.getContext(); Truth.assertThat(actualContext.getChannel()).isSameInstanceAs(channel); Truth.assertThat(actualContext.getCredentials()).isSameInstanceAs(credentials); + Truth.assertThat(actualContext.getRetrySettings()).isSameInstanceAs(retrySettings); + Truth.assertThat(actualContext.getRetryableCodes()).containsExactlyElementsIn(retryableCodes); } } diff --git a/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java b/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java index 61f668573..30cddf33b 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java +++ b/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java @@ -30,8 +30,10 @@ package com.google.api.gax.rpc.testing; import com.google.api.core.InternalApi; +import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.internal.Headers; import com.google.api.gax.tracing.ApiTracer; @@ -39,8 +41,10 @@ import com.google.auth.Credentials; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.threeten.bp.Duration; @@ -54,6 +58,8 @@ public class FakeCallContext implements ApiCallContext { private final Duration streamIdleTimeout; private final ImmutableMap> extraHeaders; private final ApiTracer tracer; + private final RetrySettings retrySettings; + private final ImmutableSet retryableCodes; private FakeCallContext( Credentials credentials, @@ -62,7 +68,9 @@ private FakeCallContext( Duration streamWaitTimeout, Duration streamIdleTimeout, ImmutableMap> extraHeaders, - ApiTracer tracer) { + ApiTracer tracer, + RetrySettings retrySettings, + Set retryableCodes) { this.credentials = credentials; this.channel = channel; this.timeout = timeout; @@ -70,11 +78,13 @@ private FakeCallContext( this.streamIdleTimeout = streamIdleTimeout; this.extraHeaders = extraHeaders; this.tracer = tracer; + this.retrySettings = retrySettings; + this.retryableCodes = retryableCodes == null ? null : ImmutableSet.copyOf(retryableCodes); } public static FakeCallContext createDefault() { return new FakeCallContext( - null, null, null, null, null, ImmutableMap.>of(), null); + null, null, null, null, null, ImmutableMap.>of(), null, null, null); } @Override @@ -135,6 +145,16 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { newTracer = this.tracer; } + RetrySettings newRetrySettings = fakeCallContext.retrySettings; + if (newRetrySettings == null) { + newRetrySettings = this.retrySettings; + } + + Set newRetryableCodes = fakeCallContext.retryableCodes; + if (newRetryableCodes == null) { + newRetryableCodes = this.retryableCodes; + } + ImmutableMap> newExtraHeaders = Headers.mergeHeaders(extraHeaders, fakeCallContext.extraHeaders); return new FakeCallContext( @@ -144,7 +164,43 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { newStreamWaitTimeout, newStreamIdleTimeout, newExtraHeaders, - newTracer); + newTracer, + newRetrySettings, + newRetryableCodes); + } + + public RetrySettings getRetrySettings() { + return retrySettings; + } + + public FakeCallContext withRetrySettings(RetrySettings retrySettings) { + return new FakeCallContext( + this.credentials, + this.channel, + this.timeout, + this.streamWaitTimeout, + this.streamIdleTimeout, + this.extraHeaders, + this.tracer, + retrySettings, + this.retryableCodes); + } + + public Set getRetryableCodes() { + return retryableCodes; + } + + public FakeCallContext withRetryableCodes(Set retryableCodes) { + return new FakeCallContext( + this.credentials, + this.channel, + this.timeout, + this.streamWaitTimeout, + this.streamIdleTimeout, + this.extraHeaders, + this.tracer, + this.retrySettings, + retryableCodes); } public Credentials getCredentials() { @@ -181,7 +237,9 @@ public FakeCallContext withCredentials(Credentials credentials) { this.streamWaitTimeout, this.streamIdleTimeout, this.extraHeaders, - this.tracer); + this.tracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -203,7 +261,9 @@ public FakeCallContext withChannel(FakeChannel channel) { this.streamWaitTimeout, this.streamIdleTimeout, this.extraHeaders, - this.tracer); + this.tracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -225,7 +285,9 @@ public FakeCallContext withTimeout(Duration timeout) { this.streamWaitTimeout, this.streamIdleTimeout, this.extraHeaders, - this.tracer); + this.tracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -237,7 +299,9 @@ public ApiCallContext withStreamWaitTimeout(@Nullable Duration streamWaitTimeout streamWaitTimeout, this.streamIdleTimeout, this.extraHeaders, - this.tracer); + this.tracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -250,7 +314,9 @@ public ApiCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeout this.streamWaitTimeout, streamIdleTimeout, this.extraHeaders, - this.tracer); + this.tracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -265,7 +331,9 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { streamWaitTimeout, streamIdleTimeout, newExtraHeaders, - this.tracer); + this.tracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -295,7 +363,9 @@ public ApiCallContext withTracer(@Nonnull ApiTracer tracer) { this.streamWaitTimeout, this.streamIdleTimeout, this.extraHeaders, - tracer); + tracer, + this.retrySettings, + this.retryableCodes); } public static FakeCallContext create(ClientContext clientContext) { From 83c2f16bef818108399abd5f963a180997b63719 Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Mon, 9 Nov 2020 19:46:39 +0100 Subject: [PATCH 02/23] chore: fix formatting and license headers --- .../google/api/gax/grpc/GrpcCallContext.java | 2 +- .../api/gax/grpc/GrpcCallContextTest.java | 1 + .../api/gax/httpjson/HttpJsonCallContext.java | 81 +++++++++++++++---- .../retrying/BasicResultRetryAlgorithm.java | 15 ++-- .../api/gax/retrying/BasicRetryingFuture.java | 17 ++-- .../ContextAwareBasicRetryingFuture.java | 61 ++++++++------ ...ntextAwareCallbackChainRetryingFuture.java | 59 ++++++++------ .../ContextAwareDirectRetryingExecutor.java | 44 +++++----- .../ContextAwareResultRetryAlgorithm.java | 50 +++++++----- .../retrying/ContextAwareRetryAlgorithm.java | 80 ++++++++++-------- ...ContextAwareScheduledRetryingExecutor.java | 64 ++++++++------- .../ContextAwareTimedRetryAlgorithm.java | 64 ++++++++------- .../retrying/ExponentialRetryAlgorithm.java | 18 ++--- .../api/gax/retrying/NoopRetryingContext.java | 8 +- .../google/api/gax/rpc/ApiCallContext.java | 29 ++++--- .../api/gax/rpc/ApiResultRetryAlgorithm.java | 11 ++- .../AbstractRetryingExecutorTest.java | 11 ++- .../ContextAwareBasicRetryingFutureTest.java | 19 +++-- ...ontextAwareDirectRetryingExecutorTest.java | 6 +- ...extAwareScheduledRetryingExecutorTest.java | 36 ++++++--- .../retrying/DirectRetryingExecutorTest.java | 1 + .../com/google/api/gax/rpc/RetryingTest.java | 44 ++++++---- .../google/api/gax/rpc/UnaryCallableTest.java | 9 ++- .../api/gax/rpc/testing/FakeCallContext.java | 2 +- 24 files changed, 443 insertions(+), 289 deletions(-) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index 7a6491b40..b663490e8 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -363,7 +363,7 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { if (newRetrySettings == null) { newRetrySettings = this.retrySettings; } - + Set newRetryableCodes = grpcCallContext.retryableCodes; if (newRetryableCodes == null) { newRetryableCodes = this.retryableCodes; diff --git a/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java index a7a6430fe..dbdc8fd5d 100644 --- a/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java +++ b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java @@ -30,6 +30,7 @@ package com.google.api.gax.grpc; import static org.junit.Assert.assertEquals; + import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.StatusCode; diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index bb58cecbc..bc718a9e3 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -73,8 +73,8 @@ public final class HttpJsonCallContext implements ApiCallContext { /** Returns an empty instance. */ public static HttpJsonCallContext createDefault() { - return new HttpJsonCallContext(null, null, null, null, ImmutableMap.>of(), - null, null, null); + return new HttpJsonCallContext( + null, null, null, null, ImmutableMap.>of(), null, null, null); } private HttpJsonCallContext( @@ -260,8 +260,15 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); ImmutableMap> newExtraHeaders = Headers.mergeHeaders(this.extraHeaders, extraHeaders); - return new HttpJsonCallContext(channel, timeout, deadline, credentials, newExtraHeaders, - this.tracer, this.retrySettings, this.retryableCodes); + return new HttpJsonCallContext( + channel, + timeout, + deadline, + credentials, + newExtraHeaders, + this.tracer, + this.retrySettings, + this.retryableCodes); } @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @@ -287,8 +294,15 @@ public RetrySettings getRetrySettings() { } public HttpJsonCallContext withRetrySettings(RetrySettings retrySettings) { - return new HttpJsonCallContext(channel, timeout, deadline, credentials, extraHeaders, - this.tracer, retrySettings, retryableCodes); + return new HttpJsonCallContext( + channel, + timeout, + deadline, + credentials, + extraHeaders, + this.tracer, + retrySettings, + retryableCodes); } public Set getRetryableCodes() { @@ -296,18 +310,39 @@ public Set getRetryableCodes() { } public HttpJsonCallContext withRetryableCodes(Set retryableCodes) { - return new HttpJsonCallContext(this.channel, this.timeout, this.deadline, this.credentials, - this.extraHeaders, this.tracer, this.retrySettings, retryableCodes); + return new HttpJsonCallContext( + this.channel, + this.timeout, + this.deadline, + this.credentials, + this.extraHeaders, + this.tracer, + this.retrySettings, + retryableCodes); } public HttpJsonCallContext withChannel(HttpJsonChannel newChannel) { - return new HttpJsonCallContext(newChannel, timeout, deadline, credentials, extraHeaders, - this.tracer, this.retrySettings, this.retryableCodes); + return new HttpJsonCallContext( + newChannel, + timeout, + deadline, + credentials, + extraHeaders, + this.tracer, + this.retrySettings, + this.retryableCodes); } public HttpJsonCallContext withDeadline(Instant newDeadline) { - return new HttpJsonCallContext(channel, timeout, newDeadline, credentials, extraHeaders, - this.tracer, this.retrySettings, this.retryableCodes); + return new HttpJsonCallContext( + channel, + timeout, + newDeadline, + credentials, + extraHeaders, + this.tracer, + this.retrySettings, + this.retryableCodes); } @Nonnull @@ -324,8 +359,15 @@ public ApiTracer getTracer() { public HttpJsonCallContext withTracer(@Nonnull ApiTracer newTracer) { Preconditions.checkNotNull(newTracer); - return new HttpJsonCallContext(channel, timeout, deadline, credentials, extraHeaders, newTracer, - this.retrySettings, this.retryableCodes); + return new HttpJsonCallContext( + channel, + timeout, + deadline, + credentials, + extraHeaders, + newTracer, + this.retrySettings, + this.retryableCodes); } @Override @@ -349,7 +391,14 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(channel, timeout, deadline, credentials, extraHeaders, tracer, - retrySettings, retryableCodes); + return Objects.hash( + channel, + timeout, + deadline, + credentials, + extraHeaders, + tracer, + retrySettings, + retryableCodes); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index 7ec5f411a..b41b3f19d 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -38,7 +38,8 @@ * * @param attempt response type */ -public class BasicResultRetryAlgorithm implements ContextAwareResultRetryAlgorithm { +public class BasicResultRetryAlgorithm + implements ContextAwareResultRetryAlgorithm { /** * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. @@ -62,8 +63,11 @@ public TimedAttemptSettings createNextAttempt( * @param prevSettings previous attempt settings */ @Override - public TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable prevThrowable, - ResponseT prevResponse, TimedAttemptSettings prevSettings) { + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable prevThrowable, + ResponseT prevResponse, + TimedAttemptSettings prevSettings) { return createNextAttempt(prevThrowable, prevResponse, prevSettings); } @@ -87,8 +91,9 @@ public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) { * @param prevResponse response returned by the previous attempt */ @Override - public boolean shouldRetry(RetryingContext context, Throwable prevThrowable, - ResponseT prevResponse) throws CancellationException { + public boolean shouldRetry( + RetryingContext context, Throwable prevThrowable, ResponseT prevResponse) + throws CancellationException { return shouldRetry(prevThrowable, prevResponse); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index bb8786bd5..822e1c53a 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -87,7 +87,7 @@ class BasicRetryingFuture extends AbstractFuture // here. super.addListener(new CompletionListener(), MoreExecutors.directExecutor()); } - + RetryAlgorithm getRetryAlgorithm() { return this.retryAlgorithm; } @@ -170,8 +170,10 @@ void handleAttempt(Throwable throwable, ResponseT response) { return; } - TimedAttemptSettings nextAttemptSettings = createNextAttempt(retryingContext, throwable, response); - boolean shouldRetry = shouldRetry(retryingContext, throwable, response, nextAttemptSettings); + TimedAttemptSettings nextAttemptSettings = + createNextAttempt(retryingContext, throwable, response); + boolean shouldRetry = + shouldRetry(retryingContext, throwable, response, nextAttemptSettings); if (shouldRetry) { // Log retry info if (LOG.isLoggable(Level.FINEST)) { @@ -226,15 +228,18 @@ TimedAttemptSettings createFirstAttempt(RetryingContext context) { // Calls retryAlgorithm.createNextAttempt(throwable, response, attemptSettings) for the basic // implementation. May be overridden by subclasses that can use the RetryingContext to determine // the next attempt settings. - TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable throwable, - ResponseT response) { + TimedAttemptSettings createNextAttempt( + RetryingContext context, Throwable throwable, ResponseT response) { return retryAlgorithm.createNextAttempt(throwable, response, attemptSettings); } // Calls retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings) for the basic // implementation. May be overridden by subclasses that can use the RetryingContext to determine // whether the call should be retried. - boolean shouldRetry(RetryingContext context, Throwable throwable, ResponseT response, + boolean shouldRetry( + RetryingContext context, + Throwable throwable, + ResponseT response, TimedAttemptSettings nextAttemptSettings) { return retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings); } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java index 5639a3829..d4fc3adf9 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java @@ -1,24 +1,31 @@ /* * Copyright 2020 Google LLC * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: * - * * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from this software without - * specific prior written permission. + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.api.gax.retrying; @@ -30,11 +37,14 @@ */ class ContextAwareBasicRetryingFuture extends BasicRetryingFuture { - ContextAwareBasicRetryingFuture(Callable callable, - ContextAwareRetryAlgorithm retryAlgorithm, RetryingContext context) { + ContextAwareBasicRetryingFuture( + Callable callable, + ContextAwareRetryAlgorithm retryAlgorithm, + RetryingContext context) { super(callable, retryAlgorithm, context); } + @Override ContextAwareRetryAlgorithm getRetryAlgorithm() { return (ContextAwareRetryAlgorithm) super.getRetryAlgorithm(); } @@ -45,17 +55,22 @@ TimedAttemptSettings createFirstAttempt(RetryingContext context) { } @Override - TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable throwable, - ResponseT response) { - return getRetryAlgorithm().createNextAttempt(context, throwable, response, - getAttemptSettings()); + TimedAttemptSettings createNextAttempt( + RetryingContext context, Throwable throwable, ResponseT response) { + return getRetryAlgorithm() + .createNextAttempt(context, throwable, response, getAttemptSettings()); } - boolean shouldRetry(RetryingContext context, Throwable throwable, ResponseT response, + @Override + boolean shouldRetry( + RetryingContext context, + Throwable throwable, + ResponseT response, TimedAttemptSettings nextAttemptSettings) { return getRetryAlgorithm().shouldRetry(context, throwable, response, nextAttemptSettings); } + @Override boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { return getRetryAlgorithm().getResultAlgorithm().shouldRetry(context, throwable, response); } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java index 17a54158d..b19bb915a 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java @@ -1,24 +1,31 @@ /* * Copyright 2020 Google LLC * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: * - * * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from this software without - * specific prior written permission. + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.api.gax.retrying; @@ -31,9 +38,11 @@ public class ContextAwareCallbackChainRetryingFuture extends CallbackChainRetryingFuture { - ContextAwareCallbackChainRetryingFuture(Callable callable, + ContextAwareCallbackChainRetryingFuture( + Callable callable, ContextAwareRetryAlgorithm retryAlgorithm, - ScheduledRetryingExecutor retryingExecutor, RetryingContext context) { + ScheduledRetryingExecutor retryingExecutor, + RetryingContext context) { super(callable, retryAlgorithm, retryingExecutor, context); } @@ -50,10 +59,10 @@ TimedAttemptSettings createFirstAttempt(RetryingContext context) { /** Creates the next attempt settings using the given {@link RetryingContext}. */ @Override - TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable throwable, - ResponseT response) { - return getRetryAlgorithm().createNextAttempt(context, throwable, response, - getAttemptSettings()); + TimedAttemptSettings createNextAttempt( + RetryingContext context, Throwable throwable, ResponseT response) { + return getRetryAlgorithm() + .createNextAttempt(context, throwable, response, getAttemptSettings()); } /** @@ -61,7 +70,10 @@ TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable throwa * the result and attempt settings of the previous attempt. */ @Override - boolean shouldRetry(RetryingContext context, Throwable throwable, ResponseT response, + boolean shouldRetry( + RetryingContext context, + Throwable throwable, + ResponseT response, TimedAttemptSettings nextAttemptSettings) { return getRetryAlgorithm().shouldRetry(context, throwable, response, nextAttemptSettings); } @@ -74,5 +86,4 @@ boolean shouldRetry(RetryingContext context, Throwable throwable, ResponseT resp boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { return getRetryAlgorithm().getResultAlgorithm().shouldRetry(context, throwable, response); } - } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java index 31fd648f3..581a248ce 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java @@ -1,24 +1,31 @@ /* * Copyright 2020 Google LLC * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: * - * * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from this software without - * specific prior written permission. + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.api.gax.retrying; @@ -39,9 +46,8 @@ public ContextAwareDirectRetryingExecutor(ContextAwareRetryAlgorithm } @Override - public RetryingFuture createFuture(Callable callable, - RetryingContext context) { + public RetryingFuture createFuture( + Callable callable, RetryingContext context) { return new ContextAwareBasicRetryingFuture<>(callable, retryAlgorithm, context); } - } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java index f7f1adddd..708fcb0ba 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java @@ -1,24 +1,31 @@ /* * Copyright 2020 Google LLC * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: * - * * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from this software without - * specific prior written permission. + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.api.gax.retrying; @@ -37,16 +44,19 @@ public interface ContextAwareResultRetryAlgorithm * @param prevResponse response returned by the previous attempt * @param prevSettings previous attempt settings * @return next attempt settings or {@code null}, if the implementing algorithm does not provide - * specific settings for the next attempt + * specific settings for the next attempt */ - TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable prevThrowable, - ResponseT prevResponse, TimedAttemptSettings prevSettings); + TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable prevThrowable, + ResponseT prevResponse, + TimedAttemptSettings prevSettings); /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context the {@link RetryingContext} that may be used to determine whether the call - * should be retried + * should be retried * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) * @param prevResponse response returned by the previous attempt * @throws CancellationException if the retrying process should be canceled diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java index f9d4da630..e231999a8 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java @@ -1,24 +1,31 @@ /* * Copyright 2020 Google LLC * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: * - * * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from this software without - * specific prior written permission. + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.api.gax.retrying; @@ -30,8 +37,7 @@ * response, the execution time settings of the previous attempt, and the {@link RetrySettings} and * retryable codes supplied by a {@link RetryingContext}. * - *

- * This class is thread-safe. + *

This class is thread-safe. * * @param response type */ @@ -44,17 +50,17 @@ public class ContextAwareRetryAlgorithm extends RetryAlgorithm resultAlgorithm, + public ContextAwareRetryAlgorithm( + ContextAwareResultRetryAlgorithm resultAlgorithm, ContextAwareTimedRetryAlgorithm timedAlgorithm) { super(resultAlgorithm, timedAlgorithm); } - /** * Creates a first attempt {@link TimedAttemptSettings}. * - * @param context the {@link RetryingContext} that can be used to get the initial - * {@link RetrySettings}. + * @param context the {@link RetryingContext} that can be used to get the initial {@link + * RetrySettings}. * @return first attempt settings */ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { @@ -65,17 +71,20 @@ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { * Creates a next attempt {@link TimedAttemptSettings}. This method will return first non-null * value, returned by either result or timed retry algorithms in that particular order. * - * @param context the {@link RetryingContext} that can be used to determine the - * {@link RetrySettings} for the next attempt + * @param context the {@link RetryingContext} that can be used to determine the {@link + * RetrySettings} for the next attempt * @param prevThrowable exception thrown by the previous attempt or null if a result was returned - * instead + * instead * @param prevResponse response returned by the previous attempt or null if an exception was - * thrown instead + * thrown instead * @param prevSettings previous attempt settings * @return next attempt settings, can be {@code null}, if no there should be no new attempt */ - public TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable prevThrowable, - ResponseT prevResponse, TimedAttemptSettings prevSettings) { + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable prevThrowable, + ResponseT prevResponse, + TimedAttemptSettings prevSettings) { // a small optimization, which allows to avoid calling relatively heavy methods // like timedAlgorithm.createNextAttempt(), when it is not necessary. if (!getResultAlgorithm().shouldRetry(context, prevThrowable, prevResponse)) { @@ -94,18 +103,21 @@ public TimedAttemptSettings createNextAttempt(RetryingContext context, Throwable * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context the {@link RetryingContext} that can be used to determine whether another - * attempt should be made. + * attempt should be made. * @param prevThrowable exception thrown by the previous attempt or null if a result was returned - * instead + * instead * @param prevResponse response returned by the previous attempt or null if an exception was - * thrown instead + * thrown instead * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if - * accepted + * accepted * @throws CancellationException if the retrying process should be canceled * @return {@code true} if another attempt should be made, or {@code false} otherwise */ - public boolean shouldRetry(RetryingContext context, Throwable prevThrowable, - ResponseT prevResponse, TimedAttemptSettings nextAttemptSettings) + public boolean shouldRetry( + RetryingContext context, + Throwable prevThrowable, + ResponseT prevResponse, + TimedAttemptSettings nextAttemptSettings) throws CancellationException { return getResultAlgorithm().shouldRetry(context, prevThrowable, prevResponse) && nextAttemptSettings != null diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java index 2522cbd57..b62cb8df0 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java @@ -1,24 +1,31 @@ /* * Copyright 2020 Google LLC * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: * - * * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from this software without - * specific prior written permission. + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.api.gax.retrying; @@ -28,18 +35,16 @@ /** * The retry executor which uses {@link ScheduledExecutorService} to schedule an attempt tasks. * - *

- * This implementation does not manage the lifecycle of the underlying - * {@link ScheduledExecutorService}, so it should be managed outside of this class (like calling the - * {@link ScheduledExecutorService#shutdown()} when the pool is not needed anymore). In a typical - * usage pattern there are usually multiple instances of this class sharing same instance of the + *

This implementation does not manage the lifecycle of the underlying {@link + * ScheduledExecutorService}, so it should be managed outside of this class (like calling the {@link + * ScheduledExecutorService#shutdown()} when the pool is not needed anymore). In a typical usage + * pattern there are usually multiple instances of this class sharing same instance of the * underlying {@link ScheduledExecutorService}. - * - * The executor will use a {@link ContextAwareRetryAlgorithm} to create attempt settings and to + * + *

The executor will use a {@link ContextAwareRetryAlgorithm} to create attempt settings and to * determine whether to retry an attempt. * - *

- * This class is thread-safe. + *

This class is thread-safe. * * @param response type */ @@ -48,16 +53,15 @@ public class ContextAwareScheduledRetryingExecutor private final ContextAwareRetryAlgorithm retryAlgorithm; - public ContextAwareScheduledRetryingExecutor(ContextAwareRetryAlgorithm retryAlgorithm, - ScheduledExecutorService scheduler) { + public ContextAwareScheduledRetryingExecutor( + ContextAwareRetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { super(retryAlgorithm, scheduler); this.retryAlgorithm = retryAlgorithm; } @Override - public RetryingFuture createFuture(Callable callable, - RetryingContext context) { + public RetryingFuture createFuture( + Callable callable, RetryingContext context) { return new ContextAwareCallbackChainRetryingFuture<>(callable, retryAlgorithm, this, context); } - } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java index 7c9879f2c..fc6ac6d5c 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java @@ -1,26 +1,32 @@ /* * Copyright 2020 Google LLC * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: * - * * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from this software without - * specific prior written permission. + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - package com.google.api.gax.retrying; import java.util.concurrent.CancellationException; @@ -30,11 +36,11 @@ * attempt settings, {@link RetryingContext} and current time: * *

    - *
  1. Creating first attempt {@link TimedAttemptSettings}. - *
  2. Accepting a task for retry so another attempt will be made. - *
  3. Canceling retrying process so the related {@link java.util.concurrent.Future} will be - * canceled. - *
  4. Creating {@link TimedAttemptSettings} for each subsequent retry attempt. + *
  5. Creating first attempt {@link TimedAttemptSettings}. + *
  6. Accepting a task for retry so another attempt will be made. + *
  7. Canceling retrying process so the related {@link java.util.concurrent.Future} will be + * canceled. + *
  8. Creating {@link TimedAttemptSettings} for each subsequent retry attempt. *
* * Implementations of this interface must be be thread-safe. @@ -45,7 +51,7 @@ public interface ContextAwareTimedRetryAlgorithm extends TimedRetryAlgorithm { * Creates a first attempt {@link TimedAttemptSettings}. * * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes. + * retryable codes. * @return first attempt settings */ TimedAttemptSettings createFirstAttempt(RetryingContext context); @@ -55,21 +61,21 @@ public interface ContextAwareTimedRetryAlgorithm extends TimedRetryAlgorithm { * attempt. * * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes. + * retryable codes. * @param prevSettings previous attempt settings * @return next attempt settings or {@code null} if the implementing algorithm does not provide - * specific settings for the next attempt + * specific settings for the next attempt */ - TimedAttemptSettings createNextAttempt(RetryingContext context, - TimedAttemptSettings prevSettings); + TimedAttemptSettings createNextAttempt( + RetryingContext context, TimedAttemptSettings prevSettings); /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes. + * retryable codes. * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if - * accepted + * accepted * @throws CancellationException if the retrying process should be canceled */ boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index 7ad57ea3c..5ba2cc5f8 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -80,9 +80,9 @@ public TimedAttemptSettings createFirstAttempt() { /** * Creates a first attempt {@link TimedAttemptSettings}. The first attempt is configured to be * executed immediately. - * + * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and - * retryable codes. + * retryable codes. * @return first attempt settings */ @Override @@ -170,15 +170,15 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) * Creates a next attempt {@link TimedAttemptSettings}. The implementation increments the current * attempt count and uses randomized exponential backoff factor for calculating next attempt * execution time. - * + * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and - * retryable codes. + * retryable codes. * @param prevSettings previous attempt settings * @return next attempt settings */ @Override - public TimedAttemptSettings createNextAttempt(RetryingContext context, - TimedAttemptSettings prevSettings) { + public TimedAttemptSettings createNextAttempt( + RetryingContext context, TimedAttemptSettings prevSettings) { // The RetrySettings from the context are not used here, as they have already been set as the // global settings during the creation of the initial attempt. return createNextAttempt(prevSettings); @@ -236,11 +236,11 @@ public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and - * retryable codes. + * retryable codes. * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if - * accepted + * accepted * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or - * totalTimeout limit, or {@code false} otherwise + * totalTimeout limit, or {@code false} otherwise */ @Override public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { diff --git a/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java b/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java index 21a85a9ab..ae7cc0438 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/NoopRetryingContext.java @@ -29,16 +29,14 @@ */ package com.google.api.gax.retrying; -import com.google.api.gax.rpc.StatusCode.Code; - // TODO(igorbernstein2): Remove this class once RetryingExecutor#createFuture(Callable) is // deprecated and removed. +import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.NoopApiTracer; import java.util.Set; import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** * Backwards compatibility class to aid in transition to adding operation state to {@link @@ -56,15 +54,11 @@ public ApiTracer getTracer() { return NoopApiTracer.getInstance(); } - /** {@inheritDoc} */ - @Nullable @Override public RetrySettings getRetrySettings() { return null; } - /** {@inheritDoc} */ - @Nullable @Override public Set getRetryableCodes() { return null; diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index 0d101b2aa..7fe7ce976 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -162,16 +162,16 @@ public interface ApiCallContext extends RetryingContext { /** * Returns a new ApiCallContext with the given {@link RetrySettings} set. - * - *

- * This sets the {@link RetrySettings} to use for the RPC. These settings will work in combination - * with either the default retryable codes for the RPC, or the retryable codes supplied through - * {@link #withRetryableCodes(Set)}. Calling {@link #withRetrySettings(RetrySettings)} on an RPC - * that does not include {@link Code#DEADLINE_EXCEEDED} as one of its retryable codes (or without - * calling {@link #withRetryableCodes(Set)} with a set that includes at least - * {@link Code#DEADLINE_EXCEEDED}) will effectively only set a simple timeout that is equal to - * {@link RetrySettings#getInitialRpcTimeout()}. It is better to use - * {@link #withTimeout(Duration)} if that is the intended behavior. + * + *

This sets the {@link RetrySettings} to use for the RPC. These settings will work in + * combination with either the default retryable codes for the RPC, or the retryable codes + * supplied through {@link #withRetryableCodes(Set)}. Calling {@link + * #withRetrySettings(RetrySettings)} on an RPC that does not include {@link + * Code#DEADLINE_EXCEEDED} as one of its retryable codes (or without calling {@link + * #withRetryableCodes(Set)} with a set that includes at least {@link Code#DEADLINE_EXCEEDED}) + * will effectively only set a simple timeout that is equal to {@link + * RetrySettings#getInitialRpcTimeout()}. It is better to use {@link #withTimeout(Duration)} if + * that is the intended behavior. */ ApiCallContext withRetrySettings(RetrySettings retrySettings); @@ -181,11 +181,10 @@ public interface ApiCallContext extends RetryingContext { /** * Returns a new ApiCallContext with the given retryable codes set. - * - *

- * This sets the retryable codes to use for the RPC. These settings will work in combination with - * either the default {@link RetrySettings} for the RPC, or the {@link RetrySettings} supplied - * through {@link #withRetrySettings(RetrySettings)}. + * + *

This sets the retryable codes to use for the RPC. These settings will work in combination + * with either the default {@link RetrySettings} for the RPC, or the {@link RetrySettings} + * supplied through {@link #withRetrySettings(RetrySettings)}. */ ApiCallContext withRetryableCodes(Set retryableCodes); diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java index 2eecd1204..dda3c0c42 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java @@ -39,14 +39,17 @@ class ApiResultRetryAlgorithm extends BasicResultRetryAlgorithm getExecutor( protected abstract RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException); - + protected abstract RetrySettings getDefaultRetrySettings(); @Before @@ -167,8 +167,10 @@ public void testTotalTimeoutExceeded() throws Exception { .setMaxRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .build(); boolean useContextRetrySettings = retryingContext.getRetrySettings() != null; - RetryingExecutorWithContext executor = getExecutor( - getAlgorithm(useContextRetrySettings ? getDefaultRetrySettings() : retrySettings, 0, null)); + RetryingExecutorWithContext executor = + getExecutor( + getAlgorithm( + useContextRetrySettings ? getDefaultRetrySettings() : retrySettings, 0, null)); FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingContext context; if (useContextRetrySettings) { @@ -262,7 +264,8 @@ public void testPollExceptionByPollAlgorithm() throws Exception { boolean useContextRetrySettings = retryingContext.getRetrySettings() != null; ContextAwareRetryAlgorithm retryAlgorithm = - new ContextAwareRetryAlgorithm<>(new TestResultRetryAlgorithm(0, null), + new ContextAwareRetryAlgorithm<>( + new TestResultRetryAlgorithm(0, null), new ExponentialPollAlgorithm( useContextRetrySettings ? getDefaultRetrySettings() : retrySettings, NanoClock.getDefaultClock())); diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java index 16a9a878e..694c862ed 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java @@ -53,7 +53,8 @@ public void testUsesRetryingContext() throws Exception { TimedAttemptSettings timedAttemptSettings = mock(TimedAttemptSettings.class); Mockito.when(retryingContext.getTracer()).thenReturn(tracer); - Mockito.when(retryAlgorithm.createFirstAttempt(retryingContext)).thenReturn(timedAttemptSettings); + Mockito.when(retryAlgorithm.createFirstAttempt(retryingContext)) + .thenReturn(timedAttemptSettings); Mockito.when( retryAlgorithm.createNextAttempt( ArgumentMatchers.eq(retryingContext), @@ -73,14 +74,16 @@ public void testUsesRetryingContext() throws Exception { new ContextAwareBasicRetryingFuture<>(callable, retryAlgorithm, retryingContext); future.handleAttempt(null, null); - + Mockito.verify(retryAlgorithm).createFirstAttempt(retryingContext); - Mockito.verify(retryAlgorithm).createNextAttempt( - ArgumentMatchers.eq(retryingContext), - ArgumentMatchers.any(), - ArgumentMatchers.any(), - ArgumentMatchers.any()); - Mockito.verify(retryAlgorithm).shouldRetry( + Mockito.verify(retryAlgorithm) + .createNextAttempt( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any()); + Mockito.verify(retryAlgorithm) + .shouldRetry( ArgumentMatchers.eq(retryingContext), ArgumentMatchers.any(), ArgumentMatchers.any(), diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java index dc7c16a64..1ef43406d 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java @@ -31,6 +31,7 @@ import static com.google.api.gax.retrying.FailingCallable.FAILING_RETRY_SETTINGS; import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; + import com.google.api.core.CurrentMillisClock; import com.google.api.gax.rpc.testing.FakeCallContext; import org.junit.Before; @@ -49,7 +50,8 @@ public void setUp() { @Override protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { - return new ContextAwareDirectRetryingExecutor<>((ContextAwareRetryAlgorithm) retryAlgorithm); + return new ContextAwareDirectRetryingExecutor<>( + (ContextAwareRetryAlgorithm) retryAlgorithm); } @Override @@ -59,7 +61,7 @@ protected ContextAwareRetryAlgorithm getAlgorithm( new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), new ExponentialRetryAlgorithm(retrySettings, CurrentMillisClock.getDefaultClock())); } - + protected RetrySettings getDefaultRetrySettings() { return FAILING_RETRY_SETTINGS; } diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java index ac08e9f41..df96e3486 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java @@ -115,8 +115,10 @@ public void testSuccessWithFailuresPeekAttempt() throws Exception { RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + RetryingFuture future = + executor.createFuture( + callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); assertNull(future.peekAttemptResult()); assertSame(future.peekAttemptResult(), future.peekAttemptResult()); @@ -165,8 +167,10 @@ public void testSuccessWithFailuresGetAttempt() throws Exception { RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + RetryingFuture future = + executor.createFuture( + callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); assertNull(future.peekAttemptResult()); assertSame(future.getAttemptResult(), future.getAttemptResult()); @@ -218,8 +222,10 @@ public void testCancelGetAttempt() throws Exception { RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + RetryingFuture future = + executor.createFuture( + callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); assertNull(future.peekAttemptResult()); assertSame(future.getAttemptResult(), future.getAttemptResult()); @@ -276,8 +282,10 @@ public void testCancelOuterFutureAfterStart() throws Exception { .build(); RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + RetryingFuture future = + executor.createFuture( + callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); future.setAttemptFuture(executor.submit(future)); Thread.sleep(30L); @@ -303,8 +311,10 @@ public void testCancelIsTraced() throws Exception { .build(); RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + RetryingFuture future = + executor.createFuture( + callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); future.setAttemptFuture(executor.submit(future)); Thread.sleep(30L); @@ -332,8 +342,10 @@ public void testCancelProxiedFutureAfterStart() throws Exception { .build(); RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); + RetryingFuture future = + executor.createFuture( + callable, + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); future.setAttemptFuture(executor.submit(future)); Thread.sleep(50L); diff --git a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java index 2543e0139..631ca9f82 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java @@ -30,6 +30,7 @@ package com.google.api.gax.retrying; import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; + import com.google.api.core.CurrentMillisClock; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; diff --git a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java index be98e8175..9046ceb2b 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java @@ -31,6 +31,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; + import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.api.gax.core.FakeApiClock; @@ -133,7 +134,8 @@ public void retryUsingContext() { .thenReturn(RetryingTest.immediateFailedFuture(throwable)) .thenReturn(ApiFutures.immediateFuture(2)); - assertRetryingUsingContext(FAILING_RETRY_SETTINGS, + assertRetryingUsingContext( + FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() .withRetrySettings(FAST_RETRY_SETTINGS) .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); @@ -173,9 +175,11 @@ public void retryUsingContextTotalTimeoutExceeded() { .build(); try { - assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() - .withRetrySettings(retrySettings) - .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + assertRetryingUsingContext( + FAILING_RETRY_SETTINGS, + FakeCallContext.createDefault() + .withRetrySettings(retrySettings) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); fail("missing expected exception"); } catch (ApiException e) { assertThat(e.getStatusCode().getCode()).isEqualTo(Code.INTERNAL); @@ -204,7 +208,8 @@ public void retryUsingContextMaxAttemptsExceeded() { .thenReturn(ApiFutures.immediateFuture(2)); try { - assertRetryingUsingContext(FAILING_RETRY_SETTINGS, + assertRetryingUsingContext( + FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() .withRetrySettings(FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(2).build()) .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); @@ -235,9 +240,11 @@ public void retryUsingContextWithinMaxAttempts() { .thenReturn(RetryingTest.immediateFailedFuture(throwable)) .thenReturn(ApiFutures.immediateFuture(2)); - assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() - .withRetrySettings(FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(3).build()) - .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + assertRetryingUsingContext( + FAILING_RETRY_SETTINGS, + FakeCallContext.createDefault() + .withRetrySettings(FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(3).build()) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); } @Test @@ -267,9 +274,11 @@ public void retryUsingContextWithOnlyMaxAttempts() { RetrySettings retrySettings = RetrySettings.newBuilder().setMaxAttempts(3).build(); - assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() - .withRetrySettings(retrySettings) - .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); + assertRetryingUsingContext( + FAILING_RETRY_SETTINGS, + FakeCallContext.createDefault() + .withRetrySettings(retrySettings) + .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); Mockito.verify(callInt, Mockito.times(3)) .futureCall(Mockito.any(), Mockito.any()); } @@ -292,8 +301,8 @@ public void retryUsingContextWithoutRetrySettings() { RetrySettings retrySettings = RetrySettings.newBuilder().build(); - assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() - .withRetrySettings(retrySettings)); + assertRetryingUsingContext( + FAILING_RETRY_SETTINGS, FakeCallContext.createDefault().withRetrySettings(retrySettings)); Mockito.verify(callInt).futureCall(Mockito.any(), Mockito.any()); } @@ -349,9 +358,12 @@ public void retryUsingContextNoRecover() { .thenReturn(RetryingTest.immediateFailedFuture(throwable)) .thenReturn(ApiFutures.immediateFuture(2)); try { - assertRetryingUsingContext(FAILING_RETRY_SETTINGS, FakeCallContext.createDefault() - .withRetrySettings(FAST_RETRY_SETTINGS) - .withRetryableCodes(Sets.newSet(Code.UNAVAILABLE, Code.DEADLINE_EXCEEDED, Code.UNKNOWN))); + assertRetryingUsingContext( + FAILING_RETRY_SETTINGS, + FakeCallContext.createDefault() + .withRetrySettings(FAST_RETRY_SETTINGS) + .withRetryableCodes( + Sets.newSet(Code.UNAVAILABLE, Code.DEADLINE_EXCEEDED, Code.UNKNOWN))); Assert.fail("Callable should have thrown an exception"); } catch (ApiException expected) { Truth.assertThat(expected).isSameInstanceAs(throwable); diff --git a/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java b/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java index 13d3778cf..58342826b 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java @@ -75,10 +75,11 @@ public void callWithContext() throws Exception { FakeChannel channel = new FakeChannel(); Credentials credentials = Mockito.mock(Credentials.class); RetrySettings retrySettings = Mockito.mock(RetrySettings.class); - Set retryableCodes = ImmutableSet.of( - StatusCode.Code.INTERNAL, - StatusCode.Code.UNAVAILABLE, - StatusCode.Code.DEADLINE_EXCEEDED); + Set retryableCodes = + ImmutableSet.of( + StatusCode.Code.INTERNAL, + StatusCode.Code.UNAVAILABLE, + StatusCode.Code.DEADLINE_EXCEEDED); ApiCallContext context = FakeCallContext.createDefault() .withChannel(channel) diff --git a/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java b/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java index 30cddf33b..09696b9ff 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java +++ b/gax/src/test/java/com/google/api/gax/rpc/testing/FakeCallContext.java @@ -149,7 +149,7 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { if (newRetrySettings == null) { newRetrySettings = this.retrySettings; } - + Set newRetryableCodes = fakeCallContext.retryableCodes; if (newRetryableCodes == null) { newRetryableCodes = this.retryableCodes; From aabd3b089b35fa79056e37fff492288878bc51ea Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Wed, 11 Nov 2020 20:15:34 +0100 Subject: [PATCH 03/23] feat: use context retry settings for streaming calls --- .../ContextAwareStreamingRetryAlgorithm.java | 110 +++++++++ .../google/api/gax/rpc/ApiCallContext.java | 28 ++- .../api/gax/rpc/ApiResultRetryAlgorithm.java | 7 + .../com/google/api/gax/rpc/Callables.java | 10 +- ...ntextAwareStreamingRetryAlgorithmTest.java | 232 ++++++++++++++++++ .../gax/rpc/ApiResultRetryAlgorithmTest.java | 122 +++++++++ .../com/google/api/gax/rpc/RetryingTest.java | 8 +- .../api/gax/rpc/StreamingCallableTest.java | 17 +- 8 files changed, 522 insertions(+), 12 deletions(-) create mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java create mode 100644 gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java create mode 100644 gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java new file mode 100644 index 000000000..9422a0138 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import com.google.api.core.InternalApi; +import java.util.concurrent.CancellationException; + +/** + * The streaming retry algorithm, which makes decision based either on the thrown exception and the + * execution time settings of the previous attempt. This extends {@link RetryAlgorithm} to take + * additional information (provided by {@code ServerStreamingAttemptCallable}) into account. + * + *

This class is thread-safe. + * + *

Internal use only - public for technical reasons. + */ +@InternalApi("For internal use only") +public final class ContextAwareStreamingRetryAlgorithm + extends ContextAwareRetryAlgorithm { + public ContextAwareStreamingRetryAlgorithm( + ContextAwareResultRetryAlgorithm resultAlgorithm, + ContextAwareTimedRetryAlgorithm timedAlgorithm) { + super(resultAlgorithm, timedAlgorithm); + } + + /** + * {@inheritDoc} + * + *

The attempt settings will be reset if the stream attempt produced any messages. + */ + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable prevThrowable, + ResponseT prevResponse, + TimedAttemptSettings prevSettings) { + + if (prevThrowable instanceof ServerStreamingAttemptException) { + ServerStreamingAttemptException attemptException = + (ServerStreamingAttemptException) prevThrowable; + prevThrowable = prevThrowable.getCause(); + + // If we have made progress in the last attempt, then reset the delays + if (attemptException.hasSeenResponses()) { + prevSettings = + createFirstAttempt(context) + .toBuilder() + .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos()) + .setOverallAttemptCount(prevSettings.getOverallAttemptCount()) + .build(); + } + } + + return super.createNextAttempt(context, prevThrowable, prevResponse, prevSettings); + } + + /** + * {@inheritDoc} + * + *

Ensures retries are only scheduled if the {@link StreamResumptionStrategy} in the {@code + * ServerStreamingAttemptCallable} supports it. + */ + @Override + public boolean shouldRetry( + RetryingContext context, + Throwable prevThrowable, + ResponseT prevResponse, + TimedAttemptSettings nextAttemptSettings) + throws CancellationException { + + // Unwrap + if (prevThrowable instanceof ServerStreamingAttemptException) { + ServerStreamingAttemptException attemptException = + (ServerStreamingAttemptException) prevThrowable; + prevThrowable = prevThrowable.getCause(); + + if (!attemptException.canResume()) { + return false; + } + } + + return super.shouldRetry(context, prevThrowable, prevResponse, nextAttemptSettings); + } +} diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index 7fe7ce976..6f115075d 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -170,8 +170,27 @@ public interface ApiCallContext extends RetryingContext { * Code#DEADLINE_EXCEEDED} as one of its retryable codes (or without calling {@link * #withRetryableCodes(Set)} with a set that includes at least {@link Code#DEADLINE_EXCEEDED}) * will effectively only set a simple timeout that is equal to {@link - * RetrySettings#getInitialRpcTimeout()}. It is better to use {@link #withTimeout(Duration)} if - * that is the intended behavior. + * RetrySettings#getInitialRpcTimeout()}. It is recommended to use {@link #withTimeout(Duration)} + * if that is the intended behavior. + * + *

Example usage: + * + *

{@code
+   * ApiCallContext context = GrpcCallContext.createDefault()
+   *   .withRetrySettings(RetrySettings.newBuilder()
+   *     .setInitialRetryDelay(Duration.ofMillis(10L))
+   *     .setInitialRpcTimeout(Duration.ofMillis(100L))
+   *     .setMaxAttempts(10)
+   *     .setMaxRetryDelay(Duration.ofSeconds(10L))
+   *     .setMaxRpcTimeout(Duration.ofSeconds(30L))
+   *     .setRetryDelayMultiplier(1.4)
+   *     .setRpcTimeoutMultiplier(1.5)
+   *     .setTotalTimeout(Duration.ofMinutes(10L))
+   *     .build())
+   *   .withRetryableCodes(Sets.newSet(
+   *     StatusCode.Code.UNAVAILABLE,
+   *     StatusCode.Code.DEADLINE_EXCEEDED));
+   * }
*/ ApiCallContext withRetrySettings(RetrySettings retrySettings); @@ -185,6 +204,11 @@ public interface ApiCallContext extends RetryingContext { *

This sets the retryable codes to use for the RPC. These settings will work in combination * with either the default {@link RetrySettings} for the RPC, or the {@link RetrySettings} * supplied through {@link #withRetrySettings(RetrySettings)}. + * + *

Setting a non-empty set of retryable codes for an RPC that is not already retryable, will + * not have any effect and the RPC will NOT be retried. This option can only be used to change + * which codes are considered retryable for an RPC that already has at least one retryable code in + * its default settings. */ ApiCallContext withRetryableCodes(Set retryableCodes); diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java index dda3c0c42..ad33e7c8b 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java @@ -35,11 +35,18 @@ /* Package-private for internal use. */ class ApiResultRetryAlgorithm extends BasicResultRetryAlgorithm { + /** Returns true if prevThrowable is an {@link ApiException} that is retryable. */ @Override public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) { return (prevThrowable instanceof ApiException) && ((ApiException) prevThrowable).isRetryable(); } + /** + * If {@link RetryingContext#getRetryableCodes()} is not null: Returns true if the status code of + * prevThrowable is in the list of retryable code of the {@link RetryingContext}. + * + *

Otherwise it returns the result of {@link #shouldRetry(Throwable, Object)}. + */ @Override public boolean shouldRetry( RetryingContext context, Throwable prevThrowable, ResponseT prevResponse) { diff --git a/gax/src/main/java/com/google/api/gax/rpc/Callables.java b/gax/src/main/java/com/google/api/gax/rpc/Callables.java index 7bcc0ecf5..a981b1a43 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/Callables.java +++ b/gax/src/main/java/com/google/api/gax/rpc/Callables.java @@ -35,11 +35,11 @@ import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.retrying.ContextAwareRetryAlgorithm; import com.google.api.gax.retrying.ContextAwareScheduledRetryingExecutor; +import com.google.api.gax.retrying.ContextAwareStreamingRetryAlgorithm; import com.google.api.gax.retrying.ExponentialRetryAlgorithm; import com.google.api.gax.retrying.RetryAlgorithm; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.retrying.ScheduledRetryingExecutor; -import com.google.api.gax.retrying.StreamingRetryAlgorithm; import java.util.Collection; /** @@ -91,14 +91,14 @@ public static ServerStreamingCallable .withTimeout(callSettings.getRetrySettings().getTotalTimeout())); } - StreamingRetryAlgorithm retryAlgorithm = - new StreamingRetryAlgorithm<>( + ContextAwareStreamingRetryAlgorithm retryAlgorithm = + new ContextAwareStreamingRetryAlgorithm<>( new ApiResultRetryAlgorithm(), new ExponentialRetryAlgorithm( callSettings.getRetrySettings(), clientContext.getClock())); - ScheduledRetryingExecutor retryingExecutor = - new ScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); + ContextAwareScheduledRetryingExecutor retryingExecutor = + new ContextAwareScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); return new RetryingServerStreamingCallable<>( innerCallable, retryingExecutor, callSettings.getResumptionStrategy()); diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java new file mode 100644 index 000000000..1afe2e84f --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.core.ApiClock; +import com.google.api.gax.rpc.UnavailableException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mockito; +import org.threeten.bp.Duration; + +@RunWith(JUnit4.class) +public class ContextAwareStreamingRetryAlgorithmTest { + private static final RetrySettings DEFAULT_RETRY_SETTINGS = + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(10L)) + .setInitialRpcTimeout(Duration.ofMillis(100L)) + .setMaxAttempts(10) + .setMaxRetryDelay(Duration.ofSeconds(10L)) + .setMaxRpcTimeout(Duration.ofSeconds(30L)) + .setRetryDelayMultiplier(1.4) + .setRpcTimeoutMultiplier(1.5) + .setTotalTimeout(Duration.ofMinutes(10L)) + .build(); + + private static final RetrySettings CONTEXT_RETRY_SETTINGS = + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(20L)) + .setInitialRpcTimeout(Duration.ofMillis(200L)) + .setMaxAttempts(10) + .setMaxRetryDelay(Duration.ofSeconds(20L)) + .setMaxRpcTimeout(Duration.ofSeconds(60L)) + .setRetryDelayMultiplier(2.4) + .setRpcTimeoutMultiplier(2.5) + .setTotalTimeout(Duration.ofMinutes(20L)) + .build(); + + @Test + public void testFirstAttemptUsesDefaultSettings() { + RetryingContext context = mock(RetryingContext.class); + @SuppressWarnings("unchecked") + ContextAwareResultRetryAlgorithm resultAlgorithm = + mock(ContextAwareResultRetryAlgorithm.class); + ContextAwareTimedRetryAlgorithm timedAlgorithm = + new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); + + ContextAwareStreamingRetryAlgorithm algorithm = + new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + TimedAttemptSettings attempt = algorithm.createFirstAttempt(context); + assertThat(attempt.getGlobalSettings()).isSameInstanceAs(DEFAULT_RETRY_SETTINGS); + assertThat(attempt.getRpcTimeout()).isEqualTo(DEFAULT_RETRY_SETTINGS.getInitialRpcTimeout()); + } + + @Test + public void testFirstAttemptUsesContextSettings() { + RetryingContext context = mock(RetryingContext.class); + when(context.getRetrySettings()).thenReturn(CONTEXT_RETRY_SETTINGS); + @SuppressWarnings("unchecked") + ContextAwareResultRetryAlgorithm resultAlgorithm = + mock(ContextAwareResultRetryAlgorithm.class); + ContextAwareTimedRetryAlgorithm timedAlgorithm = + new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); + + ContextAwareStreamingRetryAlgorithm algorithm = + new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + TimedAttemptSettings attempt = algorithm.createFirstAttempt(context); + assertThat(attempt.getGlobalSettings()).isSameInstanceAs(CONTEXT_RETRY_SETTINGS); + assertThat(attempt.getRpcTimeout()).isEqualTo(CONTEXT_RETRY_SETTINGS.getInitialRpcTimeout()); + } + + @Test + public void testNextAttemptReturnsNullWhenShouldNotRetry() { + RetryingContext context = mock(RetryingContext.class); + @SuppressWarnings("unchecked") + ContextAwareResultRetryAlgorithm resultAlgorithm = + mock(ContextAwareResultRetryAlgorithm.class); + UnavailableException exception = mock(UnavailableException.class); + when(resultAlgorithm.shouldRetry(context, exception, null)).thenReturn(false); + ContextAwareTimedRetryAlgorithm timedAlgorithm = + new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); + + ContextAwareStreamingRetryAlgorithm algorithm = + new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + TimedAttemptSettings attempt = + algorithm.createNextAttempt(context, exception, null, mock(TimedAttemptSettings.class)); + assertThat(attempt).isNull(); + } + + @Test + public void testNextAttemptReturnsResultAlgorithmSettingsWhenShouldRetry() { + RetryingContext context = mock(RetryingContext.class); + @SuppressWarnings("unchecked") + ContextAwareResultRetryAlgorithm resultAlgorithm = + mock(ContextAwareResultRetryAlgorithm.class); + UnavailableException exception = mock(UnavailableException.class); + when(resultAlgorithm.shouldRetry(context, exception, null)).thenReturn(true); + TimedAttemptSettings next = mock(TimedAttemptSettings.class); + when(resultAlgorithm.createNextAttempt( + Mockito.eq(context), + Mockito.eq(exception), + Mockito.isNull(), + any(TimedAttemptSettings.class))) + .thenReturn(next); + ContextAwareTimedRetryAlgorithm timedAlgorithm = + new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); + + ContextAwareStreamingRetryAlgorithm algorithm = + new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + TimedAttemptSettings first = algorithm.createFirstAttempt(context); + TimedAttemptSettings attempt = algorithm.createNextAttempt(context, exception, null, first); + assertThat(attempt).isSameInstanceAs(next); + } + + @Test + public void testNextAttemptResetsTimedSettings() { + RetryingContext context = mock(RetryingContext.class); + @SuppressWarnings("unchecked") + ContextAwareResultRetryAlgorithm resultAlgorithm = + mock(ContextAwareResultRetryAlgorithm.class); + + ServerStreamingAttemptException exception = mock(ServerStreamingAttemptException.class); + when(exception.canResume()).thenReturn(true); + when(exception.hasSeenResponses()).thenReturn(true); + UnavailableException cause = mock(UnavailableException.class); + when(exception.getCause()).thenReturn(cause); + + when(resultAlgorithm.shouldRetry( + Mockito.eq(context), any(Throwable.class), Mockito.isNull())) + .thenReturn(true); + + ContextAwareTimedRetryAlgorithm timedAlgorithm = + new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); + ContextAwareStreamingRetryAlgorithm algorithm = + new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + TimedAttemptSettings first = algorithm.createFirstAttempt(context); + TimedAttemptSettings second = + algorithm.createNextAttempt(context, mock(Exception.class), null, first); + TimedAttemptSettings third = algorithm.createNextAttempt(context, exception, null, second); + assertThat(third.getFirstAttemptStartTimeNanos()) + .isEqualTo(first.getFirstAttemptStartTimeNanos()); + // The timeout values are reset to the second call. + assertThat(third.getRpcTimeout()).isEqualTo(second.getRpcTimeout()); + } + + @Test + public void testShouldNotRetryIfAttemptIsNonResumable() { + RetryingContext context = mock(RetryingContext.class); + + ServerStreamingAttemptException exception = mock(ServerStreamingAttemptException.class); + when(exception.canResume()).thenReturn(false); + UnavailableException cause = mock(UnavailableException.class); + when(exception.getCause()).thenReturn(cause); + + @SuppressWarnings("unchecked") + ContextAwareResultRetryAlgorithm resultAlgorithm = + mock(ContextAwareResultRetryAlgorithm.class); + when(resultAlgorithm.shouldRetry(context, cause, null)).thenReturn(true); + + ContextAwareTimedRetryAlgorithm timedAlgorithm = + new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); + ContextAwareStreamingRetryAlgorithm algorithm = + new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + // This should return false because the attempt exception indicates that it is non-resumable. + boolean shouldRetry = + algorithm.shouldRetry(context, exception, null, mock(TimedAttemptSettings.class)); + assertThat(shouldRetry).isFalse(); + } + + @Test + public void testShouldRetryIfAllSayYes() { + RetryingContext context = mock(RetryingContext.class); + + ServerStreamingAttemptException exception = mock(ServerStreamingAttemptException.class); + when(exception.canResume()).thenReturn(true); + UnavailableException cause = mock(UnavailableException.class); + when(exception.getCause()).thenReturn(cause); + + @SuppressWarnings("unchecked") + ContextAwareResultRetryAlgorithm resultAlgorithm = + mock(ContextAwareResultRetryAlgorithm.class); + when(resultAlgorithm.shouldRetry(context, cause, null)).thenReturn(true); + + ContextAwareTimedRetryAlgorithm timedAlgorithm = mock(ContextAwareTimedRetryAlgorithm.class); + when(timedAlgorithm.shouldRetry(Mockito.eq(context), any(TimedAttemptSettings.class))) + .thenReturn(true); + ContextAwareStreamingRetryAlgorithm algorithm = + new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + boolean shouldRetry = + algorithm.shouldRetry(context, exception, null, mock(TimedAttemptSettings.class)); + assertThat(shouldRetry).isTrue(); + } +} diff --git a/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java new file mode 100644 index 000000000..8830b4700 --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2020 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.rpc; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.gax.rpc.StatusCode.Code; +import java.util.Collections; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.internal.util.collections.Sets; + +@RunWith(JUnit4.class) +public class ApiResultRetryAlgorithmTest { + + @Test + public void testShouldRetryNoContext() { + ApiException nonRetryable = mock(ApiException.class); + when(nonRetryable.isRetryable()).thenReturn(false); + ApiException retryable = mock(ApiException.class); + when(retryable.isRetryable()).thenReturn(true); + + ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); + assertThat(algorithm.shouldRetry(nonRetryable, null)).isFalse(); + assertThat(algorithm.shouldRetry(retryable, null)).isTrue(); + } + + @Test + public void testShouldRetryWithContextWithoutRetryableCodes() { + ApiCallContext context = mock(ApiCallContext.class); + // No retryable codes in the call context, means that the retry algorithm should fall back to + // its default implementation. + when(context.getRetryableCodes()).thenReturn(null); + + StatusCode unavailable = mock(StatusCode.class); + when(unavailable.getCode()).thenReturn(Code.UNAVAILABLE); + ApiException nonRetryable = mock(ApiException.class); + when(nonRetryable.isRetryable()).thenReturn(false); + when(nonRetryable.getStatusCode()).thenReturn(unavailable); + + ApiException retryable = mock(ApiException.class); + when(retryable.isRetryable()).thenReturn(true); + when(retryable.getStatusCode()).thenReturn(unavailable); + + ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); + assertThat(algorithm.shouldRetry(context, nonRetryable, null)).isFalse(); + assertThat(algorithm.shouldRetry(context, retryable, null)).isTrue(); + } + + @Test + public void testShouldRetryWithContextWithRetryableCodes() { + ApiCallContext context = mock(ApiCallContext.class); + when(context.getRetryableCodes()) + .thenReturn(Sets.newSet(StatusCode.Code.DEADLINE_EXCEEDED, StatusCode.Code.UNAVAILABLE)); + + StatusCode unavailable = mock(StatusCode.class); + when(unavailable.getCode()).thenReturn(Code.UNAVAILABLE); + StatusCode dataLoss = mock(StatusCode.class); + when(dataLoss.getCode()).thenReturn(Code.DATA_LOSS); + + ApiException unavailableException = mock(ApiException.class); + // The return value of isRetryable() will be ignored, as UNAVAILABLE has been added as a + // retryable code to the call context. + when(unavailableException.isRetryable()).thenReturn(false); + when(unavailableException.getStatusCode()).thenReturn(unavailable); + + ApiException dataLossException = mock(ApiException.class); + when(dataLossException.isRetryable()).thenReturn(true); + when(dataLossException.getStatusCode()).thenReturn(dataLoss); + + ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); + assertThat(algorithm.shouldRetry(context, unavailableException, null)).isTrue(); + assertThat(algorithm.shouldRetry(context, dataLossException, null)).isFalse(); + } + + @Test + public void testShouldRetryWithContextWithEmptyRetryableCodes() { + ApiCallContext context = mock(ApiCallContext.class); + // This will effectively make the RPC non-retryable. + when(context.getRetryableCodes()).thenReturn(Collections.emptySet()); + + StatusCode unavailable = mock(StatusCode.class); + when(unavailable.getCode()).thenReturn(Code.UNAVAILABLE); + + ApiException unavailableException = mock(ApiException.class); + when(unavailableException.isRetryable()).thenReturn(true); + when(unavailableException.getStatusCode()).thenReturn(unavailable); + + ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); + assertThat(algorithm.shouldRetry(context, unavailableException, null)).isFalse(); + } +} diff --git a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java index 9046ceb2b..8b1280209 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java @@ -80,13 +80,13 @@ public class RetryingTest { private static final RetrySettings FAILING_RETRY_SETTINGS = RetrySettings.newBuilder() .setMaxAttempts(2) - .setInitialRetryDelay(Duration.ofMillis(0L)) + .setInitialRetryDelay(Duration.ofNanos(0L)) .setRetryDelayMultiplier(1) .setMaxRetryDelay(Duration.ofMillis(0L)) - .setInitialRpcTimeout(Duration.ofMillis(1L)) + .setInitialRpcTimeout(Duration.ofNanos(1L)) .setRpcTimeoutMultiplier(1) - .setMaxRpcTimeout(Duration.ofMillis(1L)) - .setTotalTimeout(Duration.ofMillis(1L)) + .setMaxRpcTimeout(Duration.ofNanos(1L)) + .setTotalTimeout(Duration.ofNanos(1L)) .build(); @Before diff --git a/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java b/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java index 46af5daf6..d56d46a45 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.rpc; +import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.rpc.testing.FakeCallableFactory; @@ -38,9 +39,11 @@ import com.google.api.gax.rpc.testing.FakeTransportChannel; import com.google.auth.Credentials; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.truth.Truth; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -134,8 +137,18 @@ public void testClientStreamingCall() { public void testClientStreamingCallWithContext() { FakeChannel channel = new FakeChannel(); Credentials credentials = Mockito.mock(Credentials.class); + RetrySettings retrySettings = Mockito.mock(RetrySettings.class); + Set retryableCodes = + ImmutableSet.of( + StatusCode.Code.INTERNAL, + StatusCode.Code.UNAVAILABLE, + StatusCode.Code.DEADLINE_EXCEEDED); ApiCallContext context = - FakeCallContext.createDefault().withChannel(channel).withCredentials(credentials); + FakeCallContext.createDefault() + .withChannel(channel) + .withCredentials(credentials) + .withRetrySettings(retrySettings) + .withRetryableCodes(retryableCodes); ClientStreamingStashCallable stashCallable = new ClientStreamingStashCallable<>(); ApiStreamObserver observer = Mockito.mock(ApiStreamObserver.class); @@ -146,5 +159,7 @@ public void testClientStreamingCallWithContext() { FakeCallContext actualContext = (FakeCallContext) stashCallable.getContext(); Truth.assertThat(actualContext.getChannel()).isSameInstanceAs(channel); Truth.assertThat(actualContext.getCredentials()).isSameInstanceAs(credentials); + Truth.assertThat(actualContext.getRetrySettings()).isSameInstanceAs(retrySettings); + Truth.assertThat(actualContext.getRetryableCodes()).containsExactlyElementsIn(retryableCodes); } } From ed22eaf39a53d837220029a274e0dacdb4029cca Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Thu, 7 Jan 2021 12:29:21 +0100 Subject: [PATCH 04/23] fix: process review comments --- .../api/gax/grpc/GrpcCallContextTest.java | 24 ++++++++--------- .../gax/httpjson/HttpJsonCallContextTest.java | 27 ++++++++++--------- .../ContextAwareBasicRetryingFuture.java | 6 ++--- ...ntextAwareCallbackChainRetryingFuture.java | 2 +- .../ContextAwareDirectRetryingExecutor.java | 2 +- .../ContextAwareResultRetryAlgorithm.java | 2 +- .../retrying/ContextAwareRetryAlgorithm.java | 12 ++++----- ...ContextAwareScheduledRetryingExecutor.java | 4 +-- .../ContextAwareStreamingRetryAlgorithm.java | 6 ++--- .../ContextAwareTimedRetryAlgorithm.java | 6 ++--- .../google/api/gax/rpc/ApiCallContext.java | 7 ++++- .../AbstractRetryingExecutorTest.java | 11 ++++++++ .../ContextAwareBasicRetryingFutureTest.java | 2 +- ...ontextAwareDirectRetryingExecutorTest.java | 2 +- ...extAwareScheduledRetryingExecutorTest.java | 18 ++++++++----- ...ntextAwareStreamingRetryAlgorithmTest.java | 2 +- .../api/gax/retrying/FailingCallable.java | 22 ++++++++++----- .../gax/rpc/ApiResultRetryAlgorithmTest.java | 19 ++++++------- .../com/google/api/gax/rpc/RetryingTest.java | 6 ++--- 19 files changed, 105 insertions(+), 75 deletions(-) diff --git a/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java index dbdc8fd5d..1b9b5c187 100644 --- a/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java +++ b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcCallContextTest.java @@ -30,6 +30,8 @@ package com.google.api.gax.grpc; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; @@ -76,9 +78,9 @@ public void testNullToSelfWrongType() { public void testWithCredentials() { Credentials credentials = Mockito.mock(Credentials.class); GrpcCallContext emptyContext = GrpcCallContext.createDefault(); - Truth.assertThat(emptyContext.getCallOptions().getCredentials()).isNull(); + assertNull(emptyContext.getCallOptions().getCredentials()); GrpcCallContext context = emptyContext.withCredentials(credentials); - Truth.assertThat(context.getCallOptions().getCredentials()).isNotNull(); + assertNotNull(context.getCallOptions().getCredentials()); } @Test @@ -127,21 +129,17 @@ public void testWithRequestParamsDynamicHeaderOption() { @Test public void testWithTimeout() { - Truth.assertThat(GrpcCallContext.createDefault().withTimeout(null).getTimeout()).isNull(); + assertNull(GrpcCallContext.createDefault().withTimeout(null).getTimeout()); } @Test public void testWithNegativeTimeout() { - Truth.assertThat( - GrpcCallContext.createDefault().withTimeout(Duration.ofSeconds(-1L)).getTimeout()) - .isNull(); + assertNull(GrpcCallContext.createDefault().withTimeout(Duration.ofSeconds(-1L)).getTimeout()); } @Test public void testWithZeroTimeout() { - Truth.assertThat( - GrpcCallContext.createDefault().withTimeout(Duration.ofSeconds(0L)).getTimeout()) - .isNull(); + assertNull(GrpcCallContext.createDefault().withTimeout(Duration.ofSeconds(0L)).getTimeout()); } @Test @@ -342,18 +340,18 @@ public void testMergeWithTracer() { public void testWithRetrySettings() { RetrySettings retrySettings = Mockito.mock(RetrySettings.class); GrpcCallContext emptyContext = GrpcCallContext.createDefault(); - Truth.assertThat(emptyContext.getRetrySettings()).isNull(); + assertNull(emptyContext.getRetrySettings()); GrpcCallContext context = emptyContext.withRetrySettings(retrySettings); - Truth.assertThat(context.getRetrySettings()).isNotNull(); + assertNotNull(context.getRetrySettings()); } @Test public void testWithRetryableCodes() { Set codes = Collections.singleton(StatusCode.Code.UNAVAILABLE); GrpcCallContext emptyContext = GrpcCallContext.createDefault(); - Truth.assertThat(emptyContext.getRetryableCodes()).isNull(); + assertNull(emptyContext.getRetryableCodes()); GrpcCallContext context = emptyContext.withRetryableCodes(codes); - Truth.assertThat(context.getRetryableCodes()).isNotNull(); + assertNotNull(context.getRetryableCodes()); } private static Map> createTestExtraHeaders(String... keyValues) { diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java index 2b2a59ed9..452ccbb44 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java @@ -29,6 +29,9 @@ */ package com.google.api.gax.httpjson; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.testing.FakeCallContext; @@ -65,9 +68,9 @@ public void testNullToSelfWrongType() { public void testWithCredentials() { Credentials credentials = Mockito.mock(Credentials.class); HttpJsonCallContext emptyContext = HttpJsonCallContext.createDefault(); - Truth.assertThat(emptyContext.getCredentials()).isNull(); + assertNull(emptyContext.getCredentials()); HttpJsonCallContext context = emptyContext.withCredentials(credentials); - Truth.assertThat(context.getCredentials()).isNotNull(); + assertNotNull(context.getCredentials()); } @Test @@ -107,21 +110,19 @@ public void testMergeWrongType() { @Test public void testWithTimeout() { - Truth.assertThat(HttpJsonCallContext.createDefault().withTimeout(null).getTimeout()).isNull(); + assertNull(HttpJsonCallContext.createDefault().withTimeout(null).getTimeout()); } @Test public void testWithNegativeTimeout() { - Truth.assertThat( - HttpJsonCallContext.createDefault().withTimeout(Duration.ofSeconds(-1L)).getTimeout()) - .isNull(); + assertNull( + HttpJsonCallContext.createDefault().withTimeout(Duration.ofSeconds(-1L)).getTimeout()); } @Test public void testWithZeroTimeout() { - Truth.assertThat( - HttpJsonCallContext.createDefault().withTimeout(Duration.ofSeconds(0L)).getTimeout()) - .isNull(); + assertNull( + HttpJsonCallContext.createDefault().withTimeout(Duration.ofSeconds(0L)).getTimeout()); } @Test @@ -199,17 +200,17 @@ public void testMergeWithTracer() { public void testWithRetrySettings() { RetrySettings retrySettings = Mockito.mock(RetrySettings.class); HttpJsonCallContext emptyContext = HttpJsonCallContext.createDefault(); - Truth.assertThat(emptyContext.getRetrySettings()).isNull(); + assertNull(emptyContext.getRetrySettings()); HttpJsonCallContext context = emptyContext.withRetrySettings(retrySettings); - Truth.assertThat(context.getRetrySettings()).isNotNull(); + assertNotNull(context.getRetrySettings()); } @Test public void testWithRetryableCodes() { Set codes = Collections.singleton(StatusCode.Code.UNAVAILABLE); HttpJsonCallContext emptyContext = HttpJsonCallContext.createDefault(); - Truth.assertThat(emptyContext.getRetryableCodes()).isNull(); + assertNull(emptyContext.getRetryableCodes()); HttpJsonCallContext context = emptyContext.withRetryableCodes(codes); - Truth.assertThat(context.getRetryableCodes()).isNotNull(); + assertNotNull(context.getRetryableCodes()); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java index d4fc3adf9..094b2a067 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -32,8 +32,8 @@ import java.util.concurrent.Callable; /** - * {@link BasicRetryingFuture} implementation that will use {@link RetrySettings} and retryable - * codes from the {@link RetryingContext} if they have been set. + * {@link BasicRetryingFuture} implementation that uses {@link RetrySettings} and retryable codes + * from the {@link RetryingContext} if they are set. */ class ContextAwareBasicRetryingFuture extends BasicRetryingFuture { diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java index b19bb915a..d69d2811b 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java index 581a248ce..a9f6e2579 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java index 708fcb0ba..247bf7152 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java index e231999a8..3bc04268f 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -33,9 +33,9 @@ import java.util.concurrent.CancellationException; /** - * The retry algorithm, which makes decision based either on the thrown exception or the returned - * response, the execution time settings of the previous attempt, and the {@link RetrySettings} and - * retryable codes supplied by a {@link RetryingContext}. + * The retry algorithm, which decides based either on the thrown exception or the returned response, + * the execution time settings of the previous attempt, and the {@link RetrySettings} and retryable + * codes supplied by a {@link RetryingContext}. * *

This class is thread-safe. * @@ -125,13 +125,13 @@ public boolean shouldRetry( } @Override - @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + @BetaApi("Surface for inspecting a RetryAlgorithm is not yet stable") public ContextAwareResultRetryAlgorithm getResultAlgorithm() { return (ContextAwareResultRetryAlgorithm) super.getResultAlgorithm(); } @Override - @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + @BetaApi("Surface for inspecting a RetryAlgorithm is not yet stable") public ContextAwareTimedRetryAlgorithm getTimedAlgorithm() { return (ContextAwareTimedRetryAlgorithm) super.getTimedAlgorithm(); } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java index b62cb8df0..f052c9761 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -41,7 +41,7 @@ * pattern there are usually multiple instances of this class sharing same instance of the * underlying {@link ScheduledExecutorService}. * - *

The executor will use a {@link ContextAwareRetryAlgorithm} to create attempt settings and to + *

The executor uses a {@link ContextAwareRetryAlgorithm} to create attempt settings and to * determine whether to retry an attempt. * *

This class is thread-safe. diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java index 9422a0138..f087d24e4 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -33,7 +33,7 @@ import java.util.concurrent.CancellationException; /** - * The streaming retry algorithm, which makes decision based either on the thrown exception and the + * The streaming retry algorithm, which decides based either on the thrown exception and the * execution time settings of the previous attempt. This extends {@link RetryAlgorithm} to take * additional information (provided by {@code ServerStreamingAttemptCallable}) into account. * @@ -83,7 +83,7 @@ public TimedAttemptSettings createNextAttempt( /** * {@inheritDoc} * - *

Ensures retries are only scheduled if the {@link StreamResumptionStrategy} in the {@code + *

Schedules retries only if the {@link StreamResumptionStrategy} in the {@code * ServerStreamingAttemptCallable} supports it. */ @Override diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java index fc6ac6d5c..455562376 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,13 +35,13 @@ * A timed retry algorithm is responsible for the following operations, based on the previous * attempt settings, {@link RetryingContext} and current time: * - *

    + *
      *
    • Creating first attempt {@link TimedAttemptSettings}. *
    • Accepting a task for retry so another attempt will be made. *
    • Canceling retrying process so the related {@link java.util.concurrent.Future} will be * canceled. *
    • Creating {@link TimedAttemptSettings} for each subsequent retry attempt. - *
+ * * * Implementations of this interface must be be thread-safe. */ diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index 6f115075d..bbec19d6a 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -194,7 +194,12 @@ public interface ApiCallContext extends RetryingContext { */ ApiCallContext withRetrySettings(RetrySettings retrySettings); - /** The retryable codes that were previously set for this context. */ + /** + * Returns the retryable codes that were previously set for this context. + * + *

This is null if the default retryable codes should be used, and otherwise the + * codes that should be used for retrying. An empty set means that retrying should be disabled. + */ @Nullable Set getRetryableCodes(); diff --git a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java index 8eccba04a..a71611d60 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java @@ -47,6 +47,7 @@ import com.google.api.gax.retrying.FailingCallable.CustomException; import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.tracing.ApiTracer; +import com.google.common.base.Stopwatch; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -77,6 +78,16 @@ protected abstract RetryAlgorithm getAlgorithm( protected abstract RetrySettings getDefaultRetrySettings(); + protected void busyWaitForInitialResult(RetryingFuture future, Duration timeout) + throws TimeoutException { + Stopwatch watch = Stopwatch.createStarted(); + while (future.peekAttemptResult() == null) { + if (watch.elapsed(TimeUnit.NANOSECONDS) > timeout.toNanos()) { + throw new TimeoutException(); + } + } + } + @Before public void setUp() { retryingContext = FakeCallContext.createDefault().withTracer(tracer); diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java index 694c862ed..7aecb8913 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java index 1ef43406d..f1314c0ca 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java index df96e3486..fead0bbcc 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -48,6 +48,7 @@ import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -237,7 +238,7 @@ public void testCancelGetAttempt() throws Exception { CustomException exception; CancellationException cancellationException = null; int checks = 0; - int failedCancelations = 0; + int failedCancellations = 0; do { exception = null; checks++; @@ -252,7 +253,7 @@ public void testCancelGetAttempt() throws Exception { } assertTrue(attemptResult.isDone()); if (!future.cancel(true)) { - failedCancelations++; + failedCancellations++; } } while (exception != null && checks < maxRetries); @@ -261,7 +262,7 @@ public void testCancelGetAttempt() throws Exception { // future.cancel(true) may return false sometimes, which is ok. Also, the every cancellation // of // an already cancelled future should return false (this is what -1 means here) - assertEquals(2, checks - (failedCancelations - 1)); + assertEquals(2, checks - (failedCancellations - 1)); assertTrue(future.getAttemptSettings().getAttemptCount() > 0); assertFutureCancel(future); localExecutor.shutdownNow(); @@ -317,7 +318,9 @@ public void testCancelIsTraced() throws Exception { FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); future.setAttemptFuture(executor.submit(future)); - Thread.sleep(30L); + // Wait until the result has been returned to the retrying future. + callable.getFirstAttemptFinishedLatch().await(100, TimeUnit.MILLISECONDS); + busyWaitForInitialResult(future, Duration.ofMillis(100)); boolean res = future.cancel(false); assertTrue(res); @@ -336,7 +339,6 @@ public void testCancelProxiedFutureAfterStart() throws Exception { RetrySettings retrySettings = FAST_RETRY_SETTINGS .toBuilder() - .setInitialRetryDelay(Duration.ofMillis(1_000L)) .setMaxRetryDelay(Duration.ofMillis(1_000L)) .setTotalTimeout(Duration.ofMillis(10_0000L)) .build(); @@ -348,7 +350,9 @@ public void testCancelProxiedFutureAfterStart() throws Exception { FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); future.setAttemptFuture(executor.submit(future)); - Thread.sleep(50L); + // Wait until the result has been returned to the retrying future. + callable.getFirstAttemptFinishedLatch().await(100, TimeUnit.MILLISECONDS); + busyWaitForInitialResult(future, Duration.ofMillis(100)); // Note that shutdownNow() will not cancel internal FutureTasks automatically, which // may potentially cause another thread handing on RetryingFuture#get() call forever. diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java index 1afe2e84f..f8a4e5c47 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java b/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java index 4def04807..b9aff94c5 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java +++ b/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java @@ -31,6 +31,7 @@ import com.google.api.gax.tracing.ApiTracer; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import org.threeten.bp.Duration; @@ -64,6 +65,7 @@ class FailingCallable implements Callable { private final ApiTracer tracer; private final int expectedFailuresCount; private final String result; + private final CountDownLatch firstAttemptFinished = new CountDownLatch(1); FailingCallable(int expectedFailuresCount, String result, ApiTracer tracer) { this.tracer = tracer; @@ -71,17 +73,25 @@ class FailingCallable implements Callable { this.result = result; } + CountDownLatch getFirstAttemptFinishedLatch() { + return firstAttemptFinished; + } + @Override public String call() throws Exception { - int attemptNumber = attemptsCount.getAndIncrement(); + try { + int attemptNumber = attemptsCount.getAndIncrement(); - tracer.attemptStarted(attemptNumber); + tracer.attemptStarted(attemptNumber); - if (attemptNumber < expectedFailuresCount) { - throw new CustomException(); - } + if (attemptNumber < expectedFailuresCount) { + throw new CustomException(); + } - return result; + return result; + } finally { + firstAttemptFinished.countDown(); + } } static class CustomException extends RuntimeException { diff --git a/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java index 8830b4700..3f98cd884 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -29,7 +29,8 @@ */ package com.google.api.gax.rpc; -import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -51,8 +52,8 @@ public void testShouldRetryNoContext() { when(retryable.isRetryable()).thenReturn(true); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); - assertThat(algorithm.shouldRetry(nonRetryable, null)).isFalse(); - assertThat(algorithm.shouldRetry(retryable, null)).isTrue(); + assertFalse(algorithm.shouldRetry(nonRetryable, null)); + assertTrue(algorithm.shouldRetry(retryable, null)); } @Test @@ -73,8 +74,8 @@ public void testShouldRetryWithContextWithoutRetryableCodes() { when(retryable.getStatusCode()).thenReturn(unavailable); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); - assertThat(algorithm.shouldRetry(context, nonRetryable, null)).isFalse(); - assertThat(algorithm.shouldRetry(context, retryable, null)).isTrue(); + assertFalse(algorithm.shouldRetry(context, nonRetryable, null)); + assertTrue(algorithm.shouldRetry(context, retryable, null)); } @Test @@ -99,8 +100,8 @@ public void testShouldRetryWithContextWithRetryableCodes() { when(dataLossException.getStatusCode()).thenReturn(dataLoss); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); - assertThat(algorithm.shouldRetry(context, unavailableException, null)).isTrue(); - assertThat(algorithm.shouldRetry(context, dataLossException, null)).isFalse(); + assertTrue(algorithm.shouldRetry(context, unavailableException, null)); + assertFalse(algorithm.shouldRetry(context, dataLossException, null)); } @Test @@ -117,6 +118,6 @@ public void testShouldRetryWithContextWithEmptyRetryableCodes() { when(unavailableException.getStatusCode()).thenReturn(unavailable); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); - assertThat(algorithm.shouldRetry(context, unavailableException, null)).isFalse(); + assertFalse(algorithm.shouldRetry(context, unavailableException, null)); } } diff --git a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java index 8b1280209..30198bcb7 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/RetryingTest.java @@ -29,7 +29,7 @@ */ package com.google.api.gax.rpc; -import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.api.core.ApiFuture; @@ -182,7 +182,7 @@ public void retryUsingContextTotalTimeoutExceeded() { .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); fail("missing expected exception"); } catch (ApiException e) { - assertThat(e.getStatusCode().getCode()).isEqualTo(Code.INTERNAL); + assertEquals(Code.INTERNAL, e.getStatusCode().getCode()); } } @@ -215,7 +215,7 @@ public void retryUsingContextMaxAttemptsExceeded() { .withRetryableCodes(Sets.newSet(StatusCode.Code.INTERNAL))); fail("missing expected exception"); } catch (ApiException e) { - assertThat(e.getStatusCode().getCode()).isEqualTo(Code.INTERNAL); + assertEquals(Code.INTERNAL, e.getStatusCode().getCode()); } } From 80230e14218370deb46a50794c3952768c2af8cb Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Thu, 7 Jan 2021 12:33:32 +0100 Subject: [PATCH 05/23] fix: missed one Thread.sleep --- .../retrying/ContextAwareScheduledRetryingExecutorTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java index fead0bbcc..7ef7a5b47 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java @@ -289,7 +289,9 @@ public void testCancelOuterFutureAfterStart() throws Exception { FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); future.setAttemptFuture(executor.submit(future)); - Thread.sleep(30L); + // Wait until the result has been returned to the retrying future. + callable.getFirstAttemptFinishedLatch().await(100, TimeUnit.MILLISECONDS); + busyWaitForInitialResult(future, Duration.ofMillis(100)); boolean res = future.cancel(false); assertTrue(res); From 8ef6e6f5fde2e845e35fa0914a6d51ac8a9ba3dd Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Thu, 7 Jan 2021 14:00:42 +0100 Subject: [PATCH 06/23] fix: fix style --- .../retrying/BasicResultRetryAlgorithm.java | 48 ++++++++++--------- .../ContextAwareResultRetryAlgorithm.java | 19 ++++---- .../retrying/ContextAwareRetryAlgorithm.java | 33 ++++++------- .../ContextAwareStreamingRetryAlgorithm.java | 32 ++++++------- .../ContextAwareTimedRetryAlgorithm.java | 4 +- .../retrying/ExponentialRetryAlgorithm.java | 28 +++++------ .../api/gax/rpc/ApiResultRetryAlgorithm.java | 15 +++--- 7 files changed, 92 insertions(+), 87 deletions(-) diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index b41b3f19d..c7a1b8bb0 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -44,13 +44,15 @@ public class BasicResultRetryAlgorithm * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. * - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt - * @param prevSettings previous attempt settings + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt + * @param previousSettings previous attempt settings */ @Override public TimedAttemptSettings createNextAttempt( - Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { return null; } @@ -58,42 +60,42 @@ public TimedAttemptSettings createNextAttempt( * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. * - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt - * @param prevSettings previous attempt settings + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt + * @param previousSettings previous attempt settings */ @Override public TimedAttemptSettings createNextAttempt( RetryingContext context, - Throwable prevThrowable, - ResponseT prevResponse, - TimedAttemptSettings prevSettings) { - return createNextAttempt(prevThrowable, prevResponse, prevSettings); + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + return createNextAttempt(previousThrowable, previousResponse, previousSettings); } /** - * Returns {@code true} if an exception was thrown ({@code prevThrowable != null}), {@code false} - * otherwise. + * Returns {@code true} if an exception was thrown ({@code previousThrowable != null}), {@code + * false} otherwise. * - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt */ @Override - public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) { - return prevThrowable != null; + public boolean shouldRetry(Throwable previousThrowable, ResponseT previousResponse) { + return previousThrowable != null; } /** - * Returns {@code true} if an exception was thrown ({@code prevThrowable != null}), {@code false} - * otherwise. + * Returns {@code true} if an exception was thrown ({@code previousThrowable != null}), {@code + * false} otherwise. * - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt */ @Override public boolean shouldRetry( - RetryingContext context, Throwable prevThrowable, ResponseT prevResponse) + RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) throws CancellationException { - return shouldRetry(prevThrowable, prevResponse); + return shouldRetry(previousThrowable, previousResponse); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java index 247bf7152..642040ec1 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java @@ -40,27 +40,28 @@ public interface ContextAwareResultRetryAlgorithm /** * Creates a next attempt {@link TimedAttemptSettings}. * - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt - * @param prevSettings previous attempt settings + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt + * @param previousSettings previous attempt settings * @return next attempt settings or {@code null}, if the implementing algorithm does not provide * specific settings for the next attempt */ TimedAttemptSettings createNextAttempt( RetryingContext context, - Throwable prevThrowable, - ResponseT prevResponse, - TimedAttemptSettings prevSettings); + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings); /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context the {@link RetryingContext} that may be used to determine whether the call * should be retried - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt * @throws CancellationException if the retrying process should be canceled */ - boolean shouldRetry(RetryingContext context, Throwable prevThrowable, ResponseT prevResponse) + boolean shouldRetry( + RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) throws CancellationException; } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java index 3bc04268f..0cdabe059 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java @@ -73,28 +73,29 @@ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { * * @param context the {@link RetryingContext} that can be used to determine the {@link * RetrySettings} for the next attempt - * @param prevThrowable exception thrown by the previous attempt or null if a result was returned - * instead - * @param prevResponse response returned by the previous attempt or null if an exception was + * @param previousThrowable exception thrown by the previous attempt or null if a result was + * returned instead + * @param previousResponse response returned by the previous attempt or null if an exception was * thrown instead - * @param prevSettings previous attempt settings + * @param previousSettings previous attempt settings * @return next attempt settings, can be {@code null}, if no there should be no new attempt */ public TimedAttemptSettings createNextAttempt( RetryingContext context, - Throwable prevThrowable, - ResponseT prevResponse, - TimedAttemptSettings prevSettings) { + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { // a small optimization, which allows to avoid calling relatively heavy methods // like timedAlgorithm.createNextAttempt(), when it is not necessary. - if (!getResultAlgorithm().shouldRetry(context, prevThrowable, prevResponse)) { + if (!getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse)) { return null; } TimedAttemptSettings newSettings = - getResultAlgorithm().createNextAttempt(context, prevThrowable, prevResponse, prevSettings); + getResultAlgorithm() + .createNextAttempt(context, previousThrowable, previousResponse, previousSettings); if (newSettings == null) { - newSettings = getTimedAlgorithm().createNextAttempt(context, prevSettings); + newSettings = getTimedAlgorithm().createNextAttempt(context, previousSettings); } return newSettings; } @@ -104,9 +105,9 @@ public TimedAttemptSettings createNextAttempt( * * @param context the {@link RetryingContext} that can be used to determine whether another * attempt should be made. - * @param prevThrowable exception thrown by the previous attempt or null if a result was returned - * instead - * @param prevResponse response returned by the previous attempt or null if an exception was + * @param previousThrowable exception thrown by the previous attempt or null if a result was + * returned instead + * @param previousResponse response returned by the previous attempt or null if an exception was * thrown instead * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted @@ -115,11 +116,11 @@ public TimedAttemptSettings createNextAttempt( */ public boolean shouldRetry( RetryingContext context, - Throwable prevThrowable, - ResponseT prevResponse, + Throwable previousThrowable, + ResponseT previousResponse, TimedAttemptSettings nextAttemptSettings) throws CancellationException { - return getResultAlgorithm().shouldRetry(context, prevThrowable, prevResponse) + return getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse) && nextAttemptSettings != null && getTimedAlgorithm().shouldRetry(context, nextAttemptSettings); } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java index f087d24e4..7b6984dc3 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java @@ -57,27 +57,27 @@ public ContextAwareStreamingRetryAlgorithm( */ public TimedAttemptSettings createNextAttempt( RetryingContext context, - Throwable prevThrowable, - ResponseT prevResponse, - TimedAttemptSettings prevSettings) { + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { - if (prevThrowable instanceof ServerStreamingAttemptException) { + if (previousThrowable instanceof ServerStreamingAttemptException) { ServerStreamingAttemptException attemptException = - (ServerStreamingAttemptException) prevThrowable; - prevThrowable = prevThrowable.getCause(); + (ServerStreamingAttemptException) previousThrowable; + previousThrowable = previousThrowable.getCause(); // If we have made progress in the last attempt, then reset the delays if (attemptException.hasSeenResponses()) { - prevSettings = + previousSettings = createFirstAttempt(context) .toBuilder() - .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos()) - .setOverallAttemptCount(prevSettings.getOverallAttemptCount()) + .setFirstAttemptStartTimeNanos(previousSettings.getFirstAttemptStartTimeNanos()) + .setOverallAttemptCount(previousSettings.getOverallAttemptCount()) .build(); } } - return super.createNextAttempt(context, prevThrowable, prevResponse, prevSettings); + return super.createNextAttempt(context, previousThrowable, previousResponse, previousSettings); } /** @@ -89,22 +89,22 @@ public TimedAttemptSettings createNextAttempt( @Override public boolean shouldRetry( RetryingContext context, - Throwable prevThrowable, - ResponseT prevResponse, + Throwable previousThrowable, + ResponseT previousResponse, TimedAttemptSettings nextAttemptSettings) throws CancellationException { // Unwrap - if (prevThrowable instanceof ServerStreamingAttemptException) { + if (previousThrowable instanceof ServerStreamingAttemptException) { ServerStreamingAttemptException attemptException = - (ServerStreamingAttemptException) prevThrowable; - prevThrowable = prevThrowable.getCause(); + (ServerStreamingAttemptException) previousThrowable; + previousThrowable = previousThrowable.getCause(); if (!attemptException.canResume()) { return false; } } - return super.shouldRetry(context, prevThrowable, prevResponse, nextAttemptSettings); + return super.shouldRetry(context, previousThrowable, previousResponse, nextAttemptSettings); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java index 455562376..5adea30dc 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java @@ -62,12 +62,12 @@ public interface ContextAwareTimedRetryAlgorithm extends TimedRetryAlgorithm { * * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and * retryable codes. - * @param prevSettings previous attempt settings + * @param previousSettings previous attempt settings * @return next attempt settings or {@code null} if the implementing algorithm does not provide * specific settings for the next attempt */ TimedAttemptSettings createNextAttempt( - RetryingContext context, TimedAttemptSettings prevSettings); + RetryingContext context, TimedAttemptSettings previousSettings); /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index 5ba2cc5f8..5e9c4010c 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -111,12 +111,12 @@ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { * attempt count and uses randomized exponential backoff factor for calculating next attempt * execution time. * - * @param prevSettings previous attempt settings + * @param previousSettings previous attempt settings * @return next attempt settings */ @Override - public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) { - RetrySettings settings = prevSettings.getGlobalSettings(); + public TimedAttemptSettings createNextAttempt(TimedAttemptSettings previousSettings) { + RetrySettings settings = previousSettings.getGlobalSettings(); // The retry delay is determined as follows: // attempt #0 - not used (initial attempt is always made immediately); @@ -124,9 +124,9 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) // attempt #2+ - use the calculated value (i.e. the following if statement is true only // if we are about to calculate the value for the upcoming 2nd+ attempt). long newRetryDelay = settings.getInitialRetryDelay().toMillis(); - if (prevSettings.getAttemptCount() > 0) { + if (previousSettings.getAttemptCount() > 0) { newRetryDelay = - (long) (settings.getRetryDelayMultiplier() * prevSettings.getRetryDelay().toMillis()); + (long) (settings.getRetryDelayMultiplier() * previousSettings.getRetryDelay().toMillis()); newRetryDelay = Math.min(newRetryDelay, settings.getMaxRetryDelay().toMillis()); } Duration randomDelay = Duration.ofMillis(nextRandomLong(newRetryDelay, settings.isJittered())); @@ -136,7 +136,7 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) // attempt #1+ - use the calculated value, or the time remaining in totalTimeout if the // calculated value would exceed the totalTimeout. long newRpcTimeout = - (long) (settings.getRpcTimeoutMultiplier() * prevSettings.getRpcTimeout().toMillis()); + (long) (settings.getRpcTimeoutMultiplier() * previousSettings.getRpcTimeout().toMillis()); newRpcTimeout = Math.min(newRpcTimeout, settings.getMaxRpcTimeout().toMillis()); // The totalTimeout could be zero if a callable is only using maxAttempts to limit retries. @@ -145,7 +145,7 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) if (!settings.getTotalTimeout().isZero()) { Duration timeElapsed = Duration.ofNanos(clock.nanoTime()) - .minus(Duration.ofNanos(prevSettings.getFirstAttemptStartTimeNanos())); + .minus(Duration.ofNanos(previousSettings.getFirstAttemptStartTimeNanos())); Duration timeLeft = settings.getTotalTimeout().minus(timeElapsed).minus(randomDelay); // If timeLeft at this point is < 0, the shouldRetry logic will prevent @@ -156,13 +156,13 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) } return TimedAttemptSettings.newBuilder() - .setGlobalSettings(prevSettings.getGlobalSettings()) + .setGlobalSettings(previousSettings.getGlobalSettings()) .setRetryDelay(Duration.ofMillis(newRetryDelay)) .setRpcTimeout(Duration.ofMillis(newRpcTimeout)) .setRandomizedRetryDelay(randomDelay) - .setAttemptCount(prevSettings.getAttemptCount() + 1) - .setOverallAttemptCount(prevSettings.getOverallAttemptCount() + 1) - .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos()) + .setAttemptCount(previousSettings.getAttemptCount() + 1) + .setOverallAttemptCount(previousSettings.getOverallAttemptCount() + 1) + .setFirstAttemptStartTimeNanos(previousSettings.getFirstAttemptStartTimeNanos()) .build(); } @@ -173,15 +173,15 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and * retryable codes. - * @param prevSettings previous attempt settings + * @param previousSettings previous attempt settings * @return next attempt settings */ @Override public TimedAttemptSettings createNextAttempt( - RetryingContext context, TimedAttemptSettings prevSettings) { + RetryingContext context, TimedAttemptSettings previousSettings) { // The RetrySettings from the context are not used here, as they have already been set as the // global settings during the creation of the initial attempt. - return createNextAttempt(prevSettings); + return createNextAttempt(previousSettings); } /** diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java index ad33e7c8b..486584840 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java @@ -35,10 +35,11 @@ /* Package-private for internal use. */ class ApiResultRetryAlgorithm extends BasicResultRetryAlgorithm { - /** Returns true if prevThrowable is an {@link ApiException} that is retryable. */ + /** Returns true if previousThrowable is an {@link ApiException} that is retryable. */ @Override - public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) { - return (prevThrowable instanceof ApiException) && ((ApiException) prevThrowable).isRetryable(); + public boolean shouldRetry(Throwable previousThrowable, ResponseT previousResponse) { + return (previousThrowable instanceof ApiException) + && ((ApiException) previousThrowable).isRetryable(); } /** @@ -49,15 +50,15 @@ public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) { */ @Override public boolean shouldRetry( - RetryingContext context, Throwable prevThrowable, ResponseT prevResponse) { + RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) { if (context.getRetryableCodes() != null) { // Ignore the isRetryable() value of the throwable if the RetryingContext has a specific list // of codes that should be retried. - return (prevThrowable instanceof ApiException) + return (previousThrowable instanceof ApiException) && context .getRetryableCodes() - .contains(((ApiException) prevThrowable).getStatusCode().getCode()); + .contains(((ApiException) previousThrowable).getStatusCode().getCode()); } - return shouldRetry(prevThrowable, prevResponse); + return shouldRetry(previousThrowable, previousResponse); } } From 111f8f5721ea11cb535f5f752c92b6df707f0533 Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Fri, 5 Feb 2021 12:56:24 +0100 Subject: [PATCH 07/23] fix: address review comments --- .../google/api/gax/grpc/GrpcCallContext.java | 19 +++++---- .../api/gax/httpjson/HttpJsonCallContext.java | 17 ++++---- .../retrying/BasicResultRetryAlgorithm.java | 6 +-- .../ContextAwareDirectRetryingExecutor.java | 4 +- .../ContextAwareResultRetryAlgorithm.java | 2 +- .../retrying/ContextAwareRetryAlgorithm.java | 22 +++++----- ...ContextAwareScheduledRetryingExecutor.java | 4 +- .../ContextAwareStreamingRetryAlgorithm.java | 6 +-- .../ContextAwareTimedRetryAlgorithm.java | 12 +++--- .../retrying/ExponentialRetryAlgorithm.java | 6 +-- .../api/gax/retrying/RetryAlgorithm.java | 6 +-- .../api/gax/retrying/RetryingContext.java | 4 +- .../google/api/gax/rpc/ApiCallContext.java | 26 ++++++------ .../api/gax/rpc/ApiResultRetryAlgorithm.java | 2 +- ...extAwareScheduledRetryingExecutorTest.java | 15 +++---- .../gax/rpc/ApiResultRetryAlgorithmTest.java | 41 +++++++------------ .../api/gax/rpc/StreamingCallableTest.java | 16 ++++---- .../google/api/gax/rpc/UnaryCallableTest.java | 29 +++++++------ 18 files changed, 117 insertions(+), 120 deletions(-) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index b663490e8..c2af8f980 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -29,7 +29,6 @@ */ package com.google.api.gax.grpc; -import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; @@ -39,6 +38,7 @@ import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.NoopApiTracer; import com.google.auth.Credentials; +import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -64,8 +64,11 @@ * GrpcCallContext itself or the underlying data. Methods of the form {@code withX}, such as {@link * #withTransportChannel}, return copies of the object, but with one field changed. The immutability * and thread safety of the arguments solely depends on the arguments themselves. + * + *

Applications should reference {@link ApiCallContext} instead - this class is likely to + * experience breaking changes. */ -@BetaApi("Reference ApiCallContext instead - this class is likely to experience breaking changes") +@Beta @InternalExtensionOnly public final class GrpcCallContext implements ApiCallContext { static final CallOptions.Key TRACER_KEY = Key.create("gax.tracer"); @@ -237,7 +240,7 @@ public GrpcCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeou retryableCodes); } - @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") + @Beta public GrpcCallContext withChannelAffinity(@Nullable Integer affinity) { return new GrpcCallContext( channel, @@ -251,7 +254,7 @@ public GrpcCallContext withChannelAffinity(@Nullable Integer affinity) { retryableCodes); } - @BetaApi("The surface for extra headers is not stable yet and may change in the future.") + @Beta @Override public GrpcCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); @@ -409,7 +412,7 @@ public CallOptions getCallOptions() { * * @see ApiCallContext#withStreamWaitTimeout(Duration) */ - @BetaApi("The surface for streaming is not stable yet and may change in the future.") + @Beta @Nullable public Duration getStreamWaitTimeout() { return streamWaitTimeout; @@ -420,21 +423,21 @@ public Duration getStreamWaitTimeout() { * * @see ApiCallContext#withStreamIdleTimeout(Duration) */ - @BetaApi("The surface for streaming is not stable yet and may change in the future.") + @Beta @Nullable public Duration getStreamIdleTimeout() { return streamIdleTimeout; } /** The channel affinity for this context. */ - @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") + @Beta @Nullable public Integer getChannelAffinity() { return channelAffinity; } /** The extra header for this context. */ - @BetaApi("The surface for extra headers is not stable yet and may change in the future.") + @Beta @Override public Map> getExtraHeaders() { return this.extraHeaders; diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index bc718a9e3..08a179caa 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -29,7 +29,6 @@ */ package com.google.api.gax.httpjson; -import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; @@ -39,6 +38,7 @@ import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.NoopApiTracer; import com.google.auth.Credentials; +import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -59,7 +59,7 @@ * copies of the object, but with one field changed. The immutability and thread safety of the * arguments solely depends on the arguments themselves. */ -@BetaApi +@Beta @InternalExtensionOnly public final class HttpJsonCallContext implements ApiCallContext { private final HttpJsonChannel channel; @@ -84,16 +84,17 @@ private HttpJsonCallContext( Credentials credentials, ImmutableMap> extraHeaders, ApiTracer tracer, - RetrySettings retrySettings, - Set retryableCodes) { + RetrySettings defaultRetrySettings, + Set defaultRetryableCodes) { this.channel = channel; this.timeout = timeout; this.deadline = deadline; this.credentials = credentials; this.extraHeaders = extraHeaders; this.tracer = tracer; - this.retrySettings = retrySettings; - this.retryableCodes = retryableCodes == null ? null : ImmutableSet.copyOf(retryableCodes); + this.retrySettings = defaultRetrySettings; + this.retryableCodes = + defaultRetryableCodes == null ? null : ImmutableSet.copyOf(defaultRetryableCodes); } /** @@ -254,7 +255,7 @@ public Duration getStreamIdleTimeout() { throw new UnsupportedOperationException("Http/json transport does not support streaming"); } - @BetaApi("The surface for extra headers is not stable yet and may change in the future.") + @Beta @Override public ApiCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); @@ -271,7 +272,7 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { this.retryableCodes); } - @BetaApi("The surface for extra headers is not stable yet and may change in the future.") + @Beta @Override public Map> getExtraHeaders() { return this.extraHeaders; diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index c7a1b8bb0..af0ed748e 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -32,9 +32,9 @@ import java.util.concurrent.CancellationException; /** - * A basic implementation of {@link ResultRetryAlgorithm}. Using this implementation would mean that - * all exceptions should be retried, all responses should be accepted (including {@code null}) and - * no retrying process should ever be canceled. + * A basic implementation of {@link ResultRetryAlgorithm}. Using this implementation means that all + * exceptions should be retried, all responses should be accepted (including {@code null}) and no + * retrying process should ever be cancelled. * * @param attempt response type */ diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java index a9f6e2579..0a2a2bb4c 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java @@ -32,8 +32,8 @@ import java.util.concurrent.Callable; /** - * {@link DirectRetryingExecutor} implementation that will use {@link RetrySettings} and retryable - * codes from the {@link RetryingContext} if they have been set. + * {@link DirectRetryingExecutor} implementation that uses {@link RetrySettings} and retryable codes + * from the {@link RetryingContext} if they have been set. */ public class ContextAwareDirectRetryingExecutor extends DirectRetryingExecutor { diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java index 642040ec1..7fa659435 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java @@ -59,7 +59,7 @@ TimedAttemptSettings createNextAttempt( * should be retried * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) * @param previousResponse response returned by the previous attempt - * @throws CancellationException if the retrying process should be canceled + * @throws CancellationException if the retrying process should be cancelled */ boolean shouldRetry( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java index 0cdabe059..d9a915f4a 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java @@ -29,13 +29,13 @@ */ package com.google.api.gax.retrying; -import com.google.api.core.BetaApi; +import com.google.common.annotations.Beta; import java.util.concurrent.CancellationException; /** - * The retry algorithm, which decides based either on the thrown exception or the returned response, - * the execution time settings of the previous attempt, and the {@link RetrySettings} and retryable - * codes supplied by a {@link RetryingContext}. + * Retry algorithm that decides whether to retry based either on the thrown exception or the + * returned response, the execution time settings of the previous attempt, and the {@link + * RetrySettings} and retryable codes supplied by a {@link RetryingContext}. * *

This class is thread-safe. * @@ -60,7 +60,7 @@ public ContextAwareRetryAlgorithm( * Creates a first attempt {@link TimedAttemptSettings}. * * @param context the {@link RetryingContext} that can be used to get the initial {@link - * RetrySettings}. + * RetrySettings} * @return first attempt settings */ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { @@ -78,14 +78,14 @@ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { * @param previousResponse response returned by the previous attempt or null if an exception was * thrown instead * @param previousSettings previous attempt settings - * @return next attempt settings, can be {@code null}, if no there should be no new attempt + * @return next attempt settings, can be {@code null}, if there should be no new attempt */ public TimedAttemptSettings createNextAttempt( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse, TimedAttemptSettings previousSettings) { - // a small optimization, which allows to avoid calling relatively heavy methods + // a small optimization that avoids calling relatively heavy methods // like timedAlgorithm.createNextAttempt(), when it is not necessary. if (!getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse)) { return null; @@ -104,14 +104,14 @@ public TimedAttemptSettings createNextAttempt( * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context the {@link RetryingContext} that can be used to determine whether another - * attempt should be made. + * attempt should be made * @param previousThrowable exception thrown by the previous attempt or null if a result was * returned instead * @param previousResponse response returned by the previous attempt or null if an exception was * thrown instead * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted - * @throws CancellationException if the retrying process should be canceled + * @throws CancellationException if the retrying process should be cancelled * @return {@code true} if another attempt should be made, or {@code false} otherwise */ public boolean shouldRetry( @@ -126,13 +126,13 @@ public boolean shouldRetry( } @Override - @BetaApi("Surface for inspecting a RetryAlgorithm is not yet stable") + @Beta public ContextAwareResultRetryAlgorithm getResultAlgorithm() { return (ContextAwareResultRetryAlgorithm) super.getResultAlgorithm(); } @Override - @BetaApi("Surface for inspecting a RetryAlgorithm is not yet stable") + @Beta public ContextAwareTimedRetryAlgorithm getTimedAlgorithm() { return (ContextAwareTimedRetryAlgorithm) super.getTimedAlgorithm(); } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java index f052c9761..3a5f6b2ef 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java @@ -33,12 +33,12 @@ import java.util.concurrent.ScheduledExecutorService; /** - * The retry executor which uses {@link ScheduledExecutorService} to schedule an attempt tasks. + * Retry executor that uses {@link ScheduledExecutorService} to schedule attempt tasks. * *

This implementation does not manage the lifecycle of the underlying {@link * ScheduledExecutorService}, so it should be managed outside of this class (like calling the {@link * ScheduledExecutorService#shutdown()} when the pool is not needed anymore). In a typical usage - * pattern there are usually multiple instances of this class sharing same instance of the + * pattern there are usually multiple instances of this class sharing the same instance of the * underlying {@link ScheduledExecutorService}. * *

The executor uses a {@link ContextAwareRetryAlgorithm} to create attempt settings and to diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java index 7b6984dc3..5480b9a72 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java @@ -33,9 +33,9 @@ import java.util.concurrent.CancellationException; /** - * The streaming retry algorithm, which decides based either on the thrown exception and the - * execution time settings of the previous attempt. This extends {@link RetryAlgorithm} to take - * additional information (provided by {@code ServerStreamingAttemptCallable}) into account. + * Streaming retry algorithm that decides based either on the thrown exception and the execution + * time settings of the previous attempt. This extends {@link RetryAlgorithm} to take additional + * information (provided by {@code ServerStreamingAttemptCallable}) into account. * *

This class is thread-safe. * diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java index 5adea30dc..3436bb860 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java @@ -33,13 +33,13 @@ /** * A timed retry algorithm is responsible for the following operations, based on the previous - * attempt settings, {@link RetryingContext} and current time: + * attempt settings, {@link RetryingContext}, and current time: * *

    *
  • Creating first attempt {@link TimedAttemptSettings}. *
  • Accepting a task for retry so another attempt will be made. *
  • Canceling retrying process so the related {@link java.util.concurrent.Future} will be - * canceled. + * cancelled. *
  • Creating {@link TimedAttemptSettings} for each subsequent retry attempt. *
* @@ -51,7 +51,7 @@ public interface ContextAwareTimedRetryAlgorithm extends TimedRetryAlgorithm { * Creates a first attempt {@link TimedAttemptSettings}. * * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes. + * retryable codes * @return first attempt settings */ TimedAttemptSettings createFirstAttempt(RetryingContext context); @@ -61,7 +61,7 @@ public interface ContextAwareTimedRetryAlgorithm extends TimedRetryAlgorithm { * attempt. * * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes. + * retryable codes * @param previousSettings previous attempt settings * @return next attempt settings or {@code null} if the implementing algorithm does not provide * specific settings for the next attempt @@ -73,10 +73,10 @@ TimedAttemptSettings createNextAttempt( * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes. + * retryable codes * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted - * @throws CancellationException if the retrying process should be canceled + * @throws CancellationException if the retrying process should be cancelled */ boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) throws CancellationException; diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index 5e9c4010c..772134647 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -82,7 +82,7 @@ public TimedAttemptSettings createFirstAttempt() { * executed immediately. * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and - * retryable codes. + * retryable codes * @return first attempt settings */ @Override @@ -172,7 +172,7 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings previousSetti * execution time. * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and - * retryable codes. + * retryable codes * @param previousSettings previous attempt settings * @return next attempt settings */ @@ -236,7 +236,7 @@ public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and - * retryable codes. + * retryable codes * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index d06c46039..e2727b900 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -31,7 +31,7 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.api.core.BetaApi; +import com.google.common.annotations.Beta; import java.util.concurrent.CancellationException; /** @@ -116,12 +116,12 @@ public boolean shouldRetry( && timedAlgorithm.shouldRetry(nextAttemptSettings); } - @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + @Beta public ResultRetryAlgorithm getResultAlgorithm() { return resultAlgorithm; } - @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + @Beta public TimedRetryAlgorithm getTimedAlgorithm() { return timedAlgorithm; } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java index 6db53c4e2..68f094173 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java @@ -29,9 +29,9 @@ */ package com.google.api.gax.retrying; -import com.google.api.core.BetaApi; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.tracing.ApiTracer; +import com.google.common.annotations.Beta; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -41,7 +41,7 @@ * *

It provides state to individual {@link RetryingFuture}s via the {@link RetryingExecutor}. */ -@BetaApi("The surface for passing per operation state is not yet stable") +@Beta public interface RetryingContext { /** Returns the {@link ApiTracer} associated with the current operation. */ @Nonnull diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index bbec19d6a..e1f6befcf 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -29,13 +29,13 @@ */ package com.google.api.gax.rpc; -import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.retrying.RetryingContext; import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.tracing.ApiTracer; import com.google.auth.Credentials; +import com.google.common.annotations.Beta; import java.util.List; import java.util.Map; import java.util.Set; @@ -93,7 +93,7 @@ public interface ApiCallContext extends RetryingContext { *

Please note that this timeout is best effort and the maximum resolution is configured in * {@link StubSettings#getStreamWatchdogCheckInterval()}. */ - @BetaApi("The surface for streaming is not stable yet and may change in the future.") + @Beta ApiCallContext withStreamWaitTimeout(@Nullable Duration streamWaitTimeout); /** @@ -101,7 +101,7 @@ public interface ApiCallContext extends RetryingContext { * * @see #withStreamWaitTimeout(Duration) */ - @BetaApi("The surface for streaming is not stable yet and may change in the future.") + @Beta @Nullable Duration getStreamWaitTimeout(); @@ -123,7 +123,7 @@ public interface ApiCallContext extends RetryingContext { *

Please note that this timeout is best effort and the maximum resolution is configured in * {@link StubSettings#getStreamWatchdogCheckInterval()}. */ - @BetaApi("The surface for streaming is not stable yet and may change in the future.") + @Beta ApiCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeout); /** @@ -131,7 +131,7 @@ public interface ApiCallContext extends RetryingContext { * * @see #withStreamIdleTimeout(Duration) */ - @BetaApi("The surface for streaming is not stable yet and may change in the future.") + @Beta @Nullable Duration getStreamIdleTimeout(); @@ -141,7 +141,7 @@ public interface ApiCallContext extends RetryingContext { *

The {@link ApiTracer} will be used to trace the current operation and to annotate various * events like retries. */ - @BetaApi("The surface for tracing is not stable yet and may change in the future") + @Beta @Nonnull ApiTracer getTracer(); @@ -153,7 +153,7 @@ public interface ApiCallContext extends RetryingContext { * * @param tracer the {@link ApiTracer} to set. */ - @BetaApi("The surface for tracing is not stable yet and may change in the future") + @Beta ApiCallContext withTracer(@Nonnull ApiTracer tracer); /** The {@link RetrySettings} that were previously set for this context. */ @@ -210,10 +210,10 @@ public interface ApiCallContext extends RetryingContext { * with either the default {@link RetrySettings} for the RPC, or the {@link RetrySettings} * supplied through {@link #withRetrySettings(RetrySettings)}. * - *

Setting a non-empty set of retryable codes for an RPC that is not already retryable, will - * not have any effect and the RPC will NOT be retried. This option can only be used to change - * which codes are considered retryable for an RPC that already has at least one retryable code in - * its default settings. + *

Setting a non-empty set of retryable codes for an RPC that is not already retryable by + * default, will not have any effect and the RPC will NOT be retried. This option can only be used + * to change which codes are considered retryable for an RPC that already has at least one + * retryable code in its default settings. */ ApiCallContext withRetryableCodes(Set retryableCodes); @@ -227,10 +227,10 @@ public interface ApiCallContext extends RetryingContext { ApiCallContext merge(ApiCallContext inputCallContext); /** Return a new ApiCallContext with the extraHeaders merged into the present instance. */ - @BetaApi("The surface for extra headers is not stable yet and may change in the future.") + @Beta ApiCallContext withExtraHeaders(Map> extraHeaders); /** Return the extra headers set for this context. */ - @BetaApi("The surface for extra headers is not stable yet and may change in the future.") + @Beta Map> getExtraHeaders(); } diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java index 486584840..688fc32cd 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiResultRetryAlgorithm.java @@ -44,7 +44,7 @@ public boolean shouldRetry(Throwable previousThrowable, ResponseT previousRespon /** * If {@link RetryingContext#getRetryableCodes()} is not null: Returns true if the status code of - * prevThrowable is in the list of retryable code of the {@link RetryingContext}. + * previousThrowable is in the list of retryable code of the {@link RetryingContext}. * *

Otherwise it returns the result of {@link #shouldRetry(Throwable, Object)}. */ diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java index 7ef7a5b47..c76840e11 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java @@ -259,9 +259,8 @@ public void testCancelGetAttempt() throws Exception { assertTrue(future.isDone()); assertNotNull(cancellationException); - // future.cancel(true) may return false sometimes, which is ok. Also, the every cancellation - // of - // an already cancelled future should return false (this is what -1 means here) + // future.cancel(true) may return false sometimes, which is ok. Also, every cancellation + // of an already cancelled future should return false (this is what -1 means here). assertEquals(2, checks - (failedCancellations - 1)); assertTrue(future.getAttemptSettings().getAttemptCount() > 0); assertFutureCancel(future); @@ -334,7 +333,7 @@ public void testCancelIsTraced() throws Exception { @Test public void testCancelProxiedFutureAfterStart() throws Exception { - // this is a heavy test, which takes a lot of time, so only few executions. + // This is a heavy test that takes a lot of time, so only few executions. for (int executionsCount = 0; executionsCount < 2; executionsCount++) { ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); FailingCallable callable = new FailingCallable(5, "SUCCESS", tracer); @@ -357,12 +356,10 @@ public void testCancelProxiedFutureAfterStart() throws Exception { busyWaitForInitialResult(future, Duration.ofMillis(100)); // Note that shutdownNow() will not cancel internal FutureTasks automatically, which - // may potentially cause another thread handing on RetryingFuture#get() call forever. + // may potentially cause another thread hanging on RetryingFuture#get() call forever. // Canceling the tasks returned by shutdownNow() also does not help, because of missing - // feature - // in guava's ListenableScheduledFuture, which does not cancel itself, when its delegate is - // canceled. - // So only the graceful shutdown() is supported properly. + // feature in guava's ListenableScheduledFuture, which does not cancel itself, when its + // delegate is cancelled. So only the graceful shutdown() is supported properly. localExecutor.shutdown(); assertFutureFail(future, RejectedExecutionException.class); diff --git a/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java index 3f98cd884..e1ef46dbd 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/ApiResultRetryAlgorithmTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import com.google.api.gax.rpc.StatusCode.Code; +import com.google.api.gax.rpc.testing.FakeStatusCode; import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,10 +47,10 @@ public class ApiResultRetryAlgorithmTest { @Test public void testShouldRetryNoContext() { - ApiException nonRetryable = mock(ApiException.class); - when(nonRetryable.isRetryable()).thenReturn(false); - ApiException retryable = mock(ApiException.class); - when(retryable.isRetryable()).thenReturn(true); + ApiException nonRetryable = + new ApiException(null, new FakeStatusCode(Code.INTERNAL), /* retryable = */ false); + ApiException retryable = + new ApiException(null, new FakeStatusCode(Code.UNAVAILABLE), /* retryable = */ true); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); assertFalse(algorithm.shouldRetry(nonRetryable, null)); @@ -63,15 +64,10 @@ public void testShouldRetryWithContextWithoutRetryableCodes() { // its default implementation. when(context.getRetryableCodes()).thenReturn(null); - StatusCode unavailable = mock(StatusCode.class); - when(unavailable.getCode()).thenReturn(Code.UNAVAILABLE); - ApiException nonRetryable = mock(ApiException.class); - when(nonRetryable.isRetryable()).thenReturn(false); - when(nonRetryable.getStatusCode()).thenReturn(unavailable); - - ApiException retryable = mock(ApiException.class); - when(retryable.isRetryable()).thenReturn(true); - when(retryable.getStatusCode()).thenReturn(unavailable); + ApiException nonRetryable = + new ApiException(null, new FakeStatusCode(Code.UNAVAILABLE), /* retryable = */ false); + ApiException retryable = + new ApiException(null, new FakeStatusCode(Code.UNAVAILABLE), /* retryable = */ true); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); assertFalse(algorithm.shouldRetry(context, nonRetryable, null)); @@ -89,15 +85,12 @@ public void testShouldRetryWithContextWithRetryableCodes() { StatusCode dataLoss = mock(StatusCode.class); when(dataLoss.getCode()).thenReturn(Code.DATA_LOSS); - ApiException unavailableException = mock(ApiException.class); // The return value of isRetryable() will be ignored, as UNAVAILABLE has been added as a // retryable code to the call context. - when(unavailableException.isRetryable()).thenReturn(false); - when(unavailableException.getStatusCode()).thenReturn(unavailable); - - ApiException dataLossException = mock(ApiException.class); - when(dataLossException.isRetryable()).thenReturn(true); - when(dataLossException.getStatusCode()).thenReturn(dataLoss); + ApiException unavailableException = + new ApiException(null, new FakeStatusCode(Code.UNAVAILABLE), /* retryable = */ false); + ApiException dataLossException = + new ApiException(null, new FakeStatusCode(Code.DATA_LOSS), /* retryable = */ true); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); assertTrue(algorithm.shouldRetry(context, unavailableException, null)); @@ -110,12 +103,8 @@ public void testShouldRetryWithContextWithEmptyRetryableCodes() { // This will effectively make the RPC non-retryable. when(context.getRetryableCodes()).thenReturn(Collections.emptySet()); - StatusCode unavailable = mock(StatusCode.class); - when(unavailable.getCode()).thenReturn(Code.UNAVAILABLE); - - ApiException unavailableException = mock(ApiException.class); - when(unavailableException.isRetryable()).thenReturn(true); - when(unavailableException.getStatusCode()).thenReturn(unavailable); + ApiException unavailableException = + new ApiException(null, new FakeStatusCode(Code.UNAVAILABLE), /* retryable = */ true); ApiResultRetryAlgorithm algorithm = new ApiResultRetryAlgorithm<>(); assertFalse(algorithm.shouldRetry(context, unavailableException, null)); diff --git a/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java b/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java index d56d46a45..f946aff92 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/StreamingCallableTest.java @@ -29,6 +29,8 @@ */ package com.google.api.gax.rpc; +import static org.junit.Assert.assertSame; + import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.testing.FakeCallContext; @@ -128,8 +130,8 @@ public void testClientStreamingCall() { ClientStreamingCallable callable = stashCallable.withDefaultCallContext(defaultCallContext); callable.clientStreamingCall(observer); - Truth.assertThat(stashCallable.getActualObserver()).isSameInstanceAs(observer); - Truth.assertThat(stashCallable.getContext()).isSameInstanceAs(defaultCallContext); + assertSame(observer, stashCallable.getActualObserver()); + assertSame(defaultCallContext, stashCallable.getContext()); } @Test @@ -155,11 +157,11 @@ public void testClientStreamingCallWithContext() { ClientStreamingCallable callable = stashCallable.withDefaultCallContext(FakeCallContext.createDefault()); callable.clientStreamingCall(observer, context); - Truth.assertThat(stashCallable.getActualObserver()).isSameInstanceAs(observer); + assertSame(observer, stashCallable.getActualObserver()); FakeCallContext actualContext = (FakeCallContext) stashCallable.getContext(); - Truth.assertThat(actualContext.getChannel()).isSameInstanceAs(channel); - Truth.assertThat(actualContext.getCredentials()).isSameInstanceAs(credentials); - Truth.assertThat(actualContext.getRetrySettings()).isSameInstanceAs(retrySettings); - Truth.assertThat(actualContext.getRetryableCodes()).containsExactlyElementsIn(retryableCodes); + assertSame(channel, actualContext.getChannel()); + assertSame(credentials, actualContext.getCredentials()); + assertSame(retrySettings, actualContext.getRetrySettings()); + assertSame(retryableCodes, actualContext.getRetryableCodes()); } } diff --git a/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java b/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java index 58342826b..0e16545d4 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/UnaryCallableTest.java @@ -29,13 +29,18 @@ */ package com.google.api.gax.rpc; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.rpc.testing.FakeChannel; import com.google.api.gax.rpc.testing.FakeSimpleApi.StashCallable; import com.google.auth.Credentials; import com.google.common.collect.ImmutableSet; -import com.google.common.truth.Truth; import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,10 +56,10 @@ public void simpleCall() throws Exception { StashCallable stashCallable = new StashCallable<>(1); Integer response = stashCallable.call(2, FakeCallContext.createDefault()); - Truth.assertThat(response).isEqualTo(Integer.valueOf(1)); + assertEquals(Integer.valueOf(1), response); FakeCallContext callContext = (FakeCallContext) stashCallable.getContext(); - Truth.assertThat(callContext.getChannel()).isNull(); - Truth.assertThat(callContext.getCredentials()).isNull(); + assertNull(callContext.getChannel()); + assertNull(callContext.getCredentials()); } @Test @@ -65,9 +70,9 @@ public void call() throws Exception { stashCallable.withDefaultCallContext(defaultCallContext); Integer response = callable.call(2); - Truth.assertThat(response).isEqualTo(Integer.valueOf(1)); - Truth.assertThat(stashCallable.getContext()).isNotNull(); - Truth.assertThat(stashCallable.getContext()).isSameInstanceAs(defaultCallContext); + assertEquals(Integer.valueOf(1), response); + assertNotNull(stashCallable.getContext()); + assertSame(defaultCallContext, stashCallable.getContext()); } @Test @@ -91,11 +96,11 @@ public void callWithContext() throws Exception { stashCallable.withDefaultCallContext(FakeCallContext.createDefault()); Integer response = callable.call(2, context); - Truth.assertThat(response).isEqualTo(Integer.valueOf(1)); + assertEquals(Integer.valueOf(1), response); FakeCallContext actualContext = (FakeCallContext) stashCallable.getContext(); - Truth.assertThat(actualContext.getChannel()).isSameInstanceAs(channel); - Truth.assertThat(actualContext.getCredentials()).isSameInstanceAs(credentials); - Truth.assertThat(actualContext.getRetrySettings()).isSameInstanceAs(retrySettings); - Truth.assertThat(actualContext.getRetryableCodes()).containsExactlyElementsIn(retryableCodes); + assertSame(channel, actualContext.getChannel()); + assertSame(credentials, actualContext.getCredentials()); + assertSame(retrySettings, actualContext.getRetrySettings()); + assertThat(actualContext.getRetryableCodes()).containsExactlyElementsIn(retryableCodes); } } From a58eb10ccf05e46a3e3f99630dbdce9d21388a78 Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Fri, 5 Feb 2021 14:57:45 +0100 Subject: [PATCH 08/23] refactor: remove ContextAwareResultRetryAlgorithm --- .../retrying/BasicResultRetryAlgorithm.java | 17 +++-- .../ContextAwareDirectRetryingExecutor.java | 6 +- .../ContextAwareResultRetryAlgorithm.java | 67 ------------------- .../retrying/ContextAwareRetryAlgorithm.java | 6 +- ...ContextAwareScheduledRetryingExecutor.java | 6 +- .../ContextAwareStreamingRetryAlgorithm.java | 2 +- .../gax/retrying/DirectRetryingExecutor.java | 5 ++ .../retrying/ScheduledRetryingExecutor.java | 5 ++ ...ntextAwareStreamingRetryAlgorithmTest.java | 34 +++------- 9 files changed, 38 insertions(+), 110 deletions(-) delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index af0ed748e..254feedc2 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -32,14 +32,15 @@ import java.util.concurrent.CancellationException; /** - * A basic implementation of {@link ResultRetryAlgorithm}. Using this implementation means that all - * exceptions should be retried, all responses should be accepted (including {@code null}) and no - * retrying process should ever be cancelled. + * A {@link ResultRetryAlgorithm} that can use a {@link RetryingContext} to determine whether to + * retry a call. + * + *

This implementation retries all exceptions, all responses are accepted (including {@code + * null}) and no retrying process will ever be cancelled. * * @param attempt response type */ -public class BasicResultRetryAlgorithm - implements ContextAwareResultRetryAlgorithm { +public class BasicResultRetryAlgorithm implements ResultRetryAlgorithm { /** * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. @@ -60,11 +61,12 @@ public TimedAttemptSettings createNextAttempt( * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. * + * @param context the retrying context of this invocation that can be used to determine the + * settings for the next attempt * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) * @param previousResponse response returned by the previous attempt * @param previousSettings previous attempt settings */ - @Override public TimedAttemptSettings createNextAttempt( RetryingContext context, Throwable previousThrowable, @@ -89,10 +91,11 @@ public boolean shouldRetry(Throwable previousThrowable, ResponseT previousRespon * Returns {@code true} if an exception was thrown ({@code previousThrowable != null}), {@code * false} otherwise. * + * @param context the retrying context of this invocation that can be used to determine whether + * the call should be retried * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) * @param previousResponse response returned by the previous attempt */ - @Override public boolean shouldRetry( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) throws CancellationException { diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java index 0a2a2bb4c..2bef86aa9 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java @@ -38,16 +38,14 @@ public class ContextAwareDirectRetryingExecutor extends DirectRetryingExecutor { - private final ContextAwareRetryAlgorithm retryAlgorithm; - public ContextAwareDirectRetryingExecutor(ContextAwareRetryAlgorithm retryAlgorithm) { super(retryAlgorithm); - this.retryAlgorithm = retryAlgorithm; } @Override public RetryingFuture createFuture( Callable callable, RetryingContext context) { - return new ContextAwareBasicRetryingFuture<>(callable, retryAlgorithm, context); + return new ContextAwareBasicRetryingFuture<>( + callable, (ContextAwareRetryAlgorithm) getRetryAlgorithm(), context); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java deleted file mode 100644 index 7fa659435..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareResultRetryAlgorithm.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.CancellationException; - -/** - * A {@link ResultRetryAlgorithm} that can use a {@link RetryingContext} to determine whether to - * retry a call. - */ -public interface ContextAwareResultRetryAlgorithm - extends ResultRetryAlgorithm { - /** - * Creates a next attempt {@link TimedAttemptSettings}. - * - * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse response returned by the previous attempt - * @param previousSettings previous attempt settings - * @return next attempt settings or {@code null}, if the implementing algorithm does not provide - * specific settings for the next attempt - */ - TimedAttemptSettings createNextAttempt( - RetryingContext context, - Throwable previousThrowable, - ResponseT previousResponse, - TimedAttemptSettings previousSettings); - - /** - * Returns {@code true} if another attempt should be made, or {@code false} otherwise. - * - * @param context the {@link RetryingContext} that may be used to determine whether the call - * should be retried - * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse response returned by the previous attempt - * @throws CancellationException if the retrying process should be cancelled - */ - boolean shouldRetry( - RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) - throws CancellationException; -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java index d9a915f4a..2ad7d00a8 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java @@ -51,7 +51,7 @@ public class ContextAwareRetryAlgorithm extends RetryAlgorithm resultAlgorithm, + BasicResultRetryAlgorithm resultAlgorithm, ContextAwareTimedRetryAlgorithm timedAlgorithm) { super(resultAlgorithm, timedAlgorithm); } @@ -127,8 +127,8 @@ public boolean shouldRetry( @Override @Beta - public ContextAwareResultRetryAlgorithm getResultAlgorithm() { - return (ContextAwareResultRetryAlgorithm) super.getResultAlgorithm(); + public BasicResultRetryAlgorithm getResultAlgorithm() { + return (BasicResultRetryAlgorithm) super.getResultAlgorithm(); } @Override diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java index 3a5f6b2ef..b2c3e1db5 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java @@ -51,17 +51,15 @@ public class ContextAwareScheduledRetryingExecutor extends ScheduledRetryingExecutor { - private final ContextAwareRetryAlgorithm retryAlgorithm; - public ContextAwareScheduledRetryingExecutor( ContextAwareRetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { super(retryAlgorithm, scheduler); - this.retryAlgorithm = retryAlgorithm; } @Override public RetryingFuture createFuture( Callable callable, RetryingContext context) { - return new ContextAwareCallbackChainRetryingFuture<>(callable, retryAlgorithm, this, context); + return new ContextAwareCallbackChainRetryingFuture<>( + callable, (ContextAwareRetryAlgorithm) getRetryAlgorithm(), this, context); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java index 5480b9a72..2ab21657f 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java @@ -45,7 +45,7 @@ public final class ContextAwareStreamingRetryAlgorithm extends ContextAwareRetryAlgorithm { public ContextAwareStreamingRetryAlgorithm( - ContextAwareResultRetryAlgorithm resultAlgorithm, + BasicResultRetryAlgorithm resultAlgorithm, ContextAwareTimedRetryAlgorithm timedAlgorithm) { super(resultAlgorithm, timedAlgorithm); } diff --git a/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java index c024bda77..b09830ac9 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java @@ -125,4 +125,9 @@ protected void sleep(Duration delay) throws InterruptedException { Thread.sleep(delay.toMillis()); } } + + /** Returns the retry algorithm that is used by this executor. */ + protected RetryAlgorithm getRetryAlgorithm() { + return retryAlgorithm; + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java index b6ee0a0c1..b8f1ecd17 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java @@ -122,4 +122,9 @@ public ApiFuture submit(RetryingFuture retryingFuture) { return ApiFutures.immediateFailedFuture(e); } } + + /** Returns the retry algorithm used by this executor. */ + protected RetryAlgorithm getRetryAlgorithm() { + return retryAlgorithm; + } } diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java index f8a4e5c47..91c1b26b6 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java @@ -71,9 +71,7 @@ public class ContextAwareStreamingRetryAlgorithmTest { @Test public void testFirstAttemptUsesDefaultSettings() { RetryingContext context = mock(RetryingContext.class); - @SuppressWarnings("unchecked") - ContextAwareResultRetryAlgorithm resultAlgorithm = - mock(ContextAwareResultRetryAlgorithm.class); + BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); ContextAwareTimedRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); @@ -89,9 +87,7 @@ public void testFirstAttemptUsesDefaultSettings() { public void testFirstAttemptUsesContextSettings() { RetryingContext context = mock(RetryingContext.class); when(context.getRetrySettings()).thenReturn(CONTEXT_RETRY_SETTINGS); - @SuppressWarnings("unchecked") - ContextAwareResultRetryAlgorithm resultAlgorithm = - mock(ContextAwareResultRetryAlgorithm.class); + BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); ContextAwareTimedRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); @@ -107,8 +103,7 @@ public void testFirstAttemptUsesContextSettings() { public void testNextAttemptReturnsNullWhenShouldNotRetry() { RetryingContext context = mock(RetryingContext.class); @SuppressWarnings("unchecked") - ContextAwareResultRetryAlgorithm resultAlgorithm = - mock(ContextAwareResultRetryAlgorithm.class); + BasicResultRetryAlgorithm resultAlgorithm = mock(BasicResultRetryAlgorithm.class); UnavailableException exception = mock(UnavailableException.class); when(resultAlgorithm.shouldRetry(context, exception, null)).thenReturn(false); ContextAwareTimedRetryAlgorithm timedAlgorithm = @@ -126,8 +121,7 @@ public void testNextAttemptReturnsNullWhenShouldNotRetry() { public void testNextAttemptReturnsResultAlgorithmSettingsWhenShouldRetry() { RetryingContext context = mock(RetryingContext.class); @SuppressWarnings("unchecked") - ContextAwareResultRetryAlgorithm resultAlgorithm = - mock(ContextAwareResultRetryAlgorithm.class); + BasicResultRetryAlgorithm resultAlgorithm = mock(BasicResultRetryAlgorithm.class); UnavailableException exception = mock(UnavailableException.class); when(resultAlgorithm.shouldRetry(context, exception, null)).thenReturn(true); TimedAttemptSettings next = mock(TimedAttemptSettings.class); @@ -151,9 +145,7 @@ public void testNextAttemptReturnsResultAlgorithmSettingsWhenShouldRetry() { @Test public void testNextAttemptResetsTimedSettings() { RetryingContext context = mock(RetryingContext.class); - @SuppressWarnings("unchecked") - ContextAwareResultRetryAlgorithm resultAlgorithm = - mock(ContextAwareResultRetryAlgorithm.class); + BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); ServerStreamingAttemptException exception = mock(ServerStreamingAttemptException.class); when(exception.canResume()).thenReturn(true); @@ -161,9 +153,9 @@ public void testNextAttemptResetsTimedSettings() { UnavailableException cause = mock(UnavailableException.class); when(exception.getCause()).thenReturn(cause); - when(resultAlgorithm.shouldRetry( - Mockito.eq(context), any(Throwable.class), Mockito.isNull())) - .thenReturn(true); + // when(resultAlgorithm.shouldRetry( + // Mockito.eq(context), any(Throwable.class), Mockito.isNull())) + // .thenReturn(true); ContextAwareTimedRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); @@ -189,10 +181,7 @@ public void testShouldNotRetryIfAttemptIsNonResumable() { UnavailableException cause = mock(UnavailableException.class); when(exception.getCause()).thenReturn(cause); - @SuppressWarnings("unchecked") - ContextAwareResultRetryAlgorithm resultAlgorithm = - mock(ContextAwareResultRetryAlgorithm.class); - when(resultAlgorithm.shouldRetry(context, cause, null)).thenReturn(true); + BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); ContextAwareTimedRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); @@ -214,10 +203,7 @@ public void testShouldRetryIfAllSayYes() { UnavailableException cause = mock(UnavailableException.class); when(exception.getCause()).thenReturn(cause); - @SuppressWarnings("unchecked") - ContextAwareResultRetryAlgorithm resultAlgorithm = - mock(ContextAwareResultRetryAlgorithm.class); - when(resultAlgorithm.shouldRetry(context, cause, null)).thenReturn(true); + BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); ContextAwareTimedRetryAlgorithm timedAlgorithm = mock(ContextAwareTimedRetryAlgorithm.class); when(timedAlgorithm.shouldRetry(Mockito.eq(context), any(TimedAttemptSettings.class))) From 51b670b7b0166cefa5ab5e2f757172e74e6eb364 Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Fri, 5 Feb 2021 15:21:35 +0100 Subject: [PATCH 09/23] refactor: remove ContextAwareTimedRetryAlgorithm --- .../api/gax/httpjson/HttpJsonCallContext.java | 2 - .../retrying/ContextAwareRetryAlgorithm.java | 6 +- .../ContextAwareStreamingRetryAlgorithm.java | 7 +- .../ContextAwareTimedRetryAlgorithm.java | 83 ------------------- .../retrying/ExponentialRetryAlgorithm.java | 5 +- ...extAwareScheduledRetryingExecutorTest.java | 4 +- ...ntextAwareStreamingRetryAlgorithmTest.java | 18 ++-- 7 files changed, 17 insertions(+), 108 deletions(-) delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index 08a179caa..aa2739fbd 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -29,7 +29,6 @@ */ package com.google.api.gax.httpjson; -import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.StatusCode; @@ -60,7 +59,6 @@ * arguments solely depends on the arguments themselves. */ @Beta -@InternalExtensionOnly public final class HttpJsonCallContext implements ApiCallContext { private final HttpJsonChannel channel; private final Duration timeout; diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java index 2ad7d00a8..d661fca7e 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java @@ -52,7 +52,7 @@ public class ContextAwareRetryAlgorithm extends RetryAlgorithm resultAlgorithm, - ContextAwareTimedRetryAlgorithm timedAlgorithm) { + ExponentialRetryAlgorithm timedAlgorithm) { super(resultAlgorithm, timedAlgorithm); } @@ -133,7 +133,7 @@ public BasicResultRetryAlgorithm getResultAlgorithm() { @Override @Beta - public ContextAwareTimedRetryAlgorithm getTimedAlgorithm() { - return (ContextAwareTimedRetryAlgorithm) super.getTimedAlgorithm(); + public ExponentialRetryAlgorithm getTimedAlgorithm() { + return (ExponentialRetryAlgorithm) super.getTimedAlgorithm(); } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java index 2ab21657f..75879c98e 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java @@ -33,8 +33,8 @@ import java.util.concurrent.CancellationException; /** - * Streaming retry algorithm that decides based either on the thrown exception and the execution - * time settings of the previous attempt. This extends {@link RetryAlgorithm} to take additional + * Streaming retry algorithm that decides based on the thrown exception and the execution time + * settings of the previous attempt. This extends {@link RetryAlgorithm} to take additional * information (provided by {@code ServerStreamingAttemptCallable}) into account. * *

This class is thread-safe. @@ -46,7 +46,7 @@ public final class ContextAwareStreamingRetryAlgorithm extends ContextAwareRetryAlgorithm { public ContextAwareStreamingRetryAlgorithm( BasicResultRetryAlgorithm resultAlgorithm, - ContextAwareTimedRetryAlgorithm timedAlgorithm) { + ExponentialRetryAlgorithm timedAlgorithm) { super(resultAlgorithm, timedAlgorithm); } @@ -55,6 +55,7 @@ public ContextAwareStreamingRetryAlgorithm( * *

The attempt settings will be reset if the stream attempt produced any messages. */ + @Override public TimedAttemptSettings createNextAttempt( RetryingContext context, Throwable previousThrowable, diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java deleted file mode 100644 index 3436bb860..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareTimedRetryAlgorithm.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.CancellationException; - -/** - * A timed retry algorithm is responsible for the following operations, based on the previous - * attempt settings, {@link RetryingContext}, and current time: - * - *

    - *
  • Creating first attempt {@link TimedAttemptSettings}. - *
  • Accepting a task for retry so another attempt will be made. - *
  • Canceling retrying process so the related {@link java.util.concurrent.Future} will be - * cancelled. - *
  • Creating {@link TimedAttemptSettings} for each subsequent retry attempt. - *
- * - * Implementations of this interface must be be thread-safe. - */ -public interface ContextAwareTimedRetryAlgorithm extends TimedRetryAlgorithm { - - /** - * Creates a first attempt {@link TimedAttemptSettings}. - * - * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes - * @return first attempt settings - */ - TimedAttemptSettings createFirstAttempt(RetryingContext context); - - /** - * Creates a next attempt {@link TimedAttemptSettings}, which defines properties of the next - * attempt. - * - * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes - * @param previousSettings previous attempt settings - * @return next attempt settings or {@code null} if the implementing algorithm does not provide - * specific settings for the next attempt - */ - TimedAttemptSettings createNextAttempt( - RetryingContext context, TimedAttemptSettings previousSettings); - - /** - * Returns {@code true} if another attempt should be made, or {@code false} otherwise. - * - * @param context a {@link RetryingContext} that may supply custom {@link RetrySettings} and - * retryable codes - * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if - * accepted - * @throws CancellationException if the retrying process should be cancelled - */ - boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) - throws CancellationException; -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index 772134647..224397c58 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -41,7 +41,7 @@ * *

This class is thread-safe. */ -public class ExponentialRetryAlgorithm implements ContextAwareTimedRetryAlgorithm { +public class ExponentialRetryAlgorithm implements TimedRetryAlgorithm { private final RetrySettings globalSettings; private final ApiClock clock; @@ -85,7 +85,6 @@ public TimedAttemptSettings createFirstAttempt() { * retryable codes * @return first attempt settings */ - @Override public TimedAttemptSettings createFirstAttempt(RetryingContext context) { if (context.getRetrySettings() == null) { return createFirstAttempt(); @@ -176,7 +175,6 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings previousSetti * @param previousSettings previous attempt settings * @return next attempt settings */ - @Override public TimedAttemptSettings createNextAttempt( RetryingContext context, TimedAttemptSettings previousSettings) { // The RetrySettings from the context are not used here, as they have already been set as the @@ -242,7 +240,6 @@ public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or * totalTimeout limit, or {@code false} otherwise */ - @Override public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { // The RetrySettings from the context are not used here, as they have already been set as the // global settings during the creation of the initial attempt. diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java index c76840e11..0c8fe48b5 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java @@ -357,8 +357,8 @@ public void testCancelProxiedFutureAfterStart() throws Exception { // Note that shutdownNow() will not cancel internal FutureTasks automatically, which // may potentially cause another thread hanging on RetryingFuture#get() call forever. - // Canceling the tasks returned by shutdownNow() also does not help, because of missing - // feature in guava's ListenableScheduledFuture, which does not cancel itself, when its + // Cancelling the tasks returned by shutdownNow() also does not help, because of missing + // feature in guava's ListenableScheduledFuture, which does not cancel itself when its // delegate is cancelled. So only the graceful shutdown() is supported properly. localExecutor.shutdown(); diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java index 91c1b26b6..0c7bd0628 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java @@ -72,7 +72,7 @@ public class ContextAwareStreamingRetryAlgorithmTest { public void testFirstAttemptUsesDefaultSettings() { RetryingContext context = mock(RetryingContext.class); BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); - ContextAwareTimedRetryAlgorithm timedAlgorithm = + ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); ContextAwareStreamingRetryAlgorithm algorithm = @@ -88,7 +88,7 @@ public void testFirstAttemptUsesContextSettings() { RetryingContext context = mock(RetryingContext.class); when(context.getRetrySettings()).thenReturn(CONTEXT_RETRY_SETTINGS); BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); - ContextAwareTimedRetryAlgorithm timedAlgorithm = + ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); ContextAwareStreamingRetryAlgorithm algorithm = @@ -106,7 +106,7 @@ public void testNextAttemptReturnsNullWhenShouldNotRetry() { BasicResultRetryAlgorithm resultAlgorithm = mock(BasicResultRetryAlgorithm.class); UnavailableException exception = mock(UnavailableException.class); when(resultAlgorithm.shouldRetry(context, exception, null)).thenReturn(false); - ContextAwareTimedRetryAlgorithm timedAlgorithm = + ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); ContextAwareStreamingRetryAlgorithm algorithm = @@ -131,7 +131,7 @@ public void testNextAttemptReturnsResultAlgorithmSettingsWhenShouldRetry() { Mockito.isNull(), any(TimedAttemptSettings.class))) .thenReturn(next); - ContextAwareTimedRetryAlgorithm timedAlgorithm = + ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); ContextAwareStreamingRetryAlgorithm algorithm = @@ -153,11 +153,7 @@ public void testNextAttemptResetsTimedSettings() { UnavailableException cause = mock(UnavailableException.class); when(exception.getCause()).thenReturn(cause); - // when(resultAlgorithm.shouldRetry( - // Mockito.eq(context), any(Throwable.class), Mockito.isNull())) - // .thenReturn(true); - - ContextAwareTimedRetryAlgorithm timedAlgorithm = + ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); ContextAwareStreamingRetryAlgorithm algorithm = new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); @@ -183,7 +179,7 @@ public void testShouldNotRetryIfAttemptIsNonResumable() { BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); - ContextAwareTimedRetryAlgorithm timedAlgorithm = + ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); ContextAwareStreamingRetryAlgorithm algorithm = new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); @@ -205,7 +201,7 @@ public void testShouldRetryIfAllSayYes() { BasicResultRetryAlgorithm resultAlgorithm = new BasicResultRetryAlgorithm<>(); - ContextAwareTimedRetryAlgorithm timedAlgorithm = mock(ContextAwareTimedRetryAlgorithm.class); + ExponentialRetryAlgorithm timedAlgorithm = mock(ExponentialRetryAlgorithm.class); when(timedAlgorithm.shouldRetry(Mockito.eq(context), any(TimedAttemptSettings.class))) .thenReturn(true); ContextAwareStreamingRetryAlgorithm algorithm = From 41fdc4e53c2e977c5cf4edb92e42477903331d8a Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Fri, 5 Feb 2021 16:49:21 +0100 Subject: [PATCH 10/23] fix: make package-private + add ignored --- .../com/google/api/gax/grpc/GrpcCallContext.java | 2 +- .../gax/retrying/BasicResultRetryAlgorithm.java | 15 ++++++--------- .../gax/retrying/ExponentialRetryAlgorithm.java | 8 ++++++-- .../java/com/google/api/gax/rpc/Callables.java | 1 - .../ContextAwareStreamingRetryAlgorithm.java | 16 ++++++++++------ .../ContextAwareStreamingRetryAlgorithmTest.java | 9 +++++++-- 6 files changed, 30 insertions(+), 21 deletions(-) rename gax/src/main/java/com/google/api/gax/{retrying => rpc}/ContextAwareStreamingRetryAlgorithm.java (87%) rename gax/src/test/java/com/google/api/gax/{retrying => rpc}/ContextAwareStreamingRetryAlgorithmTest.java (96%) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index c2af8f980..cb23d36b1 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -65,7 +65,7 @@ * #withTransportChannel}, return copies of the object, but with one field changed. The immutability * and thread safety of the arguments solely depends on the arguments themselves. * - *

Applications should reference {@link ApiCallContext} instead - this class is likely to + *

Applications should reference {@link ApiCallContext} instead. This class is likely to * experience breaking changes. */ @Beta diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index 254feedc2..37d7b849e 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -44,10 +44,6 @@ public class BasicResultRetryAlgorithm implements ResultRetryAlgorith /** * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. - * - * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse response returned by the previous attempt - * @param previousSettings previous attempt settings */ @Override public TimedAttemptSettings createNextAttempt( @@ -62,7 +58,7 @@ public TimedAttemptSettings createNextAttempt( * the next attempt. * * @param context the retrying context of this invocation that can be used to determine the - * settings for the next attempt + * settings for the next attempt. Ignored by this implementation. * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) * @param previousResponse response returned by the previous attempt * @param previousSettings previous attempt settings @@ -72,7 +68,7 @@ public TimedAttemptSettings createNextAttempt( Throwable previousThrowable, ResponseT previousResponse, TimedAttemptSettings previousSettings) { - return createNextAttempt(previousThrowable, previousResponse, previousSettings); + return null; } /** @@ -80,7 +76,7 @@ public TimedAttemptSettings createNextAttempt( * false} otherwise. * * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse response returned by the previous attempt + * @param previousResponse ignored */ @Override public boolean shouldRetry(Throwable previousThrowable, ResponseT previousResponse) { @@ -92,9 +88,10 @@ public boolean shouldRetry(Throwable previousThrowable, ResponseT previousRespon * false} otherwise. * * @param context the retrying context of this invocation that can be used to determine whether - * the call should be retried + * the call should be retried. Ignored by this implementation. * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse response returned by the previous attempt + * @param previousResponse response returned by the previous attempt. Ignored by this + * implementation. */ public boolean shouldRetry( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index 224397c58..cc9aaedde 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -234,7 +234,7 @@ public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and - * retryable codes + * retryable codes. Ignored by this implementation. * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or @@ -247,7 +247,11 @@ public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAtt } // Injecting Random is not possible here, as Random does not provide nextLong(long bound) method - protected long nextRandomLong(long bound, boolean withJitter) { + protected long nextRandomLong(long bound) { + return nextRandomLong(bound, globalSettings.isJittered()); + } + + private long nextRandomLong(long bound, boolean withJitter) { return bound > 0 && withJitter ? ThreadLocalRandom.current().nextLong(bound) : bound; } } diff --git a/gax/src/main/java/com/google/api/gax/rpc/Callables.java b/gax/src/main/java/com/google/api/gax/rpc/Callables.java index a981b1a43..d38e1351a 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/Callables.java +++ b/gax/src/main/java/com/google/api/gax/rpc/Callables.java @@ -35,7 +35,6 @@ import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.retrying.ContextAwareRetryAlgorithm; import com.google.api.gax.retrying.ContextAwareScheduledRetryingExecutor; -import com.google.api.gax.retrying.ContextAwareStreamingRetryAlgorithm; import com.google.api.gax.retrying.ExponentialRetryAlgorithm; import com.google.api.gax.retrying.RetryAlgorithm; import com.google.api.gax.retrying.RetrySettings; diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithm.java similarity index 87% rename from gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java rename to gax/src/main/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithm.java index 75879c98e..cd884d81a 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithm.java @@ -27,9 +27,16 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.google.api.gax.retrying; +package com.google.api.gax.rpc; -import com.google.api.core.InternalApi; +import com.google.api.gax.retrying.BasicResultRetryAlgorithm; +import com.google.api.gax.retrying.ContextAwareRetryAlgorithm; +import com.google.api.gax.retrying.ExponentialRetryAlgorithm; +import com.google.api.gax.retrying.RetryAlgorithm; +import com.google.api.gax.retrying.RetryingContext; +import com.google.api.gax.retrying.ServerStreamingAttemptException; +import com.google.api.gax.retrying.StreamResumptionStrategy; +import com.google.api.gax.retrying.TimedAttemptSettings; import java.util.concurrent.CancellationException; /** @@ -38,11 +45,8 @@ * information (provided by {@code ServerStreamingAttemptCallable}) into account. * *

This class is thread-safe. - * - *

Internal use only - public for technical reasons. */ -@InternalApi("For internal use only") -public final class ContextAwareStreamingRetryAlgorithm +final class ContextAwareStreamingRetryAlgorithm extends ContextAwareRetryAlgorithm { public ContextAwareStreamingRetryAlgorithm( BasicResultRetryAlgorithm resultAlgorithm, diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithmTest.java similarity index 96% rename from gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java rename to gax/src/test/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithmTest.java index 0c7bd0628..8a36bdc72 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareStreamingRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithmTest.java @@ -27,7 +27,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.google.api.gax.retrying; +package com.google.api.gax.rpc; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -35,7 +35,12 @@ import static org.mockito.Mockito.when; import com.google.api.core.ApiClock; -import com.google.api.gax.rpc.UnavailableException; +import com.google.api.gax.retrying.BasicResultRetryAlgorithm; +import com.google.api.gax.retrying.ExponentialRetryAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.retrying.RetryingContext; +import com.google.api.gax.retrying.ServerStreamingAttemptException; +import com.google.api.gax.retrying.TimedAttemptSettings; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; From 68b2c69264f6224d29f916c3936055bc8844a693 Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Fri, 5 Feb 2021 18:48:14 +0100 Subject: [PATCH 11/23] revert: revert to always using global settings for jittered --- .../api/gax/retrying/ExponentialRetryAlgorithm.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index cc9aaedde..b26c39fab 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -128,7 +128,7 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings previousSetti (long) (settings.getRetryDelayMultiplier() * previousSettings.getRetryDelay().toMillis()); newRetryDelay = Math.min(newRetryDelay, settings.getMaxRetryDelay().toMillis()); } - Duration randomDelay = Duration.ofMillis(nextRandomLong(newRetryDelay, settings.isJittered())); + Duration randomDelay = Duration.ofMillis(nextRandomLong(newRetryDelay)); // The rpc timeout is determined as follows: // attempt #0 - use the initialRpcTimeout; @@ -248,10 +248,8 @@ public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAtt // Injecting Random is not possible here, as Random does not provide nextLong(long bound) method protected long nextRandomLong(long bound) { - return nextRandomLong(bound, globalSettings.isJittered()); - } - - private long nextRandomLong(long bound, boolean withJitter) { - return bound > 0 && withJitter ? ThreadLocalRandom.current().nextLong(bound) : bound; + return bound > 0 && globalSettings.isJittered() + ? ThreadLocalRandom.current().nextLong(bound) + : bound; } } From f6beeeffffdcdc484671d4ea3147800cdaaaef78 Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Mon, 8 Feb 2021 11:18:50 +0100 Subject: [PATCH 12/23] test: add tests for NoopRetryingContext --- .../gax/retrying/NoopRetryingContextTest.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 gax/src/test/java/com/google/api/gax/retrying/NoopRetryingContextTest.java diff --git a/gax/src/test/java/com/google/api/gax/retrying/NoopRetryingContextTest.java b/gax/src/test/java/com/google/api/gax/retrying/NoopRetryingContextTest.java new file mode 100644 index 000000000..285b8f5ce --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/NoopRetryingContextTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import com.google.api.gax.tracing.NoopApiTracer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class NoopRetryingContextTest { + + @Test + public void testGetTracer() { + RetryingContext context = NoopRetryingContext.create(); + assertSame(NoopApiTracer.getInstance(), context.getTracer()); + } + + @Test + public void testGetRetrySettings() { + RetryingContext context = NoopRetryingContext.create(); + assertNull(context.getRetrySettings()); + } + + @Test + public void testGetRetryableCodes() { + RetryingContext context = NoopRetryingContext.create(); + assertNull(context.getRetryableCodes()); + } +} From 8c5090d096b61beffa166b9ef1848291a8bb3549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 24 Feb 2021 19:45:59 +0100 Subject: [PATCH 13/23] chore: cleanup 'this' usage --- .../google/api/gax/grpc/GrpcCallContext.java | 92 +++++++++---------- .../api/gax/httpjson/HttpJsonCallContext.java | 64 ++++++------- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index 0c3534505..2449a6fdb 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -190,7 +190,7 @@ public GrpcCallContext withTimeout(@Nullable Duration timeout) { this.streamIdleTimeout, this.channelAffinity, this.extraHeaders, - retrySettings, + this.retrySettings, this.retryableCodes); } @@ -208,15 +208,15 @@ public GrpcCallContext withStreamWaitTimeout(@Nullable Duration streamWaitTimeou } return new GrpcCallContext( - channel, - callOptions, - timeout, + this.channel, + this.callOptions, + this.timeout, streamWaitTimeout, - streamIdleTimeout, - channelAffinity, - extraHeaders, - retrySettings, - retryableCodes); + this.streamIdleTimeout, + this.channelAffinity, + this.extraHeaders, + this.retrySettings, + this.retryableCodes); } @Override @@ -227,29 +227,29 @@ public GrpcCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeou } return new GrpcCallContext( - channel, - callOptions, - timeout, - streamWaitTimeout, + this.channel, + this.callOptions, + this.timeout, + this.streamWaitTimeout, streamIdleTimeout, - channelAffinity, - extraHeaders, - retrySettings, - retryableCodes); + this.channelAffinity, + this.extraHeaders, + this.retrySettings, + this.retryableCodes); } @Beta public GrpcCallContext withChannelAffinity(@Nullable Integer affinity) { return new GrpcCallContext( - channel, - callOptions, - timeout, - streamWaitTimeout, - streamIdleTimeout, + this.channel, + this.callOptions, + this.timeout, + this.streamWaitTimeout, + this.streamIdleTimeout, affinity, - extraHeaders, - retrySettings, - retryableCodes); + this.extraHeaders, + this.retrySettings, + this.retryableCodes); } @Beta @@ -259,15 +259,15 @@ public GrpcCallContext withExtraHeaders(Map> extraHeaders) ImmutableMap> newExtraHeaders = Headers.mergeHeaders(this.extraHeaders, extraHeaders); return new GrpcCallContext( - channel, - callOptions, - timeout, - streamWaitTimeout, - streamIdleTimeout, - channelAffinity, + this.channel, + this.callOptions, + this.timeout, + this.streamWaitTimeout, + this.streamIdleTimeout, + this.channelAffinity, newExtraHeaders, - retrySettings, - retryableCodes); + this.retrySettings, + this.retryableCodes); } @Override @@ -286,7 +286,7 @@ public GrpcCallContext withRetrySettings(RetrySettings retrySettings) { this.channelAffinity, this.extraHeaders, retrySettings, - retryableCodes); + this.retryableCodes); } @Override @@ -371,7 +371,7 @@ public ApiCallContext merge(ApiCallContext inputCallContext) { } ImmutableMap> newExtraHeaders = - Headers.mergeHeaders(extraHeaders, grpcCallContext.extraHeaders); + Headers.mergeHeaders(this.extraHeaders, grpcCallContext.extraHeaders); CallOptions newCallOptions = grpcCallContext @@ -446,7 +446,7 @@ public GrpcCallContext withChannel(Channel newChannel) { return new GrpcCallContext( newChannel, this.callOptions, - timeout, + this.timeout, this.streamWaitTimeout, this.streamIdleTimeout, this.channelAffinity, @@ -460,7 +460,7 @@ public GrpcCallContext withCallOptions(CallOptions newCallOptions) { return new GrpcCallContext( this.channel, newCallOptions, - timeout, + this.timeout, this.streamWaitTimeout, this.streamIdleTimeout, this.channelAffinity, @@ -518,15 +518,15 @@ public boolean equals(Object o) { } GrpcCallContext that = (GrpcCallContext) o; - return Objects.equals(channel, that.channel) - && Objects.equals(callOptions, that.callOptions) - && Objects.equals(timeout, that.timeout) - && Objects.equals(streamWaitTimeout, that.streamWaitTimeout) - && Objects.equals(streamIdleTimeout, that.streamIdleTimeout) - && Objects.equals(channelAffinity, that.channelAffinity) - && Objects.equals(extraHeaders, that.extraHeaders) - && Objects.equals(retrySettings, that.retrySettings) - && Objects.equals(retryableCodes, that.retryableCodes); + return Objects.equals(this.channel, that.channel) + && Objects.equals(this.callOptions, that.callOptions) + && Objects.equals(this.timeout, that.timeout) + && Objects.equals(this.streamWaitTimeout, that.streamWaitTimeout) + && Objects.equals(this.streamIdleTimeout, that.streamIdleTimeout) + && Objects.equals(this.channelAffinity, that.channelAffinity) + && Objects.equals(this.extraHeaders, that.extraHeaders) + && Objects.equals(this.retrySettings, that.retrySettings) + && Objects.equals(this.retryableCodes, that.retryableCodes); } Metadata getMetadata() { diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index aa2739fbd..4dba9de38 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -260,10 +260,10 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { ImmutableMap> newExtraHeaders = Headers.mergeHeaders(this.extraHeaders, extraHeaders); return new HttpJsonCallContext( - channel, - timeout, - deadline, - credentials, + this.channel, + this.timeout, + this.deadline, + this.credentials, newExtraHeaders, this.tracer, this.retrySettings, @@ -273,7 +273,7 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { @Beta @Override public Map> getExtraHeaders() { - return this.extraHeaders; + return extraHeaders; } public HttpJsonChannel getChannel() { @@ -294,14 +294,14 @@ public RetrySettings getRetrySettings() { public HttpJsonCallContext withRetrySettings(RetrySettings retrySettings) { return new HttpJsonCallContext( - channel, - timeout, - deadline, - credentials, - extraHeaders, + this.channel, + this.timeout, + this.deadline, + this.credentials, + this.extraHeaders, this.tracer, retrySettings, - retryableCodes); + this.retryableCodes); } public Set getRetryableCodes() { @@ -323,10 +323,10 @@ public HttpJsonCallContext withRetryableCodes(Set retryableCode public HttpJsonCallContext withChannel(HttpJsonChannel newChannel) { return new HttpJsonCallContext( newChannel, - timeout, - deadline, - credentials, - extraHeaders, + this.timeout, + this.deadline, + this.credentials, + this.extraHeaders, this.tracer, this.retrySettings, this.retryableCodes); @@ -334,11 +334,11 @@ public HttpJsonCallContext withChannel(HttpJsonChannel newChannel) { public HttpJsonCallContext withDeadline(Instant newDeadline) { return new HttpJsonCallContext( - channel, - timeout, + this.channel, + this.timeout, newDeadline, - credentials, - extraHeaders, + this.credentials, + this.extraHeaders, this.tracer, this.retrySettings, this.retryableCodes); @@ -359,11 +359,11 @@ public HttpJsonCallContext withTracer(@Nonnull ApiTracer newTracer) { Preconditions.checkNotNull(newTracer); return new HttpJsonCallContext( - channel, - timeout, - deadline, - credentials, - extraHeaders, + this.channel, + this.timeout, + this.deadline, + this.credentials, + this.extraHeaders, newTracer, this.retrySettings, this.retryableCodes); @@ -378,14 +378,14 @@ public boolean equals(Object o) { return false; } HttpJsonCallContext that = (HttpJsonCallContext) o; - return Objects.equals(channel, that.channel) - && Objects.equals(timeout, that.timeout) - && Objects.equals(deadline, that.deadline) - && Objects.equals(credentials, that.credentials) - && Objects.equals(extraHeaders, that.extraHeaders) - && Objects.equals(tracer, that.tracer) - && Objects.equals(retrySettings, that.retrySettings) - && Objects.equals(retryableCodes, that.retryableCodes); + return Objects.equals(this.channel, that.channel) + && Objects.equals(this.timeout, that.timeout) + && Objects.equals(this.deadline, that.deadline) + && Objects.equals(this.credentials, that.credentials) + && Objects.equals(this.extraHeaders, that.extraHeaders) + && Objects.equals(this.tracer, that.tracer) + && Objects.equals(this.retrySettings, that.retrySettings) + && Objects.equals(this.retryableCodes, that.retryableCodes); } @Override From 395d7cf71f52df107a0325fe20073321f1f3cfb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 1 Mar 2021 11:13:50 +0100 Subject: [PATCH 14/23] refactor: merge context aware classes with base classes --- .../google/api/gax/grpc/GrpcCallContext.java | 19 +- .../api/gax/httpjson/HttpJsonCallContext.java | 8 +- .../retrying/BasicResultRetryAlgorithm.java | 16 +- .../api/gax/retrying/BasicRetryingFuture.java | 12 +- .../ContextAwareBasicRetryingFuture.java | 77 -------- ...ntextAwareCallbackChainRetryingFuture.java | 89 --------- .../ContextAwareDirectRetryingExecutor.java | 51 ----- .../retrying/ContextAwareRetryAlgorithm.java | 139 -------------- ...ContextAwareScheduledRetryingExecutor.java | 65 ------- .../retrying/ExponentialRetryAlgorithm.java | 5 +- .../api/gax/retrying/RetryAlgorithm.java | 178 +++++++++++++++++- .../retrying/RetryAlgorithmWithContext.java | 80 ++++++++ .../gax/retrying/StreamingRetryAlgorithm.java | 63 ++++++- .../TimedRetryAlgorithmWithContext.java | 86 +++++++++ .../com/google/api/gax/rpc/Callables.java | 19 +- .../ContextAwareStreamingRetryAlgorithm.java | 115 ----------- .../AbstractRetryingExecutorTest.java | 4 +- .../gax/retrying/BasicRetryingFutureTest.java | 54 +++++- .../ContextAwareBasicRetryingFutureTest.java | 93 --------- ...etryingExecutorWithRetrySettingsTest.java} | 9 +- ...etryingExecutorWithRetrySettingsTest.java} | 12 +- ...gRetryAlgorithmWithRetrySettingsTest.java} | 31 +-- 22 files changed, 515 insertions(+), 710 deletions(-) delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithmWithContext.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithmWithContext.java delete mode 100644 gax/src/main/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithm.java delete mode 100644 gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java rename gax/src/test/java/com/google/api/gax/retrying/{ContextAwareDirectRetryingExecutorTest.java => DirectRetryingExecutorWithRetrySettingsTest.java} (89%) rename gax/src/test/java/com/google/api/gax/retrying/{ContextAwareScheduledRetryingExecutorTest.java => ScheduledRetryingExecutorWithRetrySettingsTest.java} (96%) rename gax/src/test/java/com/google/api/gax/rpc/{ContextAwareStreamingRetryAlgorithmTest.java => StreamingRetryAlgorithmWithRetrySettingsTest.java} (89%) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index 2449a6fdb..ebd395b92 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.grpc; +import com.google.api.core.BetaApi; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.StatusCode; @@ -37,7 +38,6 @@ import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.NoopApiTracer; import com.google.auth.Credentials; -import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -63,11 +63,8 @@ * GrpcCallContext itself or the underlying data. Methods of the form {@code withX}, such as {@link * #withTransportChannel}, return copies of the object, but with one field changed. The immutability * and thread safety of the arguments solely depends on the arguments themselves. - * - *

Applications should reference {@link ApiCallContext} instead. This class is likely to - * experience breaking changes. */ -@Beta +@BetaApi("Reference ApiCallContext instead - this class is likely to experience breaking changes") public final class GrpcCallContext implements ApiCallContext { static final CallOptions.Key TRACER_KEY = Key.create("gax.tracer"); @@ -238,7 +235,7 @@ public GrpcCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeou this.retryableCodes); } - @Beta + @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") public GrpcCallContext withChannelAffinity(@Nullable Integer affinity) { return new GrpcCallContext( this.channel, @@ -252,7 +249,7 @@ public GrpcCallContext withChannelAffinity(@Nullable Integer affinity) { this.retryableCodes); } - @Beta + @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") @Override public GrpcCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); @@ -410,7 +407,7 @@ public CallOptions getCallOptions() { * * @see ApiCallContext#withStreamWaitTimeout(Duration) */ - @Beta + @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") @Nullable public Duration getStreamWaitTimeout() { return streamWaitTimeout; @@ -421,21 +418,21 @@ public Duration getStreamWaitTimeout() { * * @see ApiCallContext#withStreamIdleTimeout(Duration) */ - @Beta + @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") @Nullable public Duration getStreamIdleTimeout() { return streamIdleTimeout; } /** The channel affinity for this context. */ - @Beta + @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") @Nullable public Integer getChannelAffinity() { return channelAffinity; } /** The extra header for this context. */ - @Beta + @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") @Override public Map> getExtraHeaders() { return this.extraHeaders; diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index 4dba9de38..75dbcb42f 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.httpjson; +import com.google.api.core.BetaApi; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.StatusCode; @@ -37,7 +38,6 @@ import com.google.api.gax.tracing.ApiTracer; import com.google.api.gax.tracing.NoopApiTracer; import com.google.auth.Credentials; -import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -58,7 +58,7 @@ * copies of the object, but with one field changed. The immutability and thread safety of the * arguments solely depends on the arguments themselves. */ -@Beta +@BetaApi public final class HttpJsonCallContext implements ApiCallContext { private final HttpJsonChannel channel; private final Duration timeout; @@ -253,7 +253,7 @@ public Duration getStreamIdleTimeout() { throw new UnsupportedOperationException("Http/json transport does not support streaming"); } - @Beta + @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @Override public ApiCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); @@ -270,7 +270,7 @@ public ApiCallContext withExtraHeaders(Map> extraHeaders) { this.retryableCodes); } - @Beta + @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @Override public Map> getExtraHeaders() { return extraHeaders; diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index 37d7b849e..37fa9602e 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -40,7 +40,7 @@ * * @param attempt response type */ -public class BasicResultRetryAlgorithm implements ResultRetryAlgorithm { +public class BasicResultRetryAlgorithm implements RetryAlgorithmWithContext { /** * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. @@ -56,13 +56,8 @@ public TimedAttemptSettings createNextAttempt( /** * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. - * - * @param context the retrying context of this invocation that can be used to determine the - * settings for the next attempt. Ignored by this implementation. - * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse response returned by the previous attempt - * @param previousSettings previous attempt settings */ + @Override public TimedAttemptSettings createNextAttempt( RetryingContext context, Throwable previousThrowable, @@ -86,13 +81,8 @@ public boolean shouldRetry(Throwable previousThrowable, ResponseT previousRespon /** * Returns {@code true} if an exception was thrown ({@code previousThrowable != null}), {@code * false} otherwise. - * - * @param context the retrying context of this invocation that can be used to determine whether - * the call should be retried. Ignored by this implementation. - * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse response returned by the previous attempt. Ignored by this - * implementation. */ + @Override public boolean shouldRetry( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) throws CancellationException { diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index 822e1c53a..9236ba08f 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -195,7 +195,9 @@ void handleAttempt(Throwable throwable, ResponseT response) { setAttemptResult(throwable, response, true); // a new attempt will be (must be) scheduled by an external executor } else if (throwable != null) { - if (retryAlgorithm.getResultAlgorithm().shouldRetry(throwable, response)) { + if (retryAlgorithm + .getResultAlgorithm() + .shouldRetry(retryingContext, throwable, response)) { tracer.attemptFailedRetriesExhausted(throwable); } else { tracer.attemptPermanentFailure(throwable); @@ -222,7 +224,7 @@ void handleAttempt(Throwable throwable, ResponseT response) { // Calls retryAlgorithm.createFirstAttempt() for the basic implementation. May be overridden by // subclasses that can use the RetryingContext to determine the initial attempt settings. TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return retryAlgorithm.createFirstAttempt(); + return retryAlgorithm.createFirstAttempt(context); } // Calls retryAlgorithm.createNextAttempt(throwable, response, attemptSettings) for the basic @@ -230,7 +232,7 @@ TimedAttemptSettings createFirstAttempt(RetryingContext context) { // the next attempt settings. TimedAttemptSettings createNextAttempt( RetryingContext context, Throwable throwable, ResponseT response) { - return retryAlgorithm.createNextAttempt(throwable, response, attemptSettings); + return retryAlgorithm.createNextAttempt(context, throwable, response, attemptSettings); } // Calls retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings) for the basic @@ -241,14 +243,14 @@ boolean shouldRetry( Throwable throwable, ResponseT response, TimedAttemptSettings nextAttemptSettings) { - return retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings); + return retryAlgorithm.shouldRetry(context, throwable, response, nextAttemptSettings); } // Calls retryAlgorithm.getResultAlgorithm().shouldRetry(throwable, response) for the basic // implementation. May be overridden by subclasses that can use the RetryingContext to determine // whether the call should be retried. boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { - return retryAlgorithm.getResultAlgorithm().shouldRetry(throwable, response); + return retryAlgorithm.getResultAlgorithm().shouldRetry(context, throwable, response); } // Sets attempt result futures. Note the "attempt result future" and "attempt future" are not same diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java deleted file mode 100644 index 094b2a067..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFuture.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.Callable; - -/** - * {@link BasicRetryingFuture} implementation that uses {@link RetrySettings} and retryable codes - * from the {@link RetryingContext} if they are set. - */ -class ContextAwareBasicRetryingFuture extends BasicRetryingFuture { - - ContextAwareBasicRetryingFuture( - Callable callable, - ContextAwareRetryAlgorithm retryAlgorithm, - RetryingContext context) { - super(callable, retryAlgorithm, context); - } - - @Override - ContextAwareRetryAlgorithm getRetryAlgorithm() { - return (ContextAwareRetryAlgorithm) super.getRetryAlgorithm(); - } - - @Override - TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return getRetryAlgorithm().createFirstAttempt(context); - } - - @Override - TimedAttemptSettings createNextAttempt( - RetryingContext context, Throwable throwable, ResponseT response) { - return getRetryAlgorithm() - .createNextAttempt(context, throwable, response, getAttemptSettings()); - } - - @Override - boolean shouldRetry( - RetryingContext context, - Throwable throwable, - ResponseT response, - TimedAttemptSettings nextAttemptSettings) { - return getRetryAlgorithm().shouldRetry(context, throwable, response, nextAttemptSettings); - } - - @Override - boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { - return getRetryAlgorithm().getResultAlgorithm().shouldRetry(context, throwable, response); - } -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java deleted file mode 100644 index d69d2811b..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareCallbackChainRetryingFuture.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.Callable; - -/** - * {@link CallbackChainRetryingFuture} implementation that will use {@link RetrySettings} and - * retryable codes from the {@link RetryingContext} if they have been set. - */ -public class ContextAwareCallbackChainRetryingFuture - extends CallbackChainRetryingFuture { - - ContextAwareCallbackChainRetryingFuture( - Callable callable, - ContextAwareRetryAlgorithm retryAlgorithm, - ScheduledRetryingExecutor retryingExecutor, - RetryingContext context) { - super(callable, retryAlgorithm, retryingExecutor, context); - } - - @Override - ContextAwareRetryAlgorithm getRetryAlgorithm() { - return (ContextAwareRetryAlgorithm) super.getRetryAlgorithm(); - } - - /** Creates the first attempt settings using the given {@link RetryingContext}. */ - @Override - TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return getRetryAlgorithm().createFirstAttempt(context); - } - - /** Creates the next attempt settings using the given {@link RetryingContext}. */ - @Override - TimedAttemptSettings createNextAttempt( - RetryingContext context, Throwable throwable, ResponseT response) { - return getRetryAlgorithm() - .createNextAttempt(context, throwable, response, getAttemptSettings()); - } - - /** - * Determines whether to schedule another attempt based on the given {@link RetryingContext} and - * the result and attempt settings of the previous attempt. - */ - @Override - boolean shouldRetry( - RetryingContext context, - Throwable throwable, - ResponseT response, - TimedAttemptSettings nextAttemptSettings) { - return getRetryAlgorithm().shouldRetry(context, throwable, response, nextAttemptSettings); - } - - /** - * Determines whether to schedule another attempt based on the given {@link RetryingContext} and - * the result of the previous attempt. - */ - @Override - boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { - return getRetryAlgorithm().getResultAlgorithm().shouldRetry(context, throwable, response); - } -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java deleted file mode 100644 index 2bef86aa9..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutor.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.Callable; - -/** - * {@link DirectRetryingExecutor} implementation that uses {@link RetrySettings} and retryable codes - * from the {@link RetryingContext} if they have been set. - */ -public class ContextAwareDirectRetryingExecutor - extends DirectRetryingExecutor { - - public ContextAwareDirectRetryingExecutor(ContextAwareRetryAlgorithm retryAlgorithm) { - super(retryAlgorithm); - } - - @Override - public RetryingFuture createFuture( - Callable callable, RetryingContext context) { - return new ContextAwareBasicRetryingFuture<>( - callable, (ContextAwareRetryAlgorithm) getRetryAlgorithm(), context); - } -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java deleted file mode 100644 index d661fca7e..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareRetryAlgorithm.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import com.google.common.annotations.Beta; -import java.util.concurrent.CancellationException; - -/** - * Retry algorithm that decides whether to retry based either on the thrown exception or the - * returned response, the execution time settings of the previous attempt, and the {@link - * RetrySettings} and retryable codes supplied by a {@link RetryingContext}. - * - *

This class is thread-safe. - * - * @param response type - */ -public class ContextAwareRetryAlgorithm extends RetryAlgorithm { - /** - * Creates a new retry algorithm instance, which uses thrown exception or returned response and - * timed algorithms to make a decision. The result algorithm has higher priority than the timed - * algorithm. - * - * @param resultAlgorithm result algorithm to use - * @param timedAlgorithm timed algorithm to use - */ - public ContextAwareRetryAlgorithm( - BasicResultRetryAlgorithm resultAlgorithm, - ExponentialRetryAlgorithm timedAlgorithm) { - super(resultAlgorithm, timedAlgorithm); - } - - /** - * Creates a first attempt {@link TimedAttemptSettings}. - * - * @param context the {@link RetryingContext} that can be used to get the initial {@link - * RetrySettings} - * @return first attempt settings - */ - public TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return getTimedAlgorithm().createFirstAttempt(context); - } - - /** - * Creates a next attempt {@link TimedAttemptSettings}. This method will return first non-null - * value, returned by either result or timed retry algorithms in that particular order. - * - * @param context the {@link RetryingContext} that can be used to determine the {@link - * RetrySettings} for the next attempt - * @param previousThrowable exception thrown by the previous attempt or null if a result was - * returned instead - * @param previousResponse response returned by the previous attempt or null if an exception was - * thrown instead - * @param previousSettings previous attempt settings - * @return next attempt settings, can be {@code null}, if there should be no new attempt - */ - public TimedAttemptSettings createNextAttempt( - RetryingContext context, - Throwable previousThrowable, - ResponseT previousResponse, - TimedAttemptSettings previousSettings) { - // a small optimization that avoids calling relatively heavy methods - // like timedAlgorithm.createNextAttempt(), when it is not necessary. - if (!getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse)) { - return null; - } - - TimedAttemptSettings newSettings = - getResultAlgorithm() - .createNextAttempt(context, previousThrowable, previousResponse, previousSettings); - if (newSettings == null) { - newSettings = getTimedAlgorithm().createNextAttempt(context, previousSettings); - } - return newSettings; - } - - /** - * Returns {@code true} if another attempt should be made, or {@code false} otherwise. - * - * @param context the {@link RetryingContext} that can be used to determine whether another - * attempt should be made - * @param previousThrowable exception thrown by the previous attempt or null if a result was - * returned instead - * @param previousResponse response returned by the previous attempt or null if an exception was - * thrown instead - * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if - * accepted - * @throws CancellationException if the retrying process should be cancelled - * @return {@code true} if another attempt should be made, or {@code false} otherwise - */ - public boolean shouldRetry( - RetryingContext context, - Throwable previousThrowable, - ResponseT previousResponse, - TimedAttemptSettings nextAttemptSettings) - throws CancellationException { - return getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse) - && nextAttemptSettings != null - && getTimedAlgorithm().shouldRetry(context, nextAttemptSettings); - } - - @Override - @Beta - public BasicResultRetryAlgorithm getResultAlgorithm() { - return (BasicResultRetryAlgorithm) super.getResultAlgorithm(); - } - - @Override - @Beta - public ExponentialRetryAlgorithm getTimedAlgorithm() { - return (ExponentialRetryAlgorithm) super.getTimedAlgorithm(); - } -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java deleted file mode 100644 index b2c3e1db5..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutor.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledExecutorService; - -/** - * Retry executor that uses {@link ScheduledExecutorService} to schedule attempt tasks. - * - *

This implementation does not manage the lifecycle of the underlying {@link - * ScheduledExecutorService}, so it should be managed outside of this class (like calling the {@link - * ScheduledExecutorService#shutdown()} when the pool is not needed anymore). In a typical usage - * pattern there are usually multiple instances of this class sharing the same instance of the - * underlying {@link ScheduledExecutorService}. - * - *

The executor uses a {@link ContextAwareRetryAlgorithm} to create attempt settings and to - * determine whether to retry an attempt. - * - *

This class is thread-safe. - * - * @param response type - */ -public class ContextAwareScheduledRetryingExecutor - extends ScheduledRetryingExecutor { - - public ContextAwareScheduledRetryingExecutor( - ContextAwareRetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { - super(retryAlgorithm, scheduler); - } - - @Override - public RetryingFuture createFuture( - Callable callable, RetryingContext context) { - return new ContextAwareCallbackChainRetryingFuture<>( - callable, (ContextAwareRetryAlgorithm) getRetryAlgorithm(), this, context); - } -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java index 46cf947b2..26beb8f0b 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ExponentialRetryAlgorithm.java @@ -41,7 +41,7 @@ * *

This class is thread-safe. */ -public class ExponentialRetryAlgorithm implements TimedRetryAlgorithm { +public class ExponentialRetryAlgorithm implements TimedRetryAlgorithmWithContext { private final RetrySettings globalSettings; private final ApiClock clock; @@ -85,6 +85,7 @@ public TimedAttemptSettings createFirstAttempt() { * retryable codes * @return first attempt settings */ + @Override public TimedAttemptSettings createFirstAttempt(RetryingContext context) { if (context.getRetrySettings() == null) { return createFirstAttempt(); @@ -175,6 +176,7 @@ public TimedAttemptSettings createNextAttempt(TimedAttemptSettings previousSetti * @param previousSettings previous attempt settings * @return next attempt settings */ + @Override public TimedAttemptSettings createNextAttempt( RetryingContext context, TimedAttemptSettings previousSettings) { // The RetrySettings from the context are not used here, as they have already been set as the @@ -240,6 +242,7 @@ public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or * totalTimeout limit, or {@code false} otherwise */ + @Override public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { // The RetrySettings from the context are not used here, as they have already been set as the // global settings during the creation of the initial attempt. diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index e2727b900..7f9db7185 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -31,7 +31,7 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; +import com.google.api.core.BetaApi; import java.util.concurrent.CancellationException; /** @@ -43,19 +43,41 @@ * @param response type */ public class RetryAlgorithm { - private final ResultRetryAlgorithm resultAlgorithm; - private final TimedRetryAlgorithm timedAlgorithm; + private final RetryAlgorithmWithContext resultAlgorithm; + private final TimedRetryAlgorithmWithContext timedAlgorithm; /** * Creates a new retry algorithm instance, which uses thrown exception or returned response and * timed algorithms to make a decision. The result algorithm has higher priority than the timed * algorithm. * + *

Instances that are created using this constructor will ignore the {@link RetryingContext} + * that is passed in to the retrying methods. Use {@link + * #RetryAlgorithm(RetryAlgorithmWithContext, TimedRetryAlgorithmWithContext)} to create an + * instance that will respect the {@link RetryingContext}. + * * @param resultAlgorithm result algorithm to use * @param timedAlgorithm timed algorithm to use */ public RetryAlgorithm( ResultRetryAlgorithm resultAlgorithm, TimedRetryAlgorithm timedAlgorithm) { + this.resultAlgorithm = + new IgnoreRetryingContextResultRetryAlgorithm<>(checkNotNull(resultAlgorithm)); + this.timedAlgorithm = + new IgnoreRetryingContextTimedRetryAlgorithm(checkNotNull(timedAlgorithm)); + } + + /** + * Creates a new retry algorithm instance, which uses thrown exception or returned response and + * timed algorithms to make a decision. The result algorithm has higher priority than the timed + * algorithm. + * + * @param resultAlgorithm result algorithm to use + * @param timedAlgorithm timed algorithm to use + */ + public RetryAlgorithm( + RetryAlgorithmWithContext resultAlgorithm, + TimedRetryAlgorithmWithContext timedAlgorithm) { this.resultAlgorithm = checkNotNull(resultAlgorithm); this.timedAlgorithm = checkNotNull(timedAlgorithm); } @@ -69,6 +91,17 @@ public TimedAttemptSettings createFirstAttempt() { return timedAlgorithm.createFirstAttempt(); } + /** + * Creates a first attempt {@link TimedAttemptSettings}. + * + * @param context the {@link RetryingContext} that can be used to get the initial {@link + * RetrySettings} + * @return first attempt settings + */ + public TimedAttemptSettings createFirstAttempt(RetryingContext context) { + return getTimedAlgorithm().createFirstAttempt(context); + } + /** * Creates a next attempt {@link TimedAttemptSettings}. This method will return first non-null * value, returned by either result or timed retry algorithms in that particular order. @@ -96,6 +129,39 @@ public TimedAttemptSettings createNextAttempt( return newSettings; } + /** + * Creates a next attempt {@link TimedAttemptSettings}. This method will return first non-null + * value, returned by either result or timed retry algorithms in that particular order. + * + * @param context the {@link RetryingContext} that can be used to determine the {@link + * RetrySettings} for the next attempt + * @param previousThrowable exception thrown by the previous attempt or null if a result was + * returned instead + * @param previousResponse response returned by the previous attempt or null if an exception was + * thrown instead + * @param previousSettings previous attempt settings + * @return next attempt settings, can be {@code null}, if there should be no new attempt + */ + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + // a small optimization that avoids calling relatively heavy methods + // like timedAlgorithm.createNextAttempt(), when it is not necessary. + if (!getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse)) { + return null; + } + + TimedAttemptSettings newSettings = + getResultAlgorithm() + .createNextAttempt(context, previousThrowable, previousResponse, previousSettings); + if (newSettings == null) { + newSettings = getTimedAlgorithm().createNextAttempt(context, previousSettings); + } + return newSettings; + } + /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * @@ -116,13 +182,111 @@ public boolean shouldRetry( && timedAlgorithm.shouldRetry(nextAttemptSettings); } - @Beta - public ResultRetryAlgorithm getResultAlgorithm() { + /** + * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * + * @param context the {@link RetryingContext} that can be used to determine whether another + * attempt should be made + * @param previousThrowable exception thrown by the previous attempt or null if a result was + * returned instead + * @param previousResponse response returned by the previous attempt or null if an exception was + * thrown instead + * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if + * accepted + * @throws CancellationException if the retrying process should be cancelled + * @return {@code true} if another attempt should be made, or {@code false} otherwise + */ + public boolean shouldRetry( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings nextAttemptSettings) + throws CancellationException { + return getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse) + && nextAttemptSettings != null + && getTimedAlgorithm().shouldRetry(context, nextAttemptSettings); + } + + @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + public RetryAlgorithmWithContext getResultAlgorithm() { return resultAlgorithm; } - @Beta - public TimedRetryAlgorithm getTimedAlgorithm() { + @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + public TimedRetryAlgorithmWithContext getTimedAlgorithm() { return timedAlgorithm; } + + private static class IgnoreRetryingContextResultRetryAlgorithm + implements RetryAlgorithmWithContext { + private final ResultRetryAlgorithm resultAlgorithm; + + IgnoreRetryingContextResultRetryAlgorithm(ResultRetryAlgorithm resultAlgorithm) { + this.resultAlgorithm = resultAlgorithm; + } + + public TimedAttemptSettings createNextAttempt( + Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { + return resultAlgorithm.createNextAttempt(prevThrowable, prevResponse, prevSettings); + } + + public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) + throws CancellationException { + return resultAlgorithm.shouldRetry(prevThrowable, prevResponse); + } + + @Override + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + return createNextAttempt(previousThrowable, previousResponse, previousSettings); + } + + @Override + public boolean shouldRetry( + RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) + throws CancellationException { + return shouldRetry(previousThrowable, previousResponse); + } + } + + private static class IgnoreRetryingContextTimedRetryAlgorithm + implements TimedRetryAlgorithmWithContext { + private final TimedRetryAlgorithm timedAlgorithm; + + IgnoreRetryingContextTimedRetryAlgorithm(TimedRetryAlgorithm timedAlgorithm) { + this.timedAlgorithm = timedAlgorithm; + } + + public TimedAttemptSettings createFirstAttempt() { + return timedAlgorithm.createFirstAttempt(); + } + + public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) { + return timedAlgorithm.createNextAttempt(prevSettings); + } + + public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) + throws CancellationException { + return timedAlgorithm.shouldRetry(nextAttemptSettings); + } + + @Override + public TimedAttemptSettings createFirstAttempt(RetryingContext context) { + return createFirstAttempt(); + } + + @Override + public TimedAttemptSettings createNextAttempt( + RetryingContext context, TimedAttemptSettings previousSettings) { + return createNextAttempt(previousSettings); + } + + @Override + public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { + return shouldRetry(nextAttemptSettings); + } + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithmWithContext.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithmWithContext.java new file mode 100644 index 000000000..9ce0fd906 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithmWithContext.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.CancellationException; + +/** + * A result retry algorithm is responsible for the following operations (based on the response + * returned by the previous attempt or on the thrown exception): + * + *

    + *
  1. Accepting a task for retry so another attempt will be made. + *
  2. Canceling retrying process so the related {@link java.util.concurrent.Future} will be + * canceled. + *
  3. Creating {@link TimedAttemptSettings} for each subsequent retry attempt. + *
+ * + * Implementations of this interface receive a {@link RetryingContext} that can contain specific + * {@link RetrySettings} and retryable codes that should be used to determine the retry behavior. + * + *

Implementations of this interface must be thread-safe. + * + * @param response type + */ +public interface RetryAlgorithmWithContext extends ResultRetryAlgorithm { + + /** + * Creates a next attempt {@link TimedAttemptSettings}. + * + * @param context the retrying context of this invocation that can be used to determine the + * settings for the next attempt. + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt + * @param previousSettings previous attempt settings + */ + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings); + + /** + * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * + * @param context the retrying context of this invocation that can be used to determine whether + * the call should be retried. + * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) + * @param previousResponse response returned by the previous attempt. + */ + public boolean shouldRetry( + RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) + throws CancellationException; +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java index 52d58f9c7..a64c41acb 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java @@ -43,8 +43,10 @@ */ @InternalApi("For internal use only") public final class StreamingRetryAlgorithm extends RetryAlgorithm { + public StreamingRetryAlgorithm( - ResultRetryAlgorithm resultAlgorithm, TimedRetryAlgorithm timedAlgorithm) { + RetryAlgorithmWithContext resultAlgorithm, + TimedRetryAlgorithmWithContext timedAlgorithm) { super(resultAlgorithm, timedAlgorithm); } @@ -76,6 +78,37 @@ public TimedAttemptSettings createNextAttempt( return super.createNextAttempt(prevThrowable, prevResponse, prevSettings); } + /** + * {@inheritDoc} + * + *

The attempt settings will be reset if the stream attempt produced any messages. + */ + @Override + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + + if (previousThrowable instanceof ServerStreamingAttemptException) { + ServerStreamingAttemptException attemptException = + (ServerStreamingAttemptException) previousThrowable; + previousThrowable = previousThrowable.getCause(); + + // If we have made progress in the last attempt, then reset the delays + if (attemptException.hasSeenResponses()) { + previousSettings = + createFirstAttempt(context) + .toBuilder() + .setFirstAttemptStartTimeNanos(previousSettings.getFirstAttemptStartTimeNanos()) + .setOverallAttemptCount(previousSettings.getOverallAttemptCount()) + .build(); + } + } + + return super.createNextAttempt(context, previousThrowable, previousResponse, previousSettings); + } + /** * {@inheritDoc} * @@ -100,4 +133,32 @@ public boolean shouldRetry( return super.shouldRetry(prevThrowable, prevResponse, nextAttemptSettings); } + + /** + * {@inheritDoc} + * + *

Schedules retries only if the {@link StreamResumptionStrategy} in the {@code + * ServerStreamingAttemptCallable} supports it. + */ + @Override + public boolean shouldRetry( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings nextAttemptSettings) + throws CancellationException { + + // Unwrap + if (previousThrowable instanceof ServerStreamingAttemptException) { + ServerStreamingAttemptException attemptException = + (ServerStreamingAttemptException) previousThrowable; + previousThrowable = previousThrowable.getCause(); + + if (!attemptException.canResume()) { + return false; + } + } + + return super.shouldRetry(context, previousThrowable, previousResponse, nextAttemptSettings); + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithmWithContext.java b/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithmWithContext.java new file mode 100644 index 000000000..72efda7f4 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithmWithContext.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.retrying; + +import java.util.concurrent.CancellationException; + +/** + * A timed retry algorithm is responsible for the following operations, based on the previous + * attempt settings and current time: + * + *

    + *
  1. Creating first attempt {@link TimedAttemptSettings}. + *
  2. Accepting a task for retry so another attempt will be made. + *
  3. Canceling retrying process so the related {@link java.util.concurrent.Future} will be + * canceled. + *
  4. Creating {@link TimedAttemptSettings} for each subsequent retry attempt. + *
+ * + * Implementations of this interface receive a {@link RetryingContext} that can contain specific + * {@link RetrySettings} and retryable codes that should be used to determine the retry behavior. + * + *

Implementations of this interface must be be thread-save. + */ +public interface TimedRetryAlgorithmWithContext extends TimedRetryAlgorithm { + + /** + * Creates a first attempt {@link TimedAttemptSettings}. + * + * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and + * retryable codes + * @return first attempt settings + */ + TimedAttemptSettings createFirstAttempt(RetryingContext context); + + /** + * Creates a next attempt {@link TimedAttemptSettings}, which defines properties of the next + * attempt. + * + * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and + * retryable codes + * @param previousSettings previous attempt settings + * @return next attempt settings or {@code null} if the implementing algorithm does not provide + * specific settings for the next attempt + */ + TimedAttemptSettings createNextAttempt( + RetryingContext context, TimedAttemptSettings previousSettings); + + /** + * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * + * @param context a {@link RetryingContext} that can contain custom {@link RetrySettings} and + * retryable codes. + * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if + * accepted + * @throws CancellationException if the retrying process should be canceled + */ + boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings); +} diff --git a/gax/src/main/java/com/google/api/gax/rpc/Callables.java b/gax/src/main/java/com/google/api/gax/rpc/Callables.java index d38e1351a..a1c34a02c 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/Callables.java +++ b/gax/src/main/java/com/google/api/gax/rpc/Callables.java @@ -33,12 +33,11 @@ import com.google.api.gax.batching.BatchingSettings; import com.google.api.gax.longrunning.OperationResponsePollAlgorithm; import com.google.api.gax.longrunning.OperationSnapshot; -import com.google.api.gax.retrying.ContextAwareRetryAlgorithm; -import com.google.api.gax.retrying.ContextAwareScheduledRetryingExecutor; import com.google.api.gax.retrying.ExponentialRetryAlgorithm; import com.google.api.gax.retrying.RetryAlgorithm; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.retrying.ScheduledRetryingExecutor; +import com.google.api.gax.retrying.StreamingRetryAlgorithm; import java.util.Collection; /** @@ -65,13 +64,13 @@ public static UnaryCallable retrying( .withTimeout(callSettings.getRetrySettings().getTotalTimeout())); } - ContextAwareRetryAlgorithm retryAlgorithm = - new ContextAwareRetryAlgorithm<>( + RetryAlgorithm retryAlgorithm = + new RetryAlgorithm<>( new ApiResultRetryAlgorithm(), new ExponentialRetryAlgorithm( callSettings.getRetrySettings(), clientContext.getClock())); - ContextAwareScheduledRetryingExecutor retryingExecutor = - new ContextAwareScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); + ScheduledRetryingExecutor retryingExecutor = + new ScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); return new RetryingCallable<>( clientContext.getDefaultCallContext(), innerCallable, retryingExecutor); } @@ -90,14 +89,14 @@ public static ServerStreamingCallable .withTimeout(callSettings.getRetrySettings().getTotalTimeout())); } - ContextAwareStreamingRetryAlgorithm retryAlgorithm = - new ContextAwareStreamingRetryAlgorithm<>( + StreamingRetryAlgorithm retryAlgorithm = + new StreamingRetryAlgorithm<>( new ApiResultRetryAlgorithm(), new ExponentialRetryAlgorithm( callSettings.getRetrySettings(), clientContext.getClock())); - ContextAwareScheduledRetryingExecutor retryingExecutor = - new ContextAwareScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); + ScheduledRetryingExecutor retryingExecutor = + new ScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); return new RetryingServerStreamingCallable<>( innerCallable, retryingExecutor, callSettings.getResumptionStrategy()); diff --git a/gax/src/main/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithm.java deleted file mode 100644 index cd884d81a..000000000 --- a/gax/src/main/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithm.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.rpc; - -import com.google.api.gax.retrying.BasicResultRetryAlgorithm; -import com.google.api.gax.retrying.ContextAwareRetryAlgorithm; -import com.google.api.gax.retrying.ExponentialRetryAlgorithm; -import com.google.api.gax.retrying.RetryAlgorithm; -import com.google.api.gax.retrying.RetryingContext; -import com.google.api.gax.retrying.ServerStreamingAttemptException; -import com.google.api.gax.retrying.StreamResumptionStrategy; -import com.google.api.gax.retrying.TimedAttemptSettings; -import java.util.concurrent.CancellationException; - -/** - * Streaming retry algorithm that decides based on the thrown exception and the execution time - * settings of the previous attempt. This extends {@link RetryAlgorithm} to take additional - * information (provided by {@code ServerStreamingAttemptCallable}) into account. - * - *

This class is thread-safe. - */ -final class ContextAwareStreamingRetryAlgorithm - extends ContextAwareRetryAlgorithm { - public ContextAwareStreamingRetryAlgorithm( - BasicResultRetryAlgorithm resultAlgorithm, - ExponentialRetryAlgorithm timedAlgorithm) { - super(resultAlgorithm, timedAlgorithm); - } - - /** - * {@inheritDoc} - * - *

The attempt settings will be reset if the stream attempt produced any messages. - */ - @Override - public TimedAttemptSettings createNextAttempt( - RetryingContext context, - Throwable previousThrowable, - ResponseT previousResponse, - TimedAttemptSettings previousSettings) { - - if (previousThrowable instanceof ServerStreamingAttemptException) { - ServerStreamingAttemptException attemptException = - (ServerStreamingAttemptException) previousThrowable; - previousThrowable = previousThrowable.getCause(); - - // If we have made progress in the last attempt, then reset the delays - if (attemptException.hasSeenResponses()) { - previousSettings = - createFirstAttempt(context) - .toBuilder() - .setFirstAttemptStartTimeNanos(previousSettings.getFirstAttemptStartTimeNanos()) - .setOverallAttemptCount(previousSettings.getOverallAttemptCount()) - .build(); - } - } - - return super.createNextAttempt(context, previousThrowable, previousResponse, previousSettings); - } - - /** - * {@inheritDoc} - * - *

Schedules retries only if the {@link StreamResumptionStrategy} in the {@code - * ServerStreamingAttemptCallable} supports it. - */ - @Override - public boolean shouldRetry( - RetryingContext context, - Throwable previousThrowable, - ResponseT previousResponse, - TimedAttemptSettings nextAttemptSettings) - throws CancellationException { - - // Unwrap - if (previousThrowable instanceof ServerStreamingAttemptException) { - ServerStreamingAttemptException attemptException = - (ServerStreamingAttemptException) previousThrowable; - previousThrowable = previousThrowable.getCause(); - - if (!attemptException.canResume()) { - return false; - } - } - - return super.shouldRetry(context, previousThrowable, previousResponse, nextAttemptSettings); - } -} diff --git a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java index a71611d60..0c352360b 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java @@ -274,8 +274,8 @@ public void testPollExceptionByPollAlgorithm() throws Exception { .build(); boolean useContextRetrySettings = retryingContext.getRetrySettings() != null; - ContextAwareRetryAlgorithm retryAlgorithm = - new ContextAwareRetryAlgorithm<>( + RetryAlgorithm retryAlgorithm = + new RetryAlgorithm<>( new TestResultRetryAlgorithm(0, null), new ExponentialPollAlgorithm( useContextRetrySettings ? getDefaultRetrySettings() : retrySettings, diff --git a/gax/src/test/java/com/google/api/gax/retrying/BasicRetryingFutureTest.java b/gax/src/test/java/com/google/api/gax/retrying/BasicRetryingFutureTest.java index 72c753915..c95cda7eb 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/BasicRetryingFutureTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/BasicRetryingFutureTest.java @@ -71,15 +71,18 @@ public void testHandleAttemptDoesNotThrowNPEWhenLogLevelLowerThanFiner() throws Mockito.when(retryingContext.getTracer()).thenReturn(tracer); - Mockito.when(retryAlgorithm.createFirstAttempt()).thenReturn(timedAttemptSettings); + Mockito.when(retryAlgorithm.createFirstAttempt(ArgumentMatchers.any())) + .thenReturn(timedAttemptSettings); Mockito.when( retryAlgorithm.createNextAttempt( + ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) .thenReturn(timedAttemptSettings); Mockito.when( retryAlgorithm.shouldRetry( + ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) @@ -97,6 +100,55 @@ public void testHandleAttemptDoesNotThrowNPEWhenLogLevelLowerThanFiner() throws Mockito.verifyNoMoreInteractions(tracer); } + @Test + public void testUsesRetryingContext() throws Exception { + @SuppressWarnings("unchecked") + Callable callable = mock(Callable.class); + @SuppressWarnings("unchecked") + RetryAlgorithm retryAlgorithm = mock(RetryAlgorithm.class); + RetryingContext retryingContext = mock(RetryingContext.class); + ApiTracer tracer = mock(ApiTracer.class); + TimedAttemptSettings timedAttemptSettings = mock(TimedAttemptSettings.class); + Mockito.when(retryingContext.getTracer()).thenReturn(tracer); + + Mockito.when(retryAlgorithm.createFirstAttempt(retryingContext)) + .thenReturn(timedAttemptSettings); + Mockito.when( + retryAlgorithm.createNextAttempt( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any())) + .thenReturn(timedAttemptSettings); + Mockito.when( + retryAlgorithm.shouldRetry( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any())) + .thenReturn(true); + + BasicRetryingFuture future = + new BasicRetryingFuture<>(callable, retryAlgorithm, retryingContext); + + future.handleAttempt(null, null); + + Mockito.verify(retryAlgorithm).createFirstAttempt(retryingContext); + Mockito.verify(retryAlgorithm) + .createNextAttempt( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any()); + Mockito.verify(retryAlgorithm) + .shouldRetry( + ArgumentMatchers.eq(retryingContext), + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any()); + Mockito.verifyNoMoreInteractions(retryAlgorithm); + } + private Logger getLoggerInstance() throws NoSuchFieldException, IllegalAccessException { Field logger = BasicRetryingFuture.class.getDeclaredField("LOG"); logger.setAccessible(true); diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java b/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java deleted file mode 100644 index 7aecb8913..000000000 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareBasicRetryingFutureTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import static org.mockito.Mockito.mock; - -import com.google.api.gax.tracing.ApiTracer; -import java.util.concurrent.Callable; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; - -@RunWith(JUnit4.class) -public class ContextAwareBasicRetryingFutureTest { - - @Test - public void testUsesRetryingContext() throws Exception { - @SuppressWarnings("unchecked") - Callable callable = mock(Callable.class); - @SuppressWarnings("unchecked") - ContextAwareRetryAlgorithm retryAlgorithm = mock(ContextAwareRetryAlgorithm.class); - RetryingContext retryingContext = mock(RetryingContext.class); - ApiTracer tracer = mock(ApiTracer.class); - TimedAttemptSettings timedAttemptSettings = mock(TimedAttemptSettings.class); - Mockito.when(retryingContext.getTracer()).thenReturn(tracer); - - Mockito.when(retryAlgorithm.createFirstAttempt(retryingContext)) - .thenReturn(timedAttemptSettings); - Mockito.when( - retryAlgorithm.createNextAttempt( - ArgumentMatchers.eq(retryingContext), - ArgumentMatchers.any(), - ArgumentMatchers.any(), - ArgumentMatchers.any())) - .thenReturn(timedAttemptSettings); - Mockito.when( - retryAlgorithm.shouldRetry( - ArgumentMatchers.eq(retryingContext), - ArgumentMatchers.any(), - ArgumentMatchers.any(), - ArgumentMatchers.any())) - .thenReturn(true); - - ContextAwareBasicRetryingFuture future = - new ContextAwareBasicRetryingFuture<>(callable, retryAlgorithm, retryingContext); - - future.handleAttempt(null, null); - - Mockito.verify(retryAlgorithm).createFirstAttempt(retryingContext); - Mockito.verify(retryAlgorithm) - .createNextAttempt( - ArgumentMatchers.eq(retryingContext), - ArgumentMatchers.any(), - ArgumentMatchers.any(), - ArgumentMatchers.any()); - Mockito.verify(retryAlgorithm) - .shouldRetry( - ArgumentMatchers.eq(retryingContext), - ArgumentMatchers.any(), - ArgumentMatchers.any(), - ArgumentMatchers.any()); - Mockito.verifyNoMoreInteractions(retryAlgorithm); - } -} diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorWithRetrySettingsTest.java similarity index 89% rename from gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java rename to gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorWithRetrySettingsTest.java index f1314c0ca..459655404 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareDirectRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorWithRetrySettingsTest.java @@ -39,7 +39,7 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class ContextAwareDirectRetryingExecutorTest extends AbstractRetryingExecutorTest { +public class DirectRetryingExecutorWithRetrySettingsTest extends AbstractRetryingExecutorTest { @Override @Before @@ -50,14 +50,13 @@ public void setUp() { @Override protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { - return new ContextAwareDirectRetryingExecutor<>( - (ContextAwareRetryAlgorithm) retryAlgorithm); + return new DirectRetryingExecutor<>(retryAlgorithm); } @Override - protected ContextAwareRetryAlgorithm getAlgorithm( + protected RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException) { - return new ContextAwareRetryAlgorithm<>( + return new RetryAlgorithm<>( new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), new ExponentialRetryAlgorithm(retrySettings, CurrentMillisClock.getDefaultClock())); } diff --git a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorWithRetrySettingsTest.java similarity index 96% rename from gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java rename to gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorWithRetrySettingsTest.java index 0c8fe48b5..2c537f336 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ContextAwareScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorWithRetrySettingsTest.java @@ -58,7 +58,7 @@ import org.threeten.bp.Duration; @RunWith(MockitoJUnitRunner.class) -public class ContextAwareScheduledRetryingExecutorTest extends AbstractRetryingExecutorTest { +public class ScheduledRetryingExecutorWithRetrySettingsTest extends AbstractRetryingExecutorTest { private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); // Number of test runs, essential for multithreaded tests. @@ -73,13 +73,13 @@ public void setUp() { @Override protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { - return getRetryingExecutor((ContextAwareRetryAlgorithm) retryAlgorithm, scheduler); + return getRetryingExecutor(retryAlgorithm, scheduler); } @Override - protected ContextAwareRetryAlgorithm getAlgorithm( + protected RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException) { - return new ContextAwareRetryAlgorithm<>( + return new RetryAlgorithm<>( new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), new ExponentialRetryAlgorithm(retrySettings, NanoClock.getDefaultClock())); } @@ -90,8 +90,8 @@ protected RetrySettings getDefaultRetrySettings() { } private RetryingExecutorWithContext getRetryingExecutor( - ContextAwareRetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { - return new ContextAwareScheduledRetryingExecutor<>(retryAlgorithm, scheduler); + RetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { + return new ScheduledRetryingExecutor<>(retryAlgorithm, scheduler); } @After diff --git a/gax/src/test/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmWithRetrySettingsTest.java similarity index 89% rename from gax/src/test/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithmTest.java rename to gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmWithRetrySettingsTest.java index 8a36bdc72..36e4f391d 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/ContextAwareStreamingRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmWithRetrySettingsTest.java @@ -40,6 +40,7 @@ import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.retrying.RetryingContext; import com.google.api.gax.retrying.ServerStreamingAttemptException; +import com.google.api.gax.retrying.StreamingRetryAlgorithm; import com.google.api.gax.retrying.TimedAttemptSettings; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,7 +49,7 @@ import org.threeten.bp.Duration; @RunWith(JUnit4.class) -public class ContextAwareStreamingRetryAlgorithmTest { +public class StreamingRetryAlgorithmWithRetrySettingsTest { private static final RetrySettings DEFAULT_RETRY_SETTINGS = RetrySettings.newBuilder() .setInitialRetryDelay(Duration.ofMillis(10L)) @@ -80,8 +81,8 @@ public void testFirstAttemptUsesDefaultSettings() { ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); - ContextAwareStreamingRetryAlgorithm algorithm = - new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + StreamingRetryAlgorithm algorithm = + new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); TimedAttemptSettings attempt = algorithm.createFirstAttempt(context); assertThat(attempt.getGlobalSettings()).isSameInstanceAs(DEFAULT_RETRY_SETTINGS); @@ -96,8 +97,8 @@ public void testFirstAttemptUsesContextSettings() { ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); - ContextAwareStreamingRetryAlgorithm algorithm = - new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + StreamingRetryAlgorithm algorithm = + new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); TimedAttemptSettings attempt = algorithm.createFirstAttempt(context); assertThat(attempt.getGlobalSettings()).isSameInstanceAs(CONTEXT_RETRY_SETTINGS); @@ -114,8 +115,8 @@ public void testNextAttemptReturnsNullWhenShouldNotRetry() { ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); - ContextAwareStreamingRetryAlgorithm algorithm = - new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + StreamingRetryAlgorithm algorithm = + new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); TimedAttemptSettings attempt = algorithm.createNextAttempt(context, exception, null, mock(TimedAttemptSettings.class)); @@ -139,8 +140,8 @@ public void testNextAttemptReturnsResultAlgorithmSettingsWhenShouldRetry() { ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); - ContextAwareStreamingRetryAlgorithm algorithm = - new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + StreamingRetryAlgorithm algorithm = + new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); TimedAttemptSettings first = algorithm.createFirstAttempt(context); TimedAttemptSettings attempt = algorithm.createNextAttempt(context, exception, null, first); @@ -160,8 +161,8 @@ public void testNextAttemptResetsTimedSettings() { ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); - ContextAwareStreamingRetryAlgorithm algorithm = - new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + StreamingRetryAlgorithm algorithm = + new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); TimedAttemptSettings first = algorithm.createFirstAttempt(context); TimedAttemptSettings second = @@ -186,8 +187,8 @@ public void testShouldNotRetryIfAttemptIsNonResumable() { ExponentialRetryAlgorithm timedAlgorithm = new ExponentialRetryAlgorithm(DEFAULT_RETRY_SETTINGS, mock(ApiClock.class)); - ContextAwareStreamingRetryAlgorithm algorithm = - new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + StreamingRetryAlgorithm algorithm = + new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); // This should return false because the attempt exception indicates that it is non-resumable. boolean shouldRetry = @@ -209,8 +210,8 @@ public void testShouldRetryIfAllSayYes() { ExponentialRetryAlgorithm timedAlgorithm = mock(ExponentialRetryAlgorithm.class); when(timedAlgorithm.shouldRetry(Mockito.eq(context), any(TimedAttemptSettings.class))) .thenReturn(true); - ContextAwareStreamingRetryAlgorithm algorithm = - new ContextAwareStreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + StreamingRetryAlgorithm algorithm = + new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm); boolean shouldRetry = algorithm.shouldRetry(context, exception, null, mock(TimedAttemptSettings.class)); From 5c90e70cca2afe51ba3d0b602f28c1663966755e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 1 Mar 2021 12:40:24 +0100 Subject: [PATCH 15/23] chore: cleanup and remove unnecessary methods --- .../google/api/gax/grpc/GrpcCallContext.java | 8 +- .../api/gax/httpjson/HttpJsonCallContext.java | 4 + .../retrying/BasicResultRetryAlgorithm.java | 14 +-- .../api/gax/retrying/BasicRetryingFuture.java | 14 +-- .../gax/retrying/DirectRetryingExecutor.java | 5 - ...reRetryingContextResultRetryAlgorithm.java | 67 ++++++++++++ ...oreRetryingContextTimedRetryAlgorithm.java | 69 ++++++++++++ ...a => ResultRetryAlgorithmWithContext.java} | 3 +- .../api/gax/retrying/RetryAlgorithm.java | 100 ++++-------------- .../api/gax/retrying/RetryingContext.java | 4 +- .../retrying/ScheduledRetryingExecutor.java | 5 - .../gax/retrying/StreamingRetryAlgorithm.java | 21 +++- .../google/api/gax/rpc/ApiCallContext.java | 18 ++-- .../ExponentialRetryAlgorithmTest.java | 11 +- .../api/gax/retrying/FailingCallable.java | 2 - 15 files changed, 206 insertions(+), 139 deletions(-) create mode 100644 gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java create mode 100644 gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java rename gax/src/main/java/com/google/api/gax/retrying/{RetryAlgorithmWithContext.java => ResultRetryAlgorithmWithContext.java} (97%) diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java index ebd395b92..b7fb63027 100644 --- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallContext.java @@ -249,7 +249,7 @@ public GrpcCallContext withChannelAffinity(@Nullable Integer affinity) { this.retryableCodes); } - @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") + @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @Override public GrpcCallContext withExtraHeaders(Map> extraHeaders) { Preconditions.checkNotNull(extraHeaders); @@ -407,7 +407,7 @@ public CallOptions getCallOptions() { * * @see ApiCallContext#withStreamWaitTimeout(Duration) */ - @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") + @BetaApi("The surface for streaming is not stable yet and may change in the future.") @Nullable public Duration getStreamWaitTimeout() { return streamWaitTimeout; @@ -418,7 +418,7 @@ public Duration getStreamWaitTimeout() { * * @see ApiCallContext#withStreamIdleTimeout(Duration) */ - @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") + @BetaApi("The surface for streaming is not stable yet and may change in the future.") @Nullable public Duration getStreamIdleTimeout() { return streamIdleTimeout; @@ -432,7 +432,7 @@ public Integer getChannelAffinity() { } /** The extra header for this context. */ - @BetaApi("The surface for channel affinity is not stable yet and may change in the future.") + @BetaApi("The surface for extra headers is not stable yet and may change in the future.") @Override public Map> getExtraHeaders() { return this.extraHeaders; diff --git a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java index 75dbcb42f..298499779 100644 --- a/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java +++ b/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallContext.java @@ -288,10 +288,12 @@ public Credentials getCredentials() { return credentials; } + @Override public RetrySettings getRetrySettings() { return retrySettings; } + @Override public HttpJsonCallContext withRetrySettings(RetrySettings retrySettings) { return new HttpJsonCallContext( this.channel, @@ -304,10 +306,12 @@ public HttpJsonCallContext withRetrySettings(RetrySettings retrySettings) { this.retryableCodes); } + @Override public Set getRetryableCodes() { return retryableCodes; } + @Override public HttpJsonCallContext withRetryableCodes(Set retryableCodes) { return new HttpJsonCallContext( this.channel, diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index 37fa9602e..27c802d4c 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -32,15 +32,14 @@ import java.util.concurrent.CancellationException; /** - * A {@link ResultRetryAlgorithm} that can use a {@link RetryingContext} to determine whether to - * retry a call. - * - *

This implementation retries all exceptions, all responses are accepted (including {@code - * null}) and no retrying process will ever be cancelled. + * A basic implementation of {@link ResultRetryAlgorithm}. Using this implementation would mean that + * all exceptions should be retried, all responses should be accepted (including {@code null}) and + * no retrying process should ever be canceled. * * @param attempt response type */ -public class BasicResultRetryAlgorithm implements RetryAlgorithmWithContext { +public class BasicResultRetryAlgorithm + implements ResultRetryAlgorithmWithContext { /** * Always returns null, indicating that this algorithm does not provide any specific settings for * the next attempt. @@ -69,9 +68,6 @@ public TimedAttemptSettings createNextAttempt( /** * Returns {@code true} if an exception was thrown ({@code previousThrowable != null}), {@code * false} otherwise. - * - * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param previousResponse ignored */ @Override public boolean shouldRetry(Throwable previousThrowable, ResponseT previousResponse) { diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index 9236ba08f..6741b3c5f 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -88,10 +88,6 @@ class BasicRetryingFuture extends AbstractFuture super.addListener(new CompletionListener(), MoreExecutors.directExecutor()); } - RetryAlgorithm getRetryAlgorithm() { - return this.retryAlgorithm; - } - @Override public void setAttemptFuture(ApiFuture attemptFuture) { try { @@ -130,6 +126,7 @@ public ApiFuture peekAttemptResult() { // heavy (and in most cases redundant) settable future instantiation on each attempt, plus reduces // possibility of callback chaining going into an infinite loop in case of buggy external // callbacks implementation. + @Override public ApiFuture getAttemptResult() { synchronized (lock) { if (attemptResult == null) { @@ -196,7 +193,7 @@ void handleAttempt(Throwable throwable, ResponseT response) { // a new attempt will be (must be) scheduled by an external executor } else if (throwable != null) { if (retryAlgorithm - .getResultAlgorithm() + .getResultAlgorithmWithContext() .shouldRetry(retryingContext, throwable, response)) { tracer.attemptFailedRetriesExhausted(throwable); } else { @@ -246,13 +243,6 @@ boolean shouldRetry( return retryAlgorithm.shouldRetry(context, throwable, response, nextAttemptSettings); } - // Calls retryAlgorithm.getResultAlgorithm().shouldRetry(throwable, response) for the basic - // implementation. May be overridden by subclasses that can use the RetryingContext to determine - // whether the call should be retried. - boolean shouldRetryOnResult(RetryingContext context, Throwable throwable, ResponseT response) { - return retryAlgorithm.getResultAlgorithm().shouldRetry(context, throwable, response); - } - // Sets attempt result futures. Note the "attempt result future" and "attempt future" are not same // things because there are more attempt futures than attempt result futures. // See AttemptCallable.call() for an example of such condition. diff --git a/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java index b09830ac9..c024bda77 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/DirectRetryingExecutor.java @@ -125,9 +125,4 @@ protected void sleep(Duration delay) throws InterruptedException { Thread.sleep(delay.toMillis()); } } - - /** Returns the retry algorithm that is used by this executor. */ - protected RetryAlgorithm getRetryAlgorithm() { - return retryAlgorithm; - } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java new file mode 100644 index 000000000..4d815828c --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.CancellationException; + +class IgnoreRetryingContextResultRetryAlgorithm + implements ResultRetryAlgorithmWithContext { + private final ResultRetryAlgorithm resultAlgorithm; + + IgnoreRetryingContextResultRetryAlgorithm(ResultRetryAlgorithm resultAlgorithm) { + this.resultAlgorithm = resultAlgorithm; + } + + public TimedAttemptSettings createNextAttempt( + Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { + return resultAlgorithm.createNextAttempt(prevThrowable, prevResponse, prevSettings); + } + + public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) + throws CancellationException { + return resultAlgorithm.shouldRetry(prevThrowable, prevResponse); + } + + @Override + public TimedAttemptSettings createNextAttempt( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + return createNextAttempt(previousThrowable, previousResponse, previousSettings); + } + + @Override + public boolean shouldRetry( + RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) + throws CancellationException { + return shouldRetry(previousThrowable, previousResponse); + } +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java new file mode 100644 index 000000000..7d147079e --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import java.util.concurrent.CancellationException; + +class IgnoreRetryingContextTimedRetryAlgorithm implements TimedRetryAlgorithmWithContext { + private final TimedRetryAlgorithm timedAlgorithm; + + IgnoreRetryingContextTimedRetryAlgorithm(TimedRetryAlgorithm timedAlgorithm) { + this.timedAlgorithm = timedAlgorithm; + } + + public TimedAttemptSettings createFirstAttempt() { + return timedAlgorithm.createFirstAttempt(); + } + + public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) { + return timedAlgorithm.createNextAttempt(prevSettings); + } + + public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) + throws CancellationException { + return timedAlgorithm.shouldRetry(nextAttemptSettings); + } + + @Override + public TimedAttemptSettings createFirstAttempt(RetryingContext context) { + return createFirstAttempt(); + } + + @Override + public TimedAttemptSettings createNextAttempt( + RetryingContext context, TimedAttemptSettings previousSettings) { + return createNextAttempt(previousSettings); + } + + @Override + public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { + return shouldRetry(nextAttemptSettings); + } +} diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithmWithContext.java b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithmWithContext.java similarity index 97% rename from gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithmWithContext.java rename to gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithmWithContext.java index 9ce0fd906..ab52f8d58 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithmWithContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithmWithContext.java @@ -49,7 +49,8 @@ * * @param response type */ -public interface RetryAlgorithmWithContext extends ResultRetryAlgorithm { +public interface ResultRetryAlgorithmWithContext + extends ResultRetryAlgorithm { /** * Creates a next attempt {@link TimedAttemptSettings}. diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index 7f9db7185..a2cd59018 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -43,7 +43,7 @@ * @param response type */ public class RetryAlgorithm { - private final RetryAlgorithmWithContext resultAlgorithm; + private final ResultRetryAlgorithmWithContext resultAlgorithm; private final TimedRetryAlgorithmWithContext timedAlgorithm; /** @@ -53,7 +53,7 @@ public class RetryAlgorithm { * *

Instances that are created using this constructor will ignore the {@link RetryingContext} * that is passed in to the retrying methods. Use {@link - * #RetryAlgorithm(RetryAlgorithmWithContext, TimedRetryAlgorithmWithContext)} to create an + * #RetryAlgorithm(ResultRetryAlgorithmWithContext, TimedRetryAlgorithmWithContext)} to create an * instance that will respect the {@link RetryingContext}. * * @param resultAlgorithm result algorithm to use @@ -76,7 +76,7 @@ public RetryAlgorithm( * @param timedAlgorithm timed algorithm to use */ public RetryAlgorithm( - RetryAlgorithmWithContext resultAlgorithm, + ResultRetryAlgorithmWithContext resultAlgorithm, TimedRetryAlgorithmWithContext timedAlgorithm) { this.resultAlgorithm = checkNotNull(resultAlgorithm); this.timedAlgorithm = checkNotNull(timedAlgorithm); @@ -99,7 +99,7 @@ public TimedAttemptSettings createFirstAttempt() { * @return first attempt settings */ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return getTimedAlgorithm().createFirstAttempt(context); + return getTimedAlgorithmWithContext().createFirstAttempt(context); } /** @@ -149,15 +149,16 @@ public TimedAttemptSettings createNextAttempt( TimedAttemptSettings previousSettings) { // a small optimization that avoids calling relatively heavy methods // like timedAlgorithm.createNextAttempt(), when it is not necessary. - if (!getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse)) { + if (!getResultAlgorithmWithContext() + .shouldRetry(context, previousThrowable, previousResponse)) { return null; } TimedAttemptSettings newSettings = - getResultAlgorithm() + getResultAlgorithmWithContext() .createNextAttempt(context, previousThrowable, previousResponse, previousSettings); if (newSettings == null) { - newSettings = getTimedAlgorithm().createNextAttempt(context, previousSettings); + newSettings = getTimedAlgorithmWithContext().createNextAttempt(context, previousSettings); } return newSettings; } @@ -202,91 +203,28 @@ public boolean shouldRetry( ResponseT previousResponse, TimedAttemptSettings nextAttemptSettings) throws CancellationException { - return getResultAlgorithm().shouldRetry(context, previousThrowable, previousResponse) + return getResultAlgorithmWithContext().shouldRetry(context, previousThrowable, previousResponse) && nextAttemptSettings != null - && getTimedAlgorithm().shouldRetry(context, nextAttemptSettings); + && getTimedAlgorithmWithContext().shouldRetry(context, nextAttemptSettings); } @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - public RetryAlgorithmWithContext getResultAlgorithm() { + public ResultRetryAlgorithm getResultAlgorithm() { return resultAlgorithm; } @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - public TimedRetryAlgorithmWithContext getTimedAlgorithm() { - return timedAlgorithm; + public ResultRetryAlgorithmWithContext getResultAlgorithmWithContext() { + return resultAlgorithm; } - private static class IgnoreRetryingContextResultRetryAlgorithm - implements RetryAlgorithmWithContext { - private final ResultRetryAlgorithm resultAlgorithm; - - IgnoreRetryingContextResultRetryAlgorithm(ResultRetryAlgorithm resultAlgorithm) { - this.resultAlgorithm = resultAlgorithm; - } - - public TimedAttemptSettings createNextAttempt( - Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { - return resultAlgorithm.createNextAttempt(prevThrowable, prevResponse, prevSettings); - } - - public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) - throws CancellationException { - return resultAlgorithm.shouldRetry(prevThrowable, prevResponse); - } - - @Override - public TimedAttemptSettings createNextAttempt( - RetryingContext context, - Throwable previousThrowable, - ResponseT previousResponse, - TimedAttemptSettings previousSettings) { - return createNextAttempt(previousThrowable, previousResponse, previousSettings); - } - - @Override - public boolean shouldRetry( - RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) - throws CancellationException { - return shouldRetry(previousThrowable, previousResponse); - } + @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + public TimedRetryAlgorithm getTimedAlgorithm() { + return timedAlgorithm; } - private static class IgnoreRetryingContextTimedRetryAlgorithm - implements TimedRetryAlgorithmWithContext { - private final TimedRetryAlgorithm timedAlgorithm; - - IgnoreRetryingContextTimedRetryAlgorithm(TimedRetryAlgorithm timedAlgorithm) { - this.timedAlgorithm = timedAlgorithm; - } - - public TimedAttemptSettings createFirstAttempt() { - return timedAlgorithm.createFirstAttempt(); - } - - public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) { - return timedAlgorithm.createNextAttempt(prevSettings); - } - - public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) - throws CancellationException { - return timedAlgorithm.shouldRetry(nextAttemptSettings); - } - - @Override - public TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return createFirstAttempt(); - } - - @Override - public TimedAttemptSettings createNextAttempt( - RetryingContext context, TimedAttemptSettings previousSettings) { - return createNextAttempt(previousSettings); - } - - @Override - public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { - return shouldRetry(nextAttemptSettings); - } + @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") + public TimedRetryAlgorithmWithContext getTimedAlgorithmWithContext() { + return timedAlgorithm; } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java index 68f094173..6db53c4e2 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryingContext.java @@ -29,9 +29,9 @@ */ package com.google.api.gax.retrying; +import com.google.api.core.BetaApi; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.tracing.ApiTracer; -import com.google.common.annotations.Beta; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -41,7 +41,7 @@ * *

It provides state to individual {@link RetryingFuture}s via the {@link RetryingExecutor}. */ -@Beta +@BetaApi("The surface for passing per operation state is not yet stable") public interface RetryingContext { /** Returns the {@link ApiTracer} associated with the current operation. */ @Nonnull diff --git a/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java b/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java index b8f1ecd17..b6ee0a0c1 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ScheduledRetryingExecutor.java @@ -122,9 +122,4 @@ public ApiFuture submit(RetryingFuture retryingFuture) { return ApiFutures.immediateFailedFuture(e); } } - - /** Returns the retry algorithm used by this executor. */ - protected RetryAlgorithm getRetryAlgorithm() { - return retryAlgorithm; - } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java index a64c41acb..d3bb67f99 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java @@ -44,8 +44,27 @@ @InternalApi("For internal use only") public final class StreamingRetryAlgorithm extends RetryAlgorithm { + /** + * Instances that are created using this constructor will ignore the {@link RetryingContext} that + * is passed in to the retrying methods. Use {@link + * #StreamingRetryAlgorithm(ResultRetryAlgorithmWithContext, TimedRetryAlgorithmWithContext)} to + * create an instance that will respect the {@link RetryingContext}. + * + * @deprecated use {@link #StreamingRetryAlgorithm(ResultRetryAlgorithmWithContext, + * TimedRetryAlgorithmWithContext)} instead + */ + @Deprecated + public StreamingRetryAlgorithm( + ResultRetryAlgorithm resultAlgorithm, TimedRetryAlgorithm timedAlgorithm) { + super(resultAlgorithm, timedAlgorithm); + } + + /** + * Creates a {@link StreamingRetryAlgorithm} that will use the settings (if any) in the {@link + * RetryingContext} that is passed in to the retrying methods. + */ public StreamingRetryAlgorithm( - RetryAlgorithmWithContext resultAlgorithm, + ResultRetryAlgorithmWithContext resultAlgorithm, TimedRetryAlgorithmWithContext timedAlgorithm) { super(resultAlgorithm, timedAlgorithm); } diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index e1f6befcf..504d3658e 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -29,13 +29,13 @@ */ package com.google.api.gax.rpc; +import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.retrying.RetryingContext; import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.tracing.ApiTracer; import com.google.auth.Credentials; -import com.google.common.annotations.Beta; import java.util.List; import java.util.Map; import java.util.Set; @@ -93,7 +93,7 @@ public interface ApiCallContext extends RetryingContext { *

Please note that this timeout is best effort and the maximum resolution is configured in * {@link StubSettings#getStreamWatchdogCheckInterval()}. */ - @Beta + @BetaApi("The surface for streaming is not stable yet and may change in the future.") ApiCallContext withStreamWaitTimeout(@Nullable Duration streamWaitTimeout); /** @@ -101,7 +101,7 @@ public interface ApiCallContext extends RetryingContext { * * @see #withStreamWaitTimeout(Duration) */ - @Beta + @BetaApi("The surface for streaming is not stable yet and may change in the future.") @Nullable Duration getStreamWaitTimeout(); @@ -123,7 +123,7 @@ public interface ApiCallContext extends RetryingContext { *

Please note that this timeout is best effort and the maximum resolution is configured in * {@link StubSettings#getStreamWatchdogCheckInterval()}. */ - @Beta + @BetaApi("The surface for streaming is not stable yet and may change in the future.") ApiCallContext withStreamIdleTimeout(@Nullable Duration streamIdleTimeout); /** @@ -131,7 +131,7 @@ public interface ApiCallContext extends RetryingContext { * * @see #withStreamIdleTimeout(Duration) */ - @Beta + @BetaApi("The surface for streaming is not stable yet and may change in the future.") @Nullable Duration getStreamIdleTimeout(); @@ -141,7 +141,7 @@ public interface ApiCallContext extends RetryingContext { *

The {@link ApiTracer} will be used to trace the current operation and to annotate various * events like retries. */ - @Beta + @BetaApi("The surface for tracing is not stable yet and may change in the future") @Nonnull ApiTracer getTracer(); @@ -153,7 +153,7 @@ public interface ApiCallContext extends RetryingContext { * * @param tracer the {@link ApiTracer} to set. */ - @Beta + @BetaApi("The surface for tracing is not stable yet and may change in the future") ApiCallContext withTracer(@Nonnull ApiTracer tracer); /** The {@link RetrySettings} that were previously set for this context. */ @@ -227,10 +227,10 @@ public interface ApiCallContext extends RetryingContext { ApiCallContext merge(ApiCallContext inputCallContext); /** Return a new ApiCallContext with the extraHeaders merged into the present instance. */ - @Beta + @BetaApi("The surface for extra headers is not stable yet and may change in the future.") ApiCallContext withExtraHeaders(Map> extraHeaders); /** Return the extra headers set for this context. */ - @Beta + @BetaApi("The surface for extra headers is not stable yet and may change in the future.") Map> getExtraHeaders(); } diff --git a/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java index 24f3db4ea..07224a95a 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java @@ -51,7 +51,6 @@ public class ExponentialRetryAlgorithmTest { .setRetryDelayMultiplier(2.0) .setMaxRetryDelay(Duration.ofMillis(8L)) .setInitialRpcTimeout(Duration.ofMillis(1L)) - .setJittered(false) .setRpcTimeoutMultiplier(2.0) .setMaxRpcTimeout(Duration.ofMillis(8L)) .setTotalTimeout(Duration.ofMillis(200L)) @@ -64,7 +63,6 @@ public class ExponentialRetryAlgorithmTest { .setInitialRetryDelay(Duration.ofMillis(2L)) .setRetryDelayMultiplier(3.0) .setMaxRetryDelay(Duration.ofMillis(18L)) - .setJittered(false) .setInitialRpcTimeout(Duration.ofMillis(2L)) .setRpcTimeoutMultiplier(3.0) .setMaxRpcTimeout(Duration.ofMillis(18L)) @@ -108,13 +106,11 @@ public void testCreateNextAttempt() { assertEquals(1, secondAttempt.getAttemptCount()); assertEquals(1, secondAttempt.getOverallAttemptCount()); assertEquals(Duration.ofMillis(1L), secondAttempt.getRetryDelay()); - assertEquals(Duration.ofMillis(1L), secondAttempt.getRandomizedRetryDelay()); assertEquals(Duration.ofMillis(2L), secondAttempt.getRpcTimeout()); TimedAttemptSettings thirdAttempt = algorithm.createNextAttempt(secondAttempt); assertEquals(2, thirdAttempt.getAttemptCount()); assertEquals(Duration.ofMillis(2L), thirdAttempt.getRetryDelay()); - assertEquals(Duration.ofMillis(2L), thirdAttempt.getRandomizedRetryDelay()); assertEquals(Duration.ofMillis(4L), thirdAttempt.getRpcTimeout()); } @@ -127,13 +123,11 @@ public void testCreateNextAttemptOverride() { assertEquals(1, secondAttempt.getAttemptCount()); assertEquals(1, secondAttempt.getOverallAttemptCount()); assertEquals(Duration.ofMillis(2L), secondAttempt.getRetryDelay()); - assertEquals(Duration.ofMillis(2L), secondAttempt.getRandomizedRetryDelay()); assertEquals(Duration.ofMillis(6L), secondAttempt.getRpcTimeout()); TimedAttemptSettings thirdAttempt = algorithm.createNextAttempt(secondAttempt); assertEquals(2, thirdAttempt.getAttemptCount()); assertEquals(Duration.ofMillis(6L), thirdAttempt.getRetryDelay()); - assertEquals(Duration.ofMillis(6L), thirdAttempt.getRandomizedRetryDelay()); assertEquals(Duration.ofMillis(18L), thirdAttempt.getRpcTimeout()); } @@ -150,10 +144,11 @@ public void testTruncateToTotalTimeout() { TimedAttemptSettings firstAttempt = timeoutAlg.createFirstAttempt(); TimedAttemptSettings secondAttempt = timeoutAlg.createNextAttempt(firstAttempt); - assertThat(firstAttempt.getRpcTimeout()).isGreaterThan(secondAttempt.getRpcTimeout()); + assertThat(secondAttempt.getRpcTimeout()).isAtLeast(firstAttempt.getRpcTimeout()); + assertThat(secondAttempt.getRpcTimeout()).isAtMost(Duration.ofSeconds(4L)); TimedAttemptSettings thirdAttempt = timeoutAlg.createNextAttempt(secondAttempt); - assertThat(secondAttempt.getRpcTimeout()).isGreaterThan(thirdAttempt.getRpcTimeout()); + assertThat(thirdAttempt.getRpcTimeout()).isAtMost(Duration.ofSeconds(4L)); } @Test diff --git a/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java b/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java index b9aff94c5..07fa1c59d 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java +++ b/gax/src/test/java/com/google/api/gax/retrying/FailingCallable.java @@ -43,7 +43,6 @@ class FailingCallable implements Callable { .setRetryDelayMultiplier(1) .setMaxRetryDelay(Duration.ofMillis(8L)) .setInitialRpcTimeout(Duration.ofMillis(8L)) - .setJittered(false) .setRpcTimeoutMultiplier(1) .setMaxRpcTimeout(Duration.ofMillis(8L)) .setTotalTimeout(Duration.ofMillis(400L)) @@ -55,7 +54,6 @@ class FailingCallable implements Callable { .setRetryDelayMultiplier(1) .setMaxRetryDelay(Duration.ofNanos(1L)) .setInitialRpcTimeout(Duration.ofNanos(1L)) - .setJittered(false) .setRpcTimeoutMultiplier(1) .setMaxRpcTimeout(Duration.ofNanos(1L)) .setTotalTimeout(Duration.ofNanos(1L)) From 301069d83b6f8b5e0a25f18f56c26324a99a9843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 1 Mar 2021 13:47:58 +0100 Subject: [PATCH 16/23] test: add safety margin as the retry settings are now jittered --- .../google/api/gax/retrying/ExponentialRetryAlgorithmTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java index 07224a95a..b81c8c95b 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ExponentialRetryAlgorithmTest.java @@ -178,7 +178,7 @@ public void testShouldRetryFalseOnMaxTimeout() { for (int i = 0; i < 4; i++) { assertTrue(algorithm.shouldRetry(attempt)); attempt = algorithm.createNextAttempt(attempt); - clock.incrementNanoTime(Duration.ofMillis(50L).toNanos()); + clock.incrementNanoTime(Duration.ofMillis(60L).toNanos()); } assertFalse(algorithm.shouldRetry(attempt)); From f3225490740230272f073c83a83a95c690e3bb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 1 Mar 2021 13:48:07 +0100 Subject: [PATCH 17/23] docs: add javadoc --- .../retrying/IgnoreRetryingContextResultRetryAlgorithm.java | 5 +++++ .../retrying/IgnoreRetryingContextTimedRetryAlgorithm.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java index 4d815828c..114bd30a2 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java @@ -31,6 +31,11 @@ import java.util.concurrent.CancellationException; +/** + * Default implementation of {@link ResultRetryAlgorithmWithContext} that ignores the {@link + * RetryingContext}. This is used to wrap {@link ResultRetryAlgorithm} instances to create a {@link + * ResultRetryAlgorithmWithContext} when one is required. + */ class IgnoreRetryingContextResultRetryAlgorithm implements ResultRetryAlgorithmWithContext { private final ResultRetryAlgorithm resultAlgorithm; diff --git a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java index 7d147079e..808a90e83 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java @@ -31,6 +31,11 @@ import java.util.concurrent.CancellationException; +/** + * Default implementation of {@link TimedRetryAlgorithmWithContext} that ignores the {@link + * RetryingContext}. This is used to wrap {@link TimedRetryAlgorithm} instances to create a {@link + * TimedRetryAlgorithmWithContext} when one is required. + */ class IgnoreRetryingContextTimedRetryAlgorithm implements TimedRetryAlgorithmWithContext { private final TimedRetryAlgorithm timedAlgorithm; From e0a61c5a6f609b3d8309a6afae7cebc67111fd4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 5 Mar 2021 15:11:28 +0100 Subject: [PATCH 18/23] chore: address review comments --- .../api/gax/retrying/BasicRetryingFuture.java | 33 +- .../api/gax/retrying/RetryAlgorithm.java | 9 +- .../google/api/gax/rpc/ApiCallContext.java | 13 - .../AbstractRetryingExecutorTest.java | 30 +- .../retrying/DirectRetryingExecutorTest.java | 7 - ...RetryingExecutorWithRetrySettingsTest.java | 67 ---- .../ScheduledRetryingExecutorTest.java | 26 +- ...RetryingExecutorWithRetrySettingsTest.java | 369 ------------------ ....java => StreamingRetryAlgorithmTest.java} | 2 +- 9 files changed, 47 insertions(+), 509 deletions(-) delete mode 100644 gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorWithRetrySettingsTest.java delete mode 100644 gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorWithRetrySettingsTest.java rename gax/src/test/java/com/google/api/gax/rpc/{StreamingRetryAlgorithmWithRetrySettingsTest.java => StreamingRetryAlgorithmTest.java} (99%) diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index 6741b3c5f..dddcf3efa 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -77,7 +77,7 @@ class BasicRetryingFuture extends AbstractFuture this.retryAlgorithm = checkNotNull(retryAlgorithm); this.retryingContext = checkNotNull(context); - this.attemptSettings = createFirstAttempt(retryingContext); + this.attemptSettings = retryAlgorithm.createFirstAttempt(context); // A micro crime, letting "this" reference to escape from constructor before initialization is // completed (via internal non-static class CompletionListener). But it is guaranteed to be ok, @@ -168,9 +168,9 @@ void handleAttempt(Throwable throwable, ResponseT response) { } TimedAttemptSettings nextAttemptSettings = - createNextAttempt(retryingContext, throwable, response); + retryAlgorithm.createNextAttempt(retryingContext, throwable, response, attemptSettings); boolean shouldRetry = - shouldRetry(retryingContext, throwable, response, nextAttemptSettings); + retryAlgorithm.shouldRetry(retryingContext, throwable, response, nextAttemptSettings); if (shouldRetry) { // Log retry info if (LOG.isLoggable(Level.FINEST)) { @@ -193,7 +193,7 @@ void handleAttempt(Throwable throwable, ResponseT response) { // a new attempt will be (must be) scheduled by an external executor } else if (throwable != null) { if (retryAlgorithm - .getResultAlgorithmWithContext() + .getContextAwareResultAlgorithm() .shouldRetry(retryingContext, throwable, response)) { tracer.attemptFailedRetriesExhausted(throwable); } else { @@ -218,31 +218,6 @@ void handleAttempt(Throwable throwable, ResponseT response) { } } - // Calls retryAlgorithm.createFirstAttempt() for the basic implementation. May be overridden by - // subclasses that can use the RetryingContext to determine the initial attempt settings. - TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return retryAlgorithm.createFirstAttempt(context); - } - - // Calls retryAlgorithm.createNextAttempt(throwable, response, attemptSettings) for the basic - // implementation. May be overridden by subclasses that can use the RetryingContext to determine - // the next attempt settings. - TimedAttemptSettings createNextAttempt( - RetryingContext context, Throwable throwable, ResponseT response) { - return retryAlgorithm.createNextAttempt(context, throwable, response, attemptSettings); - } - - // Calls retryAlgorithm.shouldRetry(throwable, response, nextAttemptSettings) for the basic - // implementation. May be overridden by subclasses that can use the RetryingContext to determine - // whether the call should be retried. - boolean shouldRetry( - RetryingContext context, - Throwable throwable, - ResponseT response, - TimedAttemptSettings nextAttemptSettings) { - return retryAlgorithm.shouldRetry(context, throwable, response, nextAttemptSettings); - } - // Sets attempt result futures. Note the "attempt result future" and "attempt future" are not same // things because there are more attempt futures than attempt result futures. // See AttemptCallable.call() for an example of such condition. diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index a2cd59018..2b29d6be9 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -149,13 +149,13 @@ public TimedAttemptSettings createNextAttempt( TimedAttemptSettings previousSettings) { // a small optimization that avoids calling relatively heavy methods // like timedAlgorithm.createNextAttempt(), when it is not necessary. - if (!getResultAlgorithmWithContext() + if (!getContextAwareResultAlgorithm() .shouldRetry(context, previousThrowable, previousResponse)) { return null; } TimedAttemptSettings newSettings = - getResultAlgorithmWithContext() + getContextAwareResultAlgorithm() .createNextAttempt(context, previousThrowable, previousResponse, previousSettings); if (newSettings == null) { newSettings = getTimedAlgorithmWithContext().createNextAttempt(context, previousSettings); @@ -203,7 +203,8 @@ public boolean shouldRetry( ResponseT previousResponse, TimedAttemptSettings nextAttemptSettings) throws CancellationException { - return getResultAlgorithmWithContext().shouldRetry(context, previousThrowable, previousResponse) + return getContextAwareResultAlgorithm() + .shouldRetry(context, previousThrowable, previousResponse) && nextAttemptSettings != null && getTimedAlgorithmWithContext().shouldRetry(context, nextAttemptSettings); } @@ -214,7 +215,7 @@ public ResultRetryAlgorithm getResultAlgorithm() { } @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - public ResultRetryAlgorithmWithContext getResultAlgorithmWithContext() { + public ResultRetryAlgorithmWithContext getContextAwareResultAlgorithm() { return resultAlgorithm; } diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index 504d3658e..f7c79e683 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -156,10 +156,6 @@ public interface ApiCallContext extends RetryingContext { @BetaApi("The surface for tracing is not stable yet and may change in the future") ApiCallContext withTracer(@Nonnull ApiTracer tracer); - /** The {@link RetrySettings} that were previously set for this context. */ - @Nullable - RetrySettings getRetrySettings(); - /** * Returns a new ApiCallContext with the given {@link RetrySettings} set. * @@ -194,15 +190,6 @@ public interface ApiCallContext extends RetryingContext { */ ApiCallContext withRetrySettings(RetrySettings retrySettings); - /** - * Returns the retryable codes that were previously set for this context. - * - *

This is null if the default retryable codes should be used, and otherwise the - * codes that should be used for retrying. An empty set means that retrying should be disabled. - */ - @Nullable - Set getRetryableCodes(); - /** * Returns a new ApiCallContext with the given retryable codes set. * diff --git a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java index 0c352360b..3be825747 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.retrying; +import static com.google.api.gax.retrying.FailingCallable.FAILING_RETRY_SETTINGS; import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; @@ -48,6 +49,8 @@ import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.tracing.ApiTracer; import com.google.common.base.Stopwatch; +import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -57,14 +60,24 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.threeten.bp.Duration; -@RunWith(JUnit4.class) +@RunWith(Parameterized.class) public abstract class AbstractRetryingExecutorTest { + + @Parameters(name = "Custom retry settings: {0}") + public static Collection data() { + return Arrays.asList(new Object[][] {{false}, {true}}); + } + + @Parameter public boolean withCustomRetrySettings; + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock protected ApiTracer tracer; @@ -76,8 +89,6 @@ protected abstract RetryingExecutorWithContext getExecutor( protected abstract RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException); - protected abstract RetrySettings getDefaultRetrySettings(); - protected void busyWaitForInitialResult(RetryingFuture future, Duration timeout) throws TimeoutException { Stopwatch watch = Stopwatch.createStarted(); @@ -90,7 +101,16 @@ protected void busyWaitForInitialResult(RetryingFuture future, Duration t @Before public void setUp() { - retryingContext = FakeCallContext.createDefault().withTracer(tracer); + if (withCustomRetrySettings) { + retryingContext = + FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(FAST_RETRY_SETTINGS); + } else { + retryingContext = FakeCallContext.createDefault().withTracer(tracer); + } + } + + private RetrySettings getDefaultRetrySettings() { + return withCustomRetrySettings ? FAILING_RETRY_SETTINGS : FAST_RETRY_SETTINGS; } @Test diff --git a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java index 631ca9f82..b8a990fbb 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java @@ -29,8 +29,6 @@ */ package com.google.api.gax.retrying; -import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; - import com.google.api.core.CurrentMillisClock; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -50,9 +48,4 @@ protected RetryAlgorithm getAlgorithm( new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), new ExponentialRetryAlgorithm(retrySettings, CurrentMillisClock.getDefaultClock())); } - - @Override - protected RetrySettings getDefaultRetrySettings() { - return FAST_RETRY_SETTINGS; - } } diff --git a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorWithRetrySettingsTest.java b/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorWithRetrySettingsTest.java deleted file mode 100644 index 459655404..000000000 --- a/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorWithRetrySettingsTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import static com.google.api.gax.retrying.FailingCallable.FAILING_RETRY_SETTINGS; -import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; - -import com.google.api.core.CurrentMillisClock; -import com.google.api.gax.rpc.testing.FakeCallContext; -import org.junit.Before; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class DirectRetryingExecutorWithRetrySettingsTest extends AbstractRetryingExecutorTest { - - @Override - @Before - public void setUp() { - retryingContext = - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(FAST_RETRY_SETTINGS); - } - - @Override - protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { - return new DirectRetryingExecutor<>(retryAlgorithm); - } - - @Override - protected RetryAlgorithm getAlgorithm( - RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException) { - return new RetryAlgorithm<>( - new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), - new ExponentialRetryAlgorithm(retrySettings, CurrentMillisClock.getDefaultClock())); - } - - protected RetrySettings getDefaultRetrySettings() { - return FAILING_RETRY_SETTINGS; - } -} diff --git a/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java b/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java index 0e8e7e969..23dff4a30 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorTest.java @@ -40,6 +40,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.NanoClock; import com.google.api.gax.retrying.FailingCallable.CustomException; +import com.google.api.gax.rpc.testing.FakeCallContext; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -48,12 +49,10 @@ import java.util.concurrent.ScheduledExecutorService; import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; import org.threeten.bp.Duration; -@RunWith(MockitoJUnitRunner.class) +// @RunWith(MockitoJUnitRunner.class) public class ScheduledRetryingExecutorTest extends AbstractRetryingExecutorTest { private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); @@ -73,14 +72,8 @@ protected RetryAlgorithm getAlgorithm( new ExponentialRetryAlgorithm(retrySettings, NanoClock.getDefaultClock())); } - @Override - protected RetrySettings getDefaultRetrySettings() { - return FAST_RETRY_SETTINGS; - } - private RetryingExecutorWithContext getRetryingExecutor( RetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { - return new ScheduledRetryingExecutor<>(retryAlgorithm, scheduler); } @@ -106,7 +99,8 @@ public void testSuccessWithFailuresPeekAttempt() throws Exception { RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(retrySettings, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, retryingContext); + RetryingFuture future = + executor.createFuture(callable, FakeCallContext.createDefault().withTracer(tracer)); assertNull(future.peekAttemptResult()); assertSame(future.peekAttemptResult(), future.peekAttemptResult()); @@ -155,7 +149,8 @@ public void testSuccessWithFailuresGetAttempt() throws Exception { RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(retrySettings, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, retryingContext); + RetryingFuture future = + executor.createFuture(callable, FakeCallContext.createDefault().withTracer(tracer)); assertNull(future.peekAttemptResult()); assertSame(future.getAttemptResult(), future.getAttemptResult()); @@ -264,7 +259,8 @@ public void testCancelOuterFutureAfterStart() throws Exception { .build(); RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(retrySettings, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, retryingContext); + RetryingFuture future = + executor.createFuture(callable, FakeCallContext.createDefault().withTracer(tracer)); future.setAttemptFuture(executor.submit(future)); Thread.sleep(30L); @@ -290,7 +286,8 @@ public void testCancelIsTraced() throws Exception { .build(); RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(retrySettings, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, retryingContext); + RetryingFuture future = + executor.createFuture(callable, FakeCallContext.createDefault().withTracer(tracer)); future.setAttemptFuture(executor.submit(future)); Thread.sleep(30L); @@ -318,7 +315,8 @@ public void testCancelProxiedFutureAfterStart() throws Exception { .build(); RetryingExecutorWithContext executor = getRetryingExecutor(getAlgorithm(retrySettings, 0, null), localExecutor); - RetryingFuture future = executor.createFuture(callable, retryingContext); + RetryingFuture future = + executor.createFuture(callable, FakeCallContext.createDefault().withTracer(tracer)); future.setAttemptFuture(executor.submit(future)); Thread.sleep(50L); diff --git a/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorWithRetrySettingsTest.java b/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorWithRetrySettingsTest.java deleted file mode 100644 index 2c537f336..000000000 --- a/gax/src/test/java/com/google/api/gax/retrying/ScheduledRetryingExecutorWithRetrySettingsTest.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import static com.google.api.gax.retrying.FailingCallable.FAILING_RETRY_SETTINGS; -import static com.google.api.gax.retrying.FailingCallable.FAST_RETRY_SETTINGS; -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import com.google.api.core.ApiFuture; -import com.google.api.core.NanoClock; -import com.google.api.gax.retrying.FailingCallable.CustomException; -import com.google.api.gax.rpc.testing.FakeCallContext; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import org.threeten.bp.Duration; - -@RunWith(MockitoJUnitRunner.class) -public class ScheduledRetryingExecutorWithRetrySettingsTest extends AbstractRetryingExecutorTest { - private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - - // Number of test runs, essential for multithreaded tests. - private static final int EXECUTIONS_COUNT = 5; - - @Override - @Before - public void setUp() { - retryingContext = - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(FAST_RETRY_SETTINGS); - } - - @Override - protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { - return getRetryingExecutor(retryAlgorithm, scheduler); - } - - @Override - protected RetryAlgorithm getAlgorithm( - RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException) { - return new RetryAlgorithm<>( - new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), - new ExponentialRetryAlgorithm(retrySettings, NanoClock.getDefaultClock())); - } - - @Override - protected RetrySettings getDefaultRetrySettings() { - return FAILING_RETRY_SETTINGS; - } - - private RetryingExecutorWithContext getRetryingExecutor( - RetryAlgorithm retryAlgorithm, ScheduledExecutorService scheduler) { - return new ScheduledRetryingExecutor<>(retryAlgorithm, scheduler); - } - - @After - public void after() { - scheduler.shutdownNow(); - } - - @Test - public void testSuccessWithFailuresPeekAttempt() throws Exception { - for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { - final int maxRetries = 100; - - ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); - FailingCallable callable = new FailingCallable(15, "SUCCESS", tracer); - - RetrySettings retrySettings = - FAST_RETRY_SETTINGS - .toBuilder() - .setTotalTimeout(Duration.ofMillis(1000L)) - .setMaxAttempts(maxRetries) - .build(); - - RetryingExecutorWithContext executor = - getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = - executor.createFuture( - callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); - - assertNull(future.peekAttemptResult()); - assertSame(future.peekAttemptResult(), future.peekAttemptResult()); - assertFalse(future.getAttemptResult().isDone()); - assertFalse(future.getAttemptResult().isCancelled()); - - future.setAttemptFuture(executor.submit(future)); - - int failedAttempts = 0; - while (!future.isDone()) { - ApiFuture attemptResult = future.peekAttemptResult(); - if (attemptResult != null) { - assertTrue(attemptResult.isDone()); - assertFalse(attemptResult.isCancelled()); - try { - attemptResult.get(); - } catch (ExecutionException e) { - if (e.getCause() instanceof CustomException) { - failedAttempts++; - } - } - } - Thread.sleep(0L, 100); - } - - assertFutureSuccess(future); - assertEquals(15, future.getAttemptSettings().getAttemptCount()); - assertTrue(failedAttempts > 0); - localExecutor.shutdownNow(); - } - } - - @Test - public void testSuccessWithFailuresGetAttempt() throws Exception { - for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { - final int maxRetries = 100; - - ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); - FailingCallable callable = new FailingCallable(15, "SUCCESS", tracer); - RetrySettings retrySettings = - FAST_RETRY_SETTINGS - .toBuilder() - .setTotalTimeout(Duration.ofMillis(1000L)) - .setMaxAttempts(maxRetries) - .build(); - - RetryingExecutorWithContext executor = - getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = - executor.createFuture( - callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); - - assertNull(future.peekAttemptResult()); - assertSame(future.getAttemptResult(), future.getAttemptResult()); - assertFalse(future.getAttemptResult().isDone()); - assertFalse(future.getAttemptResult().isCancelled()); - - future.setAttemptFuture(executor.submit(future)); - - CustomException exception; - int checks = 0; - do { - exception = null; - checks++; - Future attemptResult = future.getAttemptResult(); - try { - // testing that the gotten attempt result is non-cancelable - assertFalse(attemptResult.cancel(false)); - assertFalse(attemptResult.cancel(true)); - attemptResult.get(); - assertNotNull(future.peekAttemptResult()); - } catch (ExecutionException e) { - exception = (CustomException) e.getCause(); - } - assertTrue(attemptResult.isDone()); - assertFalse(attemptResult.isCancelled()); - } while (exception != null && checks < maxRetries + 1); - - assertTrue(future.isDone()); - assertFutureSuccess(future); - assertEquals(15, future.getAttemptSettings().getAttemptCount()); - assertTrue("checks is equal to " + checks, checks > 1 && checks <= maxRetries); - localExecutor.shutdownNow(); - } - } - - @Test - public void testCancelGetAttempt() throws Exception { - for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { - ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); - final int maxRetries = 100; - - FailingCallable callable = new FailingCallable(maxRetries - 1, "SUCCESS", tracer); - RetrySettings retrySettings = - FAST_RETRY_SETTINGS - .toBuilder() - .setTotalTimeout(Duration.ofMillis(1000L)) - .setMaxAttempts(maxRetries) - .build(); - - RetryingExecutorWithContext executor = - getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = - executor.createFuture( - callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); - - assertNull(future.peekAttemptResult()); - assertSame(future.getAttemptResult(), future.getAttemptResult()); - assertFalse(future.getAttemptResult().isDone()); - assertFalse(future.getAttemptResult().isCancelled()); - - future.setAttemptFuture(executor.submit(future)); - - CustomException exception; - CancellationException cancellationException = null; - int checks = 0; - int failedCancellations = 0; - do { - exception = null; - checks++; - Future attemptResult = future.getAttemptResult(); - try { - attemptResult.get(); - assertNotNull(future.peekAttemptResult()); - } catch (CancellationException e) { - cancellationException = e; - } catch (ExecutionException e) { - exception = (CustomException) e.getCause(); - } - assertTrue(attemptResult.isDone()); - if (!future.cancel(true)) { - failedCancellations++; - } - } while (exception != null && checks < maxRetries); - - assertTrue(future.isDone()); - assertNotNull(cancellationException); - // future.cancel(true) may return false sometimes, which is ok. Also, every cancellation - // of an already cancelled future should return false (this is what -1 means here). - assertEquals(2, checks - (failedCancellations - 1)); - assertTrue(future.getAttemptSettings().getAttemptCount() > 0); - assertFutureCancel(future); - localExecutor.shutdownNow(); - } - } - - @Test - public void testCancelOuterFutureAfterStart() throws Exception { - for (int executionsCount = 0; executionsCount < EXECUTIONS_COUNT; executionsCount++) { - ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); - FailingCallable callable = new FailingCallable(4, "SUCCESS", tracer); - RetrySettings retrySettings = - FAST_RETRY_SETTINGS - .toBuilder() - .setInitialRetryDelay(Duration.ofMillis(1_000L)) - .setMaxRetryDelay(Duration.ofMillis(1_000L)) - .setTotalTimeout(Duration.ofMillis(10_0000L)) - .build(); - RetryingExecutorWithContext executor = - getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = - executor.createFuture( - callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); - future.setAttemptFuture(executor.submit(future)); - - // Wait until the result has been returned to the retrying future. - callable.getFirstAttemptFinishedLatch().await(100, TimeUnit.MILLISECONDS); - busyWaitForInitialResult(future, Duration.ofMillis(100)); - - boolean res = future.cancel(false); - assertTrue(res); - assertFutureCancel(future); - assertTrue(future.getAttemptSettings().getAttemptCount() < 4); - localExecutor.shutdownNow(); - } - } - - @Test - public void testCancelIsTraced() throws Exception { - ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); - FailingCallable callable = new FailingCallable(4, "SUCCESS", tracer); - RetrySettings retrySettings = - FAST_RETRY_SETTINGS - .toBuilder() - .setInitialRetryDelay(Duration.ofMillis(1_000L)) - .setMaxRetryDelay(Duration.ofMillis(1_000L)) - .setTotalTimeout(Duration.ofMillis(10_0000L)) - .build(); - RetryingExecutorWithContext executor = - getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = - executor.createFuture( - callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); - future.setAttemptFuture(executor.submit(future)); - - // Wait until the result has been returned to the retrying future. - callable.getFirstAttemptFinishedLatch().await(100, TimeUnit.MILLISECONDS); - busyWaitForInitialResult(future, Duration.ofMillis(100)); - - boolean res = future.cancel(false); - assertTrue(res); - assertFutureCancel(future); - - Mockito.verify(tracer).attemptCancelled(); - localExecutor.shutdownNow(); - } - - @Test - public void testCancelProxiedFutureAfterStart() throws Exception { - // This is a heavy test that takes a lot of time, so only few executions. - for (int executionsCount = 0; executionsCount < 2; executionsCount++) { - ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor(); - FailingCallable callable = new FailingCallable(5, "SUCCESS", tracer); - RetrySettings retrySettings = - FAST_RETRY_SETTINGS - .toBuilder() - .setMaxRetryDelay(Duration.ofMillis(1_000L)) - .setTotalTimeout(Duration.ofMillis(10_0000L)) - .build(); - RetryingExecutorWithContext executor = - getRetryingExecutor(getAlgorithm(FAILING_RETRY_SETTINGS, 0, null), localExecutor); - RetryingFuture future = - executor.createFuture( - callable, - FakeCallContext.createDefault().withTracer(tracer).withRetrySettings(retrySettings)); - future.setAttemptFuture(executor.submit(future)); - - // Wait until the result has been returned to the retrying future. - callable.getFirstAttemptFinishedLatch().await(100, TimeUnit.MILLISECONDS); - busyWaitForInitialResult(future, Duration.ofMillis(100)); - - // Note that shutdownNow() will not cancel internal FutureTasks automatically, which - // may potentially cause another thread hanging on RetryingFuture#get() call forever. - // Cancelling the tasks returned by shutdownNow() also does not help, because of missing - // feature in guava's ListenableScheduledFuture, which does not cancel itself when its - // delegate is cancelled. So only the graceful shutdown() is supported properly. - localExecutor.shutdown(); - - assertFutureFail(future, RejectedExecutionException.class); - localExecutor.shutdownNow(); - } - } -} diff --git a/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmWithRetrySettingsTest.java b/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java similarity index 99% rename from gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmWithRetrySettingsTest.java rename to gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java index 36e4f391d..15dc0b34c 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmWithRetrySettingsTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java @@ -49,7 +49,7 @@ import org.threeten.bp.Duration; @RunWith(JUnit4.class) -public class StreamingRetryAlgorithmWithRetrySettingsTest { +public class StreamingRetryAlgorithmTest { private static final RetrySettings DEFAULT_RETRY_SETTINGS = RetrySettings.newBuilder() .setInitialRetryDelay(Duration.ofMillis(10L)) From e02f95188ab138fa3efc6ef9d4068cf1e0c18d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 10 Mar 2021 17:17:48 +0100 Subject: [PATCH 19/23] fix: address review comments --- .../retrying/BasicResultRetryAlgorithm.java | 2 +- .../api/gax/retrying/BasicRetryingFuture.java | 4 +- ...reRetryingContextResultRetryAlgorithm.java | 72 --------- ...oreRetryingContextTimedRetryAlgorithm.java | 74 --------- .../gax/retrying/ResultRetryAlgorithm.java | 34 ++-- .../ResultRetryAlgorithmWithContext.java | 4 +- .../api/gax/retrying/RetryAlgorithm.java | 147 +++++++++++------- .../gax/retrying/StreamingRetryAlgorithm.java | 41 +---- .../api/gax/retrying/TimedRetryAlgorithm.java | 39 ++--- .../google/api/gax/rpc/ApiCallContext.java | 8 +- 10 files changed, 139 insertions(+), 286 deletions(-) delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java delete mode 100644 gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java index 27c802d4c..b33f131be 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicResultRetryAlgorithm.java @@ -62,7 +62,7 @@ public TimedAttemptSettings createNextAttempt( Throwable previousThrowable, ResponseT previousResponse, TimedAttemptSettings previousSettings) { - return null; + return createNextAttempt(previousThrowable, previousResponse, previousSettings); } /** diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index dddcf3efa..de7b5b5ac 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -192,9 +192,7 @@ void handleAttempt(Throwable throwable, ResponseT response) { setAttemptResult(throwable, response, true); // a new attempt will be (must be) scheduled by an external executor } else if (throwable != null) { - if (retryAlgorithm - .getContextAwareResultAlgorithm() - .shouldRetry(retryingContext, throwable, response)) { + if (retryAlgorithm.shouldRetryBasedOnResult(retryingContext, throwable, response)) { tracer.attemptFailedRetriesExhausted(throwable); } else { tracer.attemptPermanentFailure(throwable); diff --git a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java deleted file mode 100644 index 114bd30a2..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextResultRetryAlgorithm.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.CancellationException; - -/** - * Default implementation of {@link ResultRetryAlgorithmWithContext} that ignores the {@link - * RetryingContext}. This is used to wrap {@link ResultRetryAlgorithm} instances to create a {@link - * ResultRetryAlgorithmWithContext} when one is required. - */ -class IgnoreRetryingContextResultRetryAlgorithm - implements ResultRetryAlgorithmWithContext { - private final ResultRetryAlgorithm resultAlgorithm; - - IgnoreRetryingContextResultRetryAlgorithm(ResultRetryAlgorithm resultAlgorithm) { - this.resultAlgorithm = resultAlgorithm; - } - - public TimedAttemptSettings createNextAttempt( - Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { - return resultAlgorithm.createNextAttempt(prevThrowable, prevResponse, prevSettings); - } - - public boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) - throws CancellationException { - return resultAlgorithm.shouldRetry(prevThrowable, prevResponse); - } - - @Override - public TimedAttemptSettings createNextAttempt( - RetryingContext context, - Throwable previousThrowable, - ResponseT previousResponse, - TimedAttemptSettings previousSettings) { - return createNextAttempt(previousThrowable, previousResponse, previousSettings); - } - - @Override - public boolean shouldRetry( - RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) - throws CancellationException { - return shouldRetry(previousThrowable, previousResponse); - } -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java deleted file mode 100644 index 808a90e83..000000000 --- a/gax/src/main/java/com/google/api/gax/retrying/IgnoreRetryingContextTimedRetryAlgorithm.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.google.api.gax.retrying; - -import java.util.concurrent.CancellationException; - -/** - * Default implementation of {@link TimedRetryAlgorithmWithContext} that ignores the {@link - * RetryingContext}. This is used to wrap {@link TimedRetryAlgorithm} instances to create a {@link - * TimedRetryAlgorithmWithContext} when one is required. - */ -class IgnoreRetryingContextTimedRetryAlgorithm implements TimedRetryAlgorithmWithContext { - private final TimedRetryAlgorithm timedAlgorithm; - - IgnoreRetryingContextTimedRetryAlgorithm(TimedRetryAlgorithm timedAlgorithm) { - this.timedAlgorithm = timedAlgorithm; - } - - public TimedAttemptSettings createFirstAttempt() { - return timedAlgorithm.createFirstAttempt(); - } - - public TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings) { - return timedAlgorithm.createNextAttempt(prevSettings); - } - - public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) - throws CancellationException { - return timedAlgorithm.shouldRetry(nextAttemptSettings); - } - - @Override - public TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return createFirstAttempt(); - } - - @Override - public TimedAttemptSettings createNextAttempt( - RetryingContext context, TimedAttemptSettings previousSettings) { - return createNextAttempt(previousSettings); - } - - @Override - public boolean shouldRetry(RetryingContext context, TimedAttemptSettings nextAttemptSettings) { - return shouldRetry(nextAttemptSettings); - } -} diff --git a/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java index c1958215e..00f72bff4 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java @@ -32,39 +32,27 @@ import java.util.concurrent.CancellationException; /** - * A result retry algorithm is responsible for the following operations (based on the response - * returned by the previous attempt or on the thrown exception): - * - *

    - *
  1. Accepting a task for retry so another attempt will be made. - *
  2. Canceling retrying process so the related {@link java.util.concurrent.Future} will be - * canceled. - *
  3. Creating {@link TimedAttemptSettings} for each subsequent retry attempt. - *
- * - * Implementations of this interface must be thread-safe. - * - * @param response type + * Same as {@link ResultRetryAlgorithmWithContext}, but without methods that accept a {@link + * RetryingContext}. */ public interface ResultRetryAlgorithm { /** - * Creates a next attempt {@link TimedAttemptSettings}. + * Same as {@link ResultRetryAlgorithmWithContext#createNextAttempt(RetryingContext, Throwable, + * Object, TimedAttemptSettings)}, but without a {@link RetryingContext}. * - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt - * @param prevSettings previous attempt settings - * @return next attempt settings or {@code null}, if the implementing algorithm does not provide - * specific settings for the next attempt + * @deprecated use {@link ResultRetryAlgorithmWithContext#createNextAttempt(RetryingContext, + * Throwable, Object, TimedAttemptSettings)} instead */ + @Deprecated TimedAttemptSettings createNextAttempt( Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings); /** - * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * Same as {@link ResultRetryAlgorithmWithContext#shouldRetry(Throwable, Object)}, but without a + * {@link RetryingContext}. * - * @param prevThrowable exception thrown by the previous attempt ({@code null}, if none) - * @param prevResponse response returned by the previous attempt - * @throws CancellationException if the retrying process should be canceled + * @deprecated use {@link ResultRetryAlgorithmWithContext#shouldRetry(RetryingContext, Throwable, + * Object)} instead */ boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) throws CancellationException; } diff --git a/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithmWithContext.java b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithmWithContext.java index ab52f8d58..2a347b13a 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithmWithContext.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithmWithContext.java @@ -61,7 +61,7 @@ public interface ResultRetryAlgorithmWithContext * @param previousResponse response returned by the previous attempt * @param previousSettings previous attempt settings */ - public TimedAttemptSettings createNextAttempt( + TimedAttemptSettings createNextAttempt( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse, @@ -75,7 +75,7 @@ public TimedAttemptSettings createNextAttempt( * @param previousThrowable exception thrown by the previous attempt ({@code null}, if none) * @param previousResponse response returned by the previous attempt. */ - public boolean shouldRetry( + boolean shouldRetry( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) throws CancellationException; } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index 2b29d6be9..3c9cbd6ef 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -33,6 +33,7 @@ import com.google.api.core.BetaApi; import java.util.concurrent.CancellationException; +import javax.annotation.Nullable; /** * The retry algorithm, which makes decision based either on the thrown exception or the returned @@ -43,8 +44,10 @@ * @param response type */ public class RetryAlgorithm { - private final ResultRetryAlgorithmWithContext resultAlgorithm; - private final TimedRetryAlgorithmWithContext timedAlgorithm; + private final ResultRetryAlgorithm resultAlgorithm; + private final TimedRetryAlgorithm timedAlgorithm; + private final ResultRetryAlgorithmWithContext resultAlgorithmWithContext; + private final TimedRetryAlgorithmWithContext timedAlgorithmWithContext; /** * Creates a new retry algorithm instance, which uses thrown exception or returned response and @@ -58,13 +61,16 @@ public class RetryAlgorithm { * * @param resultAlgorithm result algorithm to use * @param timedAlgorithm timed algorithm to use + * @deprecated use {@link RetryAlgorithm#RetryAlgorithm(ResultRetryAlgorithmWithContext, + * TimedRetryAlgorithmWithContext)} instead */ + @Deprecated public RetryAlgorithm( ResultRetryAlgorithm resultAlgorithm, TimedRetryAlgorithm timedAlgorithm) { - this.resultAlgorithm = - new IgnoreRetryingContextResultRetryAlgorithm<>(checkNotNull(resultAlgorithm)); - this.timedAlgorithm = - new IgnoreRetryingContextTimedRetryAlgorithm(checkNotNull(timedAlgorithm)); + this.resultAlgorithm = checkNotNull(resultAlgorithm); + this.timedAlgorithm = checkNotNull(timedAlgorithm); + this.resultAlgorithmWithContext = null; + this.timedAlgorithmWithContext = null; } /** @@ -78,8 +84,10 @@ public RetryAlgorithm( public RetryAlgorithm( ResultRetryAlgorithmWithContext resultAlgorithm, TimedRetryAlgorithmWithContext timedAlgorithm) { - this.resultAlgorithm = checkNotNull(resultAlgorithm); - this.timedAlgorithm = checkNotNull(timedAlgorithm); + this.resultAlgorithm = null; + this.timedAlgorithm = null; + this.resultAlgorithmWithContext = checkNotNull(resultAlgorithm); + this.timedAlgorithmWithContext = checkNotNull(timedAlgorithm); } /** @@ -88,7 +96,7 @@ public RetryAlgorithm( * @return first attempt settings */ public TimedAttemptSettings createFirstAttempt() { - return timedAlgorithm.createFirstAttempt(); + return createFirstAttempt(null); } /** @@ -98,35 +106,30 @@ public TimedAttemptSettings createFirstAttempt() { * RetrySettings} * @return first attempt settings */ + @SuppressWarnings("deprecation") public TimedAttemptSettings createFirstAttempt(RetryingContext context) { - return getTimedAlgorithmWithContext().createFirstAttempt(context); + if (timedAlgorithmWithContext != null && context != null) { + return timedAlgorithmWithContext.createFirstAttempt(context); + } + return timedAlgorithm.createFirstAttempt(); } /** * Creates a next attempt {@link TimedAttemptSettings}. This method will return first non-null * value, returned by either result or timed retry algorithms in that particular order. * - * @param prevThrowable exception thrown by the previous attempt or null if a result was returned - * instead - * @param prevResponse response returned by the previous attempt or null if an exception was + * @param previousThrowable exception thrown by the previous attempt or null if a result was + * returned instead + * @param previousResponse response returned by the previous attempt or null if an exception was * thrown instead - * @param prevSettings previous attempt settings + * @param previousSettings previous attempt settings * @return next attempt settings, can be {@code null}, if no there should be no new attempt */ public TimedAttemptSettings createNextAttempt( - Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { - // a small optimization, which allows to avoid calling relatively heavy methods - // like timedAlgorithm.createNextAttempt(), when it is not necessary. - if (!resultAlgorithm.shouldRetry(prevThrowable, prevResponse)) { - return null; - } - - TimedAttemptSettings newSettings = - resultAlgorithm.createNextAttempt(prevThrowable, prevResponse, prevSettings); - if (newSettings == null) { - newSettings = timedAlgorithm.createNextAttempt(prevSettings); - } - return newSettings; + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + return createNextAttempt(null, previousThrowable, previousResponse, previousSettings); } /** @@ -149,26 +152,47 @@ public TimedAttemptSettings createNextAttempt( TimedAttemptSettings previousSettings) { // a small optimization that avoids calling relatively heavy methods // like timedAlgorithm.createNextAttempt(), when it is not necessary. - if (!getContextAwareResultAlgorithm() - .shouldRetry(context, previousThrowable, previousResponse)) { + if (!shouldRetryBasedOnResult(context, previousThrowable, previousResponse)) { return null; } TimedAttemptSettings newSettings = - getContextAwareResultAlgorithm() - .createNextAttempt(context, previousThrowable, previousResponse, previousSettings); + createNextAttemptBasedOnResult( + context, previousThrowable, previousResponse, previousSettings); if (newSettings == null) { - newSettings = getTimedAlgorithmWithContext().createNextAttempt(context, previousSettings); + newSettings = createNextAttemptBasedOnTiming(context, previousSettings); } return newSettings; } + @SuppressWarnings("deprecation") + private TimedAttemptSettings createNextAttemptBasedOnResult( + RetryingContext context, + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + if (resultAlgorithmWithContext != null && context != null) { + return resultAlgorithmWithContext.createNextAttempt( + context, previousThrowable, previousResponse, previousSettings); + } + return resultAlgorithm.createNextAttempt(previousThrowable, previousResponse, previousSettings); + } + + @SuppressWarnings("deprecation") + private TimedAttemptSettings createNextAttemptBasedOnTiming( + RetryingContext context, TimedAttemptSettings previousSettings) { + if (timedAlgorithmWithContext != null && context != null) { + return timedAlgorithmWithContext.createNextAttempt(context, previousSettings); + } + return timedAlgorithm.createNextAttempt(previousSettings); + } + /** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * - * @param prevThrowable exception thrown by the previous attempt or null if a result was returned - * instead - * @param prevResponse response returned by the previous attempt or null if an exception was + * @param previousThrowable exception thrown by the previous attempt or null if a result was + * returned instead + * @param previousResponse response returned by the previous attempt or null if an exception was * thrown instead * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted @@ -176,11 +200,11 @@ public TimedAttemptSettings createNextAttempt( * @return {@code true} if another attempt should be made, or {@code false} otherwise */ public boolean shouldRetry( - Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings nextAttemptSettings) + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings nextAttemptSettings) throws CancellationException { - return resultAlgorithm.shouldRetry(prevThrowable, prevResponse) - && nextAttemptSettings != null - && timedAlgorithm.shouldRetry(nextAttemptSettings); + return shouldRetry(null, previousThrowable, previousResponse, nextAttemptSettings); } /** @@ -203,29 +227,48 @@ public boolean shouldRetry( ResponseT previousResponse, TimedAttemptSettings nextAttemptSettings) throws CancellationException { - return getContextAwareResultAlgorithm() - .shouldRetry(context, previousThrowable, previousResponse) - && nextAttemptSettings != null - && getTimedAlgorithmWithContext().shouldRetry(context, nextAttemptSettings); + return shouldRetryBasedOnResult(context, previousThrowable, previousResponse) + && shouldRetryBasedOnTiming(context, nextAttemptSettings); } - @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - public ResultRetryAlgorithm getResultAlgorithm() { - return resultAlgorithm; + @SuppressWarnings("deprecation") + boolean shouldRetryBasedOnResult( + RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) { + if (resultAlgorithmWithContext != null && context != null) { + return resultAlgorithmWithContext.shouldRetry(context, previousThrowable, previousResponse); + } + return resultAlgorithm.shouldRetry(previousThrowable, previousResponse); + } + + @SuppressWarnings("deprecation") + private boolean shouldRetryBasedOnTiming( + RetryingContext context, TimedAttemptSettings nextAttemptSettings) { + if (nextAttemptSettings == null) { + return false; + } + if (timedAlgorithmWithContext != null && context != null) { + return timedAlgorithmWithContext.shouldRetry(context, nextAttemptSettings); + } + return timedAlgorithm.shouldRetry(nextAttemptSettings); } @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - public ResultRetryAlgorithmWithContext getContextAwareResultAlgorithm() { - return resultAlgorithm; + public ResultRetryAlgorithm getResultAlgorithm() { + return resultAlgorithmWithContext != null ? resultAlgorithmWithContext : resultAlgorithm; } + /** + * Returns the context-aware result retry algorithm that is used by this {@link RetryAlgorithm}, + * or null if the algorithm was created with a result algorithm that is not context aware. + */ @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - public TimedRetryAlgorithm getTimedAlgorithm() { - return timedAlgorithm; + @Nullable + public ResultRetryAlgorithmWithContext getContextAwareResultAlgorithm_dontuse() { + return resultAlgorithmWithContext; } @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - public TimedRetryAlgorithmWithContext getTimedAlgorithmWithContext() { - return timedAlgorithm; + public TimedRetryAlgorithm getTimedAlgorithm() { + return timedAlgorithmWithContext != null ? timedAlgorithmWithContext : timedAlgorithm; } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java index d3bb67f99..272fe3ba1 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/StreamingRetryAlgorithm.java @@ -76,25 +76,10 @@ public StreamingRetryAlgorithm( */ @Override public TimedAttemptSettings createNextAttempt( - Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { - - if (prevThrowable instanceof ServerStreamingAttemptException) { - ServerStreamingAttemptException attemptException = - (ServerStreamingAttemptException) prevThrowable; - prevThrowable = prevThrowable.getCause(); - - // If we have made progress in the last attempt, then reset the delays - if (attemptException.hasSeenResponses()) { - prevSettings = - createFirstAttempt() - .toBuilder() - .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos()) - .setOverallAttemptCount(prevSettings.getOverallAttemptCount()) - .build(); - } - } - - return super.createNextAttempt(prevThrowable, prevResponse, prevSettings); + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings previousSettings) { + return createNextAttempt(null, previousThrowable, previousResponse, previousSettings); } /** @@ -136,21 +121,11 @@ public TimedAttemptSettings createNextAttempt( */ @Override public boolean shouldRetry( - Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings nextAttemptSettings) + Throwable previousThrowable, + ResponseT previousResponse, + TimedAttemptSettings nextAttemptSettings) throws CancellationException { - - // Unwrap - if (prevThrowable instanceof ServerStreamingAttemptException) { - ServerStreamingAttemptException attemptExceptino = - (ServerStreamingAttemptException) prevThrowable; - prevThrowable = prevThrowable.getCause(); - - if (!attemptExceptino.canResume()) { - return false; - } - } - - return super.shouldRetry(prevThrowable, prevResponse, nextAttemptSettings); + return shouldRetry(null, previousThrowable, previousResponse, nextAttemptSettings); } /** diff --git a/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java index b67128a1e..743873d7d 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java @@ -33,44 +33,37 @@ import java.util.concurrent.CancellationException; /** - * A timed retry algorithm is responsible for the following operations, based on the previous - * attempt settings and current time: - * - *
    - *
  1. Creating first attempt {@link TimedAttemptSettings}. - *
  2. Accepting a task for retry so another attempt will be made. - *
  3. Canceling retrying process so the related {@link java.util.concurrent.Future} will be - * canceled. - *
  4. Creating {@link TimedAttemptSettings} for each subsequent retry attempt. - *
- * - * Implementations of this interface must be be thread-save. + * Same as {@link TimedRetryAlgorithmWithContext}, but without methods that accept a {@link + * RetryingContext}. */ public interface TimedRetryAlgorithm { /** - * Creates a first attempt {@link TimedAttemptSettings}. + * Same as {@link TimedRetryAlgorithmWithContext#createFirstAttempt(RetryingContext)}, but without + * a {@link RetryingContext}. * - * @return first attempt settings + * @deprecated use {@link TimedRetryAlgorithmWithContext#createFirstAttempt(RetryingContext)} + * instead */ TimedAttemptSettings createFirstAttempt(); /** - * Creates a next attempt {@link TimedAttemptSettings}, which defines properties of the next - * attempt. + * Same as {@link TimedRetryAlgorithmWithContext#createNextAttempt(RetryingContext, + * TimedAttemptSettings)}, but without a {@link RetryingContext}. * - * @param prevSettings previous attempt settings - * @return next attempt settings or {@code null} if the implementing algorithm does not provide - * specific settings for the next attempt + * @deprecated use {@link TimedRetryAlgorithmWithContext#createNextAttempt(RetryingContext, + * TimedAttemptSettings)} instead */ + @Deprecated TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings); /** - * Returns {@code true} if another attempt should be made, or {@code false} otherwise. + * Same as {@link TimedRetryAlgorithmWithContext#shouldRetry(RetryingContext, + * TimedAttemptSettings)}, but without a {@link RetryingContext}. * - * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if - * accepted - * @throws CancellationException if the retrying process should be canceled + * @deprecated use {@link TimedRetryAlgorithmWithContext#shouldRetry(RetryingContext, + * TimedAttemptSettings)} instead */ + @Deprecated boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) throws CancellationException; } diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index f7c79e683..f3c4b3afe 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -165,9 +165,9 @@ public interface ApiCallContext extends RetryingContext { * #withRetrySettings(RetrySettings)} on an RPC that does not include {@link * Code#DEADLINE_EXCEEDED} as one of its retryable codes (or without calling {@link * #withRetryableCodes(Set)} with a set that includes at least {@link Code#DEADLINE_EXCEEDED}) - * will effectively only set a simple timeout that is equal to {@link - * RetrySettings#getInitialRpcTimeout()}. It is recommended to use {@link #withTimeout(Duration)} - * if that is the intended behavior. + * will effectively only set a single timeout that is equal to {@link + * RetrySettings#getInitialRpcTimeout()}. If this timeout is exceeded, the RPC will not be retried + * and will fail with {@link Code#DEADLINE_EXCEEDED}. * *

Example usage: * @@ -188,6 +188,7 @@ public interface ApiCallContext extends RetryingContext { * StatusCode.Code.DEADLINE_EXCEEDED)); * } */ + @BetaApi ApiCallContext withRetrySettings(RetrySettings retrySettings); /** @@ -202,6 +203,7 @@ public interface ApiCallContext extends RetryingContext { * to change which codes are considered retryable for an RPC that already has at least one * retryable code in its default settings. */ + @BetaApi ApiCallContext withRetryableCodes(Set retryableCodes); /** If inputContext is not null, returns it; if it is null, returns the present instance. */ From eafe23967d4b518e68a4e140d891b81936b4171a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 10 Mar 2021 18:02:51 +0100 Subject: [PATCH 20/23] fix: add tests + fix potential NPE --- .../gax/httpjson/HttpJsonCallContextTest.java | 16 ++ .../api/gax/retrying/RetryAlgorithm.java | 11 +- .../api/gax/retrying/RetryAlgorithmTest.java | 177 ++++++++++++++++++ 3 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java diff --git a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java index 452ccbb44..245ace65c 100644 --- a/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java +++ b/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonCallContextTest.java @@ -29,18 +29,25 @@ */ package com.google.api.gax.httpjson; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.testing.FakeCallContext; import com.google.api.gax.rpc.testing.FakeChannel; import com.google.api.gax.rpc.testing.FakeTransportChannel; import com.google.api.gax.tracing.ApiTracer; import com.google.auth.Credentials; +import com.google.common.collect.ImmutableMap; import com.google.common.truth.Truth; +import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.Assert; import org.junit.Test; @@ -213,4 +220,13 @@ public void testWithRetryableCodes() { HttpJsonCallContext context = emptyContext.withRetryableCodes(codes); assertNotNull(context.getRetryableCodes()); } + + @Test + public void testWithExtraHeaders() { + Map> headers = ImmutableMap.of("k", Arrays.asList("v")); + ApiCallContext emptyContext = HttpJsonCallContext.createDefault(); + assertTrue(emptyContext.getExtraHeaders().isEmpty()); + ApiCallContext context = emptyContext.withExtraHeaders(headers); + assertEquals(headers, context.getExtraHeaders()); + } } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index 3c9cbd6ef..6b8078530 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -111,7 +111,7 @@ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { if (timedAlgorithmWithContext != null && context != null) { return timedAlgorithmWithContext.createFirstAttempt(context); } - return timedAlgorithm.createFirstAttempt(); + return getTimedAlgorithm().createFirstAttempt(); } /** @@ -175,7 +175,8 @@ private TimedAttemptSettings createNextAttemptBasedOnResult( return resultAlgorithmWithContext.createNextAttempt( context, previousThrowable, previousResponse, previousSettings); } - return resultAlgorithm.createNextAttempt(previousThrowable, previousResponse, previousSettings); + return getResultAlgorithm() + .createNextAttempt(previousThrowable, previousResponse, previousSettings); } @SuppressWarnings("deprecation") @@ -184,7 +185,7 @@ private TimedAttemptSettings createNextAttemptBasedOnTiming( if (timedAlgorithmWithContext != null && context != null) { return timedAlgorithmWithContext.createNextAttempt(context, previousSettings); } - return timedAlgorithm.createNextAttempt(previousSettings); + return getTimedAlgorithm().createNextAttempt(previousSettings); } /** @@ -237,7 +238,7 @@ boolean shouldRetryBasedOnResult( if (resultAlgorithmWithContext != null && context != null) { return resultAlgorithmWithContext.shouldRetry(context, previousThrowable, previousResponse); } - return resultAlgorithm.shouldRetry(previousThrowable, previousResponse); + return getResultAlgorithm().shouldRetry(previousThrowable, previousResponse); } @SuppressWarnings("deprecation") @@ -249,7 +250,7 @@ private boolean shouldRetryBasedOnTiming( if (timedAlgorithmWithContext != null && context != null) { return timedAlgorithmWithContext.shouldRetry(context, nextAttemptSettings); } - return timedAlgorithm.shouldRetry(nextAttemptSettings); + return getTimedAlgorithm().shouldRetry(nextAttemptSettings); } @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") diff --git a/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java new file mode 100644 index 000000000..57270a4f0 --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java @@ -0,0 +1,177 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@SuppressWarnings({"unchecked", "deprecation"}) +@RunWith(JUnit4.class) +public class RetryAlgorithmTest { + + @Test + public void testCreateFirstAttempt() { + TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class); + RetryAlgorithm algorithm = + new RetryAlgorithm<>(mock(ResultRetryAlgorithm.class), timedAlgorithm); + + algorithm.createFirstAttempt(); + verify(timedAlgorithm).createFirstAttempt(); + } + + @Test + public void testCreateFirstAttemptWithUnusedContext() { + TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class); + RetryAlgorithm algorithm = + new RetryAlgorithm<>(mock(ResultRetryAlgorithm.class), timedAlgorithm); + + RetryingContext context = mock(RetryingContext.class); + algorithm.createFirstAttempt(context); + verify(timedAlgorithm).createFirstAttempt(); + } + + @Test + public void testCreateFirstAttemptWithContext() { + TimedRetryAlgorithmWithContext timedAlgorithm = mock(TimedRetryAlgorithmWithContext.class); + RetryAlgorithm algorithm = + new RetryAlgorithm<>(mock(ResultRetryAlgorithmWithContext.class), timedAlgorithm); + + RetryingContext context = mock(RetryingContext.class); + algorithm.createFirstAttempt(context); + verify(timedAlgorithm).createFirstAttempt(context); + } + + @Test + public void testCreateFirstAttemptWithNullContext() { + TimedRetryAlgorithmWithContext timedAlgorithm = mock(TimedRetryAlgorithmWithContext.class); + RetryAlgorithm algorithm = + new RetryAlgorithm<>(mock(ResultRetryAlgorithmWithContext.class), timedAlgorithm); + + algorithm.createFirstAttempt(null); + verify(timedAlgorithm).createFirstAttempt(); + } + + @Test + public void testNextAttempt() { + ResultRetryAlgorithm resultAlgorithm = mock(ResultRetryAlgorithm.class); + TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class); + + algorithm.createNextAttempt(previousThrowable, previousResult, previousSettings); + verify(resultAlgorithm).shouldRetry(previousThrowable, previousResult); + } + + @Test + public void testNextAttemptWithContext() { + ResultRetryAlgorithmWithContext resultAlgorithm = + mock(ResultRetryAlgorithmWithContext.class); + TimedRetryAlgorithmWithContext timedAlgorithm = mock(TimedRetryAlgorithmWithContext.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + RetryingContext context = mock(RetryingContext.class); + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class); + + algorithm.createNextAttempt(context, previousThrowable, previousResult, previousSettings); + verify(resultAlgorithm).shouldRetry(context, previousThrowable, previousResult); + } + + @Test + public void testShouldRetry() { + ResultRetryAlgorithm resultAlgorithm = mock(ResultRetryAlgorithm.class); + TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class); + + algorithm.shouldRetry(previousThrowable, previousResult, previousSettings); + verify(resultAlgorithm).shouldRetry(previousThrowable, previousResult); + } + + @Test + public void testShouldRetry_usesTimedAlgorithm() { + ResultRetryAlgorithm resultAlgorithm = mock(ResultRetryAlgorithm.class); + TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class); + when(resultAlgorithm.shouldRetry(previousThrowable, previousResult)).thenReturn(true); + + algorithm.shouldRetry(previousThrowable, previousResult, previousSettings); + verify(timedAlgorithm).shouldRetry(previousSettings); + } + + @Test + public void testShouldRetryWithContext() { + ResultRetryAlgorithmWithContext resultAlgorithm = + mock(ResultRetryAlgorithmWithContext.class); + TimedRetryAlgorithmWithContext timedAlgorithm = mock(TimedRetryAlgorithmWithContext.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + RetryingContext context = mock(RetryingContext.class); + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class); + + algorithm.shouldRetry(context, previousThrowable, previousResult, previousSettings); + verify(resultAlgorithm).shouldRetry(context, previousThrowable, previousResult); + } + + @Test + public void testShouldRetryWithContext_usesTimedAlgorithm() { + ResultRetryAlgorithmWithContext resultAlgorithm = + mock(ResultRetryAlgorithmWithContext.class); + TimedRetryAlgorithmWithContext timedAlgorithm = mock(TimedRetryAlgorithmWithContext.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + RetryingContext context = mock(RetryingContext.class); + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class); + when(resultAlgorithm.shouldRetry(context, previousThrowable, previousResult)).thenReturn(true); + + algorithm.shouldRetry(context, previousThrowable, previousResult, previousSettings); + verify(timedAlgorithm).shouldRetry(context, previousSettings); + } +} From 8ae3280ef7f8915bfba70665852fd00b44d2f0db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 10 Mar 2021 19:45:10 +0100 Subject: [PATCH 21/23] fix: remove unused method + add tests --- .../api/gax/retrying/RetryAlgorithm.java | 11 -------- .../api/gax/retrying/RetryAlgorithmTest.java | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index 6b8078530..4feb442b8 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -33,7 +33,6 @@ import com.google.api.core.BetaApi; import java.util.concurrent.CancellationException; -import javax.annotation.Nullable; /** * The retry algorithm, which makes decision based either on the thrown exception or the returned @@ -258,16 +257,6 @@ public ResultRetryAlgorithm getResultAlgorithm() { return resultAlgorithmWithContext != null ? resultAlgorithmWithContext : resultAlgorithm; } - /** - * Returns the context-aware result retry algorithm that is used by this {@link RetryAlgorithm}, - * or null if the algorithm was created with a result algorithm that is not context aware. - */ - @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") - @Nullable - public ResultRetryAlgorithmWithContext getContextAwareResultAlgorithm_dontuse() { - return resultAlgorithmWithContext; - } - @BetaApi("Surface for inspecting the a RetryAlgorithm is not yet stable") public TimedRetryAlgorithm getTimedAlgorithm() { return timedAlgorithmWithContext != null ? timedAlgorithmWithContext : timedAlgorithm; diff --git a/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java index 57270a4f0..a08cdee7f 100644 --- a/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.retrying; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -174,4 +175,31 @@ public void testShouldRetryWithContext_usesTimedAlgorithm() { algorithm.shouldRetry(context, previousThrowable, previousResult, previousSettings); verify(timedAlgorithm).shouldRetry(context, previousSettings); } + + @Test + public void testShouldRetry_noPreviousSettings() { + ResultRetryAlgorithm resultAlgorithm = mock(ResultRetryAlgorithm.class); + TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + when(resultAlgorithm.shouldRetry(previousThrowable, previousResult)).thenReturn(true); + + assertFalse(algorithm.shouldRetry(previousThrowable, previousResult, null)); + } + + @Test + public void testShouldRetryWithContext_noPreviousSettings() { + ResultRetryAlgorithmWithContext resultAlgorithm = + mock(ResultRetryAlgorithmWithContext.class); + TimedRetryAlgorithmWithContext timedAlgorithm = mock(TimedRetryAlgorithmWithContext.class); + RetryAlgorithm algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm); + + RetryingContext context = mock(RetryingContext.class); + Throwable previousThrowable = new Throwable(); + Object previousResult = new Object(); + + assertFalse(algorithm.shouldRetry(context, previousThrowable, previousResult, null)); + } } From 5da364aef513130867097e91f8d8d1d42f97a166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 10 Mar 2021 20:02:19 +0100 Subject: [PATCH 22/23] test: add additional test calls --- .../google/api/gax/rpc/StreamingRetryAlgorithmTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java b/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java index 15dc0b34c..740000723 100644 --- a/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java +++ b/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java @@ -121,6 +121,10 @@ public void testNextAttemptReturnsNullWhenShouldNotRetry() { TimedAttemptSettings attempt = algorithm.createNextAttempt(context, exception, null, mock(TimedAttemptSettings.class)); assertThat(attempt).isNull(); + + TimedAttemptSettings attemptWithoutContext = + algorithm.createNextAttempt(exception, null, mock(TimedAttemptSettings.class)); + assertThat(attemptWithoutContext).isNull(); } @Test @@ -194,6 +198,10 @@ public void testShouldNotRetryIfAttemptIsNonResumable() { boolean shouldRetry = algorithm.shouldRetry(context, exception, null, mock(TimedAttemptSettings.class)); assertThat(shouldRetry).isFalse(); + + boolean shouldRetryWithoutContext = + algorithm.shouldRetry(exception, null, mock(TimedAttemptSettings.class)); + assertThat(shouldRetryWithoutContext).isFalse(); } @Test From a42ea603d803cc24221e113ac249464e35aabf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 17 Mar 2021 11:13:23 +0100 Subject: [PATCH 23/23] fix: deprecation --- .../api/gax/retrying/ResultRetryAlgorithm.java | 12 ++++++------ .../google/api/gax/retrying/RetryAlgorithm.java | 13 ++++++++----- .../api/gax/retrying/TimedRetryAlgorithm.java | 17 ++++++++--------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java index 00f72bff4..53363f37a 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/ResultRetryAlgorithm.java @@ -33,17 +33,17 @@ /** * Same as {@link ResultRetryAlgorithmWithContext}, but without methods that accept a {@link - * RetryingContext}. + * RetryingContext}. Use {@link ResultRetryAlgorithmWithContext} instead of this interface when + * possible. */ public interface ResultRetryAlgorithm { /** * Same as {@link ResultRetryAlgorithmWithContext#createNextAttempt(RetryingContext, Throwable, * Object, TimedAttemptSettings)}, but without a {@link RetryingContext}. * - * @deprecated use {@link ResultRetryAlgorithmWithContext#createNextAttempt(RetryingContext, - * Throwable, Object, TimedAttemptSettings)} instead + *

Use {@link ResultRetryAlgorithmWithContext#createNextAttempt(RetryingContext, Throwable, + * Object, TimedAttemptSettings)} instead of this method when possible. */ - @Deprecated TimedAttemptSettings createNextAttempt( Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings); @@ -51,8 +51,8 @@ TimedAttemptSettings createNextAttempt( * Same as {@link ResultRetryAlgorithmWithContext#shouldRetry(Throwable, Object)}, but without a * {@link RetryingContext}. * - * @deprecated use {@link ResultRetryAlgorithmWithContext#shouldRetry(RetryingContext, Throwable, - * Object)} instead + *

Use {@link ResultRetryAlgorithmWithContext#shouldRetry(RetryingContext, Throwable, Object)} + * instead of this method when possible. */ boolean shouldRetry(Throwable prevThrowable, ResponseT prevResponse) throws CancellationException; } diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java index 4feb442b8..16d60d148 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java @@ -93,7 +93,9 @@ public RetryAlgorithm( * Creates a first attempt {@link TimedAttemptSettings}. * * @return first attempt settings + * @deprecated use {@link #createFirstAttempt(RetryingContext)} instead */ + @Deprecated public TimedAttemptSettings createFirstAttempt() { return createFirstAttempt(null); } @@ -105,7 +107,6 @@ public TimedAttemptSettings createFirstAttempt() { * RetrySettings} * @return first attempt settings */ - @SuppressWarnings("deprecation") public TimedAttemptSettings createFirstAttempt(RetryingContext context) { if (timedAlgorithmWithContext != null && context != null) { return timedAlgorithmWithContext.createFirstAttempt(context); @@ -123,7 +124,10 @@ public TimedAttemptSettings createFirstAttempt(RetryingContext context) { * thrown instead * @param previousSettings previous attempt settings * @return next attempt settings, can be {@code null}, if no there should be no new attempt + * @deprecated use {@link #createNextAttempt(RetryingContext, Throwable, Object, + * TimedAttemptSettings)} instead */ + @Deprecated public TimedAttemptSettings createNextAttempt( Throwable previousThrowable, ResponseT previousResponse, @@ -164,7 +168,6 @@ public TimedAttemptSettings createNextAttempt( return newSettings; } - @SuppressWarnings("deprecation") private TimedAttemptSettings createNextAttemptBasedOnResult( RetryingContext context, Throwable previousThrowable, @@ -178,7 +181,6 @@ private TimedAttemptSettings createNextAttemptBasedOnResult( .createNextAttempt(previousThrowable, previousResponse, previousSettings); } - @SuppressWarnings("deprecation") private TimedAttemptSettings createNextAttemptBasedOnTiming( RetryingContext context, TimedAttemptSettings previousSettings) { if (timedAlgorithmWithContext != null && context != null) { @@ -198,7 +200,10 @@ private TimedAttemptSettings createNextAttemptBasedOnTiming( * accepted * @throws CancellationException if the retrying process should be canceled * @return {@code true} if another attempt should be made, or {@code false} otherwise + * @deprecated use {@link #shouldRetry(RetryingContext, Throwable, Object, TimedAttemptSettings)} + * instead */ + @Deprecated public boolean shouldRetry( Throwable previousThrowable, ResponseT previousResponse, @@ -231,7 +236,6 @@ public boolean shouldRetry( && shouldRetryBasedOnTiming(context, nextAttemptSettings); } - @SuppressWarnings("deprecation") boolean shouldRetryBasedOnResult( RetryingContext context, Throwable previousThrowable, ResponseT previousResponse) { if (resultAlgorithmWithContext != null && context != null) { @@ -240,7 +244,6 @@ boolean shouldRetryBasedOnResult( return getResultAlgorithm().shouldRetry(previousThrowable, previousResponse); } - @SuppressWarnings("deprecation") private boolean shouldRetryBasedOnTiming( RetryingContext context, TimedAttemptSettings nextAttemptSettings) { if (nextAttemptSettings == null) { diff --git a/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java b/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java index 743873d7d..5cae22b20 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java +++ b/gax/src/main/java/com/google/api/gax/retrying/TimedRetryAlgorithm.java @@ -34,7 +34,8 @@ /** * Same as {@link TimedRetryAlgorithmWithContext}, but without methods that accept a {@link - * RetryingContext}. + * RetryingContext}. Use {@link TimedRetryAlgorithmWithContext} instead of this interface when + * possible. */ public interface TimedRetryAlgorithm { @@ -42,8 +43,8 @@ public interface TimedRetryAlgorithm { * Same as {@link TimedRetryAlgorithmWithContext#createFirstAttempt(RetryingContext)}, but without * a {@link RetryingContext}. * - * @deprecated use {@link TimedRetryAlgorithmWithContext#createFirstAttempt(RetryingContext)} - * instead + *

Use {@link TimedRetryAlgorithmWithContext#createFirstAttempt(RetryingContext)} instead of + * this method when possible. */ TimedAttemptSettings createFirstAttempt(); @@ -51,19 +52,17 @@ public interface TimedRetryAlgorithm { * Same as {@link TimedRetryAlgorithmWithContext#createNextAttempt(RetryingContext, * TimedAttemptSettings)}, but without a {@link RetryingContext}. * - * @deprecated use {@link TimedRetryAlgorithmWithContext#createNextAttempt(RetryingContext, - * TimedAttemptSettings)} instead + *

Use {@link TimedRetryAlgorithmWithContext#createNextAttempt(RetryingContext, + * TimedAttemptSettings)} instead of this method when possible. */ - @Deprecated TimedAttemptSettings createNextAttempt(TimedAttemptSettings prevSettings); /** * Same as {@link TimedRetryAlgorithmWithContext#shouldRetry(RetryingContext, * TimedAttemptSettings)}, but without a {@link RetryingContext}. * - * @deprecated use {@link TimedRetryAlgorithmWithContext#shouldRetry(RetryingContext, - * TimedAttemptSettings)} instead + *

Use {@link TimedRetryAlgorithmWithContext#shouldRetry(RetryingContext, + * TimedAttemptSettings)} instead of this method when possible. */ - @Deprecated boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) throws CancellationException; }