From 6d7e19cbe5dfaaeb787cff9c15c0778213c939dc Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Thu, 12 Apr 2018 11:41:04 -0700 Subject: [PATCH] core: replumb RetryPolicy - replumbed `RetryPolicy` with `MethodInfo` without breaking the existing `RetryPolicyTest`. - moved `ServiceConfigInterceptor.MethodInfo.RetryPolicy` out as a top level class so that `RetriableStream` does not import `ServiceConfigInterceptor`. --- .../AbstractManagedChannelImplBuilder.java | 15 +- .../io/grpc/internal/ManagedChannelImpl.java | 24 +- .../io/grpc/internal/RetriableStream.java | 116 +++------ .../java/io/grpc/internal/RetryPolicy.java | 107 ++++++++ .../internal/ServiceConfigInterceptor.java | 239 ++++++++++-------- .../io/grpc/internal/ServiceConfigUtil.java | 110 -------- ...AbstractManagedChannelImplBuilderTest.java | 8 +- .../io/grpc/internal/RetriableStreamTest.java | 28 +- .../io/grpc/internal/RetryPolicyTest.java | 101 ++++++-- .../ServiceConfigInterceptorTest.java | 28 +- .../internal/test_retry_service_config.json | 2 +- 11 files changed, 407 insertions(+), 371 deletions(-) create mode 100644 core/src/main/java/io/grpc/internal/RetryPolicy.java diff --git a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java index 74e06062ad..b1938a8302 100644 --- a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java @@ -127,7 +127,11 @@ public abstract class AbstractManagedChannelImplBuilder int maxHedgedAttempts = 5; long retryBufferSize = DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES; long perRpcBufferLimit = DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES; - boolean retryDisabled = true; // TODO(zdapeng): default to false + boolean retryEnabled = false; // TODO(zdapeng): default to true + // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know + // what should be the desired behavior for retry + stats/tracing. + // TODO(zdapeng): delete me + boolean temporarilyDisableRetry; Channelz channelz = Channelz.instance(); @@ -315,13 +319,13 @@ public abstract class AbstractManagedChannelImplBuilder @Override public final T disableRetry() { - retryDisabled = true; + retryEnabled = false; return thisT(); } @Override public final T enableRetry() { - retryDisabled = false; + retryEnabled = true; return thisT(); } @@ -398,8 +402,9 @@ public abstract class AbstractManagedChannelImplBuilder final List getEffectiveInterceptors() { List effectiveInterceptors = new ArrayList(this.interceptors); + temporarilyDisableRetry = false; if (statsEnabled) { - retryDisabled = true; + temporarilyDisableRetry = true; CensusStatsModule censusStats = this.censusStatsOverride; if (censusStats == null) { censusStats = new CensusStatsModule(GrpcUtil.STOPWATCH_SUPPLIER, true); @@ -410,7 +415,7 @@ public abstract class AbstractManagedChannelImplBuilder 0, censusStats.getClientInterceptor(recordStartedRpcs, recordFinishedRpcs)); } if (tracingEnabled) { - retryDisabled = true; + temporarilyDisableRetry = true; CensusTracingModule censusTracing = new CensusTracingModule(Tracing.getTracer(), Tracing.getPropagationComponent().getBinaryFormat()); diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 593ca193e8..d313df89e5 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -22,7 +22,7 @@ import static com.google.common.base.Preconditions.checkState; import static io.grpc.ConnectivityState.IDLE; import static io.grpc.ConnectivityState.SHUTDOWN; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; -import static io.grpc.internal.RetriableStream.RetryPolicy.DEFAULT; +import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; @@ -55,8 +55,6 @@ import io.grpc.Status; import io.grpc.internal.Channelz.ChannelStats; import io.grpc.internal.ClientCallImpl.ClientTransportProvider; import io.grpc.internal.RetriableStream.ChannelBufferMeter; -import io.grpc.internal.RetriableStream.RetryPolicies; -import io.grpc.internal.RetriableStream.RetryPolicy; import io.grpc.internal.RetriableStream.Throttle; import java.net.URI; import java.net.URISyntaxException; @@ -135,7 +133,7 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented( method, headers, channelBufferUsed, perRpcBufferLimit, channelBufferLimit, getCallExecutor(callOptions), transportFactory.getScheduledExecutorService(), - retryPolicy, throttle) { + callOptions.getOption(RETRY_POLICY_KEY), throttle) { @Override Status prestart() { return uncommittedRetriableStreamsRegistry.add(this); @@ -539,6 +533,8 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented implements ClientStream { @@ -68,7 +65,8 @@ abstract class RetriableStream implements ClientStream { private final ScheduledExecutorService scheduledExecutorService; // Must not modify it. private final Metadata headers; - private final RetryPolicy retryPolicy; + private final RetryPolicy.Provider retryPolicyProvider; + private RetryPolicy retryPolicy; /** Must be held when updating state, accessing state.buffer, or certain substream attributes. */ private final Object lock = new Object(); @@ -94,13 +92,13 @@ abstract class RetriableStream implements ClientStream { private ClientStreamListener masterListener; private Future scheduledRetry; - private double nextBackoffIntervalInSeconds; + private long nextBackoffIntervalNanos; RetriableStream( MethodDescriptor method, Metadata headers, ChannelBufferMeter channelBufferUsed, long perRpcBufferLimit, long channelBufferLimit, Executor callExecutor, ScheduledExecutorService scheduledExecutorService, - RetryPolicy retryPolicy, @Nullable Throttle throttle) { + RetryPolicy.Provider retryPolicyProvider, @Nullable Throttle throttle) { this.method = method; this.channelBufferUsed = channelBufferUsed; this.perRpcBufferLimit = perRpcBufferLimit; @@ -108,8 +106,7 @@ abstract class RetriableStream implements ClientStream { this.callExecutor = callExecutor; this.scheduledExecutorService = scheduledExecutorService; this.headers = headers; - this.retryPolicy = checkNotNull(retryPolicy, "retryPolicy"); - nextBackoffIntervalInSeconds = retryPolicy.initialBackoffInSeconds; + this.retryPolicyProvider = checkNotNull(retryPolicyProvider, "retryPolicyProvider"); this.throttle = throttle; } @@ -572,6 +569,12 @@ abstract class RetriableStream implements ClientStream { // TODO(zdapeng): cancel all scheduled hedges (TBD) } else { noMoreTransparentRetry = true; + + if (retryPolicy == null) { + retryPolicy = retryPolicyProvider.get(); + nextBackoffIntervalNanos = retryPolicy.initialBackoffNanos; + } + RetryPlan retryPlan = makeRetryDecision(retryPolicy, status, trailers); if (retryPlan.shouldRetry) { // The check state.winningSubstream == null, checking if is not already committed, is @@ -591,8 +594,8 @@ abstract class RetriableStream implements ClientStream { }); } }, - retryPlan.backoffInMillis, - TimeUnit.MILLISECONDS); + retryPlan.backoffNanos, + TimeUnit.NANOSECONDS); return; } } @@ -617,45 +620,46 @@ abstract class RetriableStream implements ClientStream { // TODO(zdapeng): add HedgingPolicy as param private RetryPlan makeRetryDecision(RetryPolicy retryPolicy, Status status, Metadata trailer) { boolean shouldRetry = false; - long backoffInMillis = 0L; + long backoffNanos = 0L; boolean isRetryableStatusCode = retryPolicy.retryableStatusCodes.contains(status.getCode()); String pushbackStr = trailer.get(GRPC_RETRY_PUSHBACK_MS); - Integer pushback = null; + Integer pushbackMillis = null; if (pushbackStr != null) { try { - pushback = Integer.valueOf(pushbackStr); + pushbackMillis = Integer.valueOf(pushbackStr); } catch (NumberFormatException e) { - pushback = -1; + pushbackMillis = -1; } } boolean isThrottled = false; if (throttle != null) { - if (isRetryableStatusCode || (pushback != null && pushback < 0)) { + if (isRetryableStatusCode || (pushbackMillis != null && pushbackMillis < 0)) { isThrottled = !throttle.onQualifiedFailureThenCheckIsAboveThreshold(); } } if (retryPolicy.maxAttempts > substream.previousAttempts + 1 && !isThrottled) { - if (pushback == null) { + if (pushbackMillis == null) { if (isRetryableStatusCode) { shouldRetry = true; - backoffInMillis = (long) (nextBackoffIntervalInSeconds * 1000D * random.nextDouble()); - nextBackoffIntervalInSeconds = Math.min( - nextBackoffIntervalInSeconds * retryPolicy.backoffMultiplier, - retryPolicy.maxBackoffInSeconds); + backoffNanos = (long) (nextBackoffIntervalNanos * random.nextDouble()); + nextBackoffIntervalNanos = Math.min( + (long) (nextBackoffIntervalNanos * retryPolicy.backoffMultiplier), + retryPolicy.maxBackoffNanos); + } // else no retry - } else if (pushback >= 0) { + } else if (pushbackMillis >= 0) { shouldRetry = true; - backoffInMillis = pushback; - nextBackoffIntervalInSeconds = retryPolicy.initialBackoffInSeconds; + backoffNanos = TimeUnit.MILLISECONDS.toNanos(pushbackMillis); + nextBackoffIntervalNanos = retryPolicy.initialBackoffNanos; } // else no retry } // else no retry // TODO(zdapeng): transparent retry // TODO(zdapeng): hedging - return new RetryPlan(shouldRetry, backoffInMillis); + return new RetryPlan(shouldRetry, backoffNanos); } @Override @@ -972,72 +976,14 @@ abstract class RetriableStream implements ClientStream { } } - interface RetryPolicies { - @Nonnull - RetryPolicy get(MethodDescriptor method); - } - - @Immutable - static final class RetryPolicy { - private final int maxAttempts; - private final double initialBackoffInSeconds; - private final double maxBackoffInSeconds; - private final double backoffMultiplier; - private final Collection retryableStatusCodes; - - RetryPolicy( - int maxAttempts, double initialBackoffInSeconds, double maxBackoffInSeconds, - double backoffMultiplier, Collection retryableStatusCodes) { - checkArgument(maxAttempts >= 1, "maxAttempts"); - this.maxAttempts = maxAttempts; - checkArgument(initialBackoffInSeconds >= 0D, "initialBackoffInSeconds"); - this.initialBackoffInSeconds = initialBackoffInSeconds; - checkArgument( - maxBackoffInSeconds >= initialBackoffInSeconds, - "maxBackoffInSeconds should be at least initialBackoffInSeconds"); - this.maxBackoffInSeconds = maxBackoffInSeconds; - checkArgument(backoffMultiplier > 0D, "backoffMultiplier"); - this.backoffMultiplier = backoffMultiplier; - this.retryableStatusCodes = Collections.unmodifiableSet( - new HashSet(checkNotNull(retryableStatusCodes, "retryableStatusCodes"))); - } - - /** No retry. */ - static final RetryPolicy DEFAULT = - new RetryPolicy(1, 0, 0, 1, Collections.emptyList()); - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof RetryPolicy)) { - return false; - } - RetryPolicy that = (RetryPolicy) o; - return maxAttempts == that.maxAttempts - && Double.compare(backoffMultiplier, that.backoffMultiplier) == 0 - && Double.compare(initialBackoffInSeconds, that.initialBackoffInSeconds) == 0 - && Double.compare(maxBackoffInSeconds, that.maxBackoffInSeconds) == 0 - && Objects.equal(retryableStatusCodes, that.retryableStatusCodes); - } - - @Override - public int hashCode() { - return Objects.hashCode( - maxAttempts, initialBackoffInSeconds, maxBackoffInSeconds, backoffMultiplier, - retryableStatusCodes); - } - } - private static final class RetryPlan { final boolean shouldRetry; // TODO(zdapeng) boolean hasHedging - final long backoffInMillis; + final long backoffNanos; - RetryPlan(boolean shouldRetry, long backoffInMillis) { + RetryPlan(boolean shouldRetry, long backoffNanos) { this.shouldRetry = shouldRetry; - this.backoffInMillis = backoffInMillis; + this.backoffNanos = backoffNanos; } } } diff --git a/core/src/main/java/io/grpc/internal/RetryPolicy.java b/core/src/main/java/io/grpc/internal/RetryPolicy.java new file mode 100644 index 0000000000..96d7e7d54f --- /dev/null +++ b/core/src/main/java/io/grpc/internal/RetryPolicy.java @@ -0,0 +1,107 @@ +/* + * Copyright 2018, gRPC Authors All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.internal; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import io.grpc.Status; +import io.grpc.Status.Code; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; + +/** + * Retry policy data object. + */ +@Immutable +final class RetryPolicy { + final int maxAttempts; + final long initialBackoffNanos; + final long maxBackoffNanos; + final double backoffMultiplier; + final Set retryableStatusCodes; + + /** No retry. */ + static final RetryPolicy DEFAULT = + new RetryPolicy(1, 0, 0, 1, Collections.emptySet()); + + /** + * The caller is supposed to have validated the arguments and handled throwing exception or + * logging warnings already, so we avoid repeating args check here. + */ + RetryPolicy( + int maxAttempts, + long initialBackoffNanos, + long maxBackoffNanos, + double backoffMultiplier, + @Nonnull Set retryableStatusCodes) { + this.maxAttempts = maxAttempts; + this.initialBackoffNanos = initialBackoffNanos; + this.maxBackoffNanos = maxBackoffNanos; + this.backoffMultiplier = backoffMultiplier; + this.retryableStatusCodes = ImmutableSet.copyOf(retryableStatusCodes); + } + + @Override + public int hashCode() { + return Objects.hashCode( + maxAttempts, + initialBackoffNanos, + maxBackoffNanos, + backoffMultiplier, + retryableStatusCodes); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof RetryPolicy)) { + return false; + } + RetryPolicy that = (RetryPolicy) other; + return Objects.equal(this.maxAttempts, that.maxAttempts) + && Objects.equal(this.initialBackoffNanos, that.initialBackoffNanos) + && Objects.equal(this.maxBackoffNanos, that.maxBackoffNanos) + && Double.compare(this.backoffMultiplier, that.backoffMultiplier) == 0 + && Objects.equal(this.retryableStatusCodes, that.retryableStatusCodes); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("maxAttempts", maxAttempts) + .add("initialBackoffNanos", initialBackoffNanos) + .add("maxBackoffNanos", maxBackoffNanos) + .add("backoffMultiplier", backoffMultiplier) + .add("retryableStatusCodes", retryableStatusCodes) + .toString(); + } + + /** + * Providers the most suitable retry policy for a call when this will have to provide a retry + * policy. + */ + interface Provider { + + /** + * This method is used no more than once for each call. + */ + @Nonnull + RetryPolicy get(); + } +} diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java index e4e47b50d1..d02b457576 100644 --- a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java +++ b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java @@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.CheckForNull; /** * Modifies RPCs in in conformance with a Service Config. @@ -56,7 +57,17 @@ final class ServiceConfigInterceptor implements ClientInterceptor { final AtomicReference> serviceMap = new AtomicReference>(); - ServiceConfigInterceptor() {} + private final boolean retryEnabled; + private final int maxRetryAttemptsLimit; + + // Setting this to true and observing this equal to true are run in different threads. + private volatile boolean nameResolveComplete; + + ServiceConfigInterceptor( + boolean retryEnabled, int maxRetryAttemptsLimit) { + this.retryEnabled = retryEnabled; + this.maxRetryAttemptsLimit = maxRetryAttemptsLimit; + } void handleUpdate(Map serviceConfig) { Map newServiceMethodConfigs = new HashMap(); @@ -69,11 +80,12 @@ final class ServiceConfigInterceptor implements ClientInterceptor { ServiceConfigUtil.getMethodConfigFromServiceConfig(serviceConfig); if (methodConfigs == null) { logger.log(Level.FINE, "No method configs found, skipping"); + nameResolveComplete = true; return; } for (Map methodConfig : methodConfigs) { - MethodInfo info = new MethodInfo(methodConfig); + MethodInfo info = new MethodInfo(methodConfig, retryEnabled, maxRetryAttemptsLimit); List> nameList = ServiceConfigUtil.getNameListFromMethodConfig(methodConfig); @@ -104,10 +116,11 @@ final class ServiceConfigInterceptor implements ClientInterceptor { // Okay, service config is good, swap it. serviceMethodMap.set(Collections.unmodifiableMap(newServiceMethodConfigs)); serviceMap.set(Collections.unmodifiableMap(newServiceConfigs)); + nameResolveComplete = true; } /** - * Equivalent of MethodConfig from a ServiceConfig. + * Equivalent of MethodConfig from a ServiceConfig with restrictions from Channel setting. */ static final class MethodInfo { final Long timeoutNanos; @@ -116,7 +129,13 @@ final class ServiceConfigInterceptor implements ClientInterceptor { final Integer maxOutboundMessageSize; final RetryPolicy retryPolicy; - MethodInfo(Map methodConfig) { + /** + * Constructor. + * + * @param retryEnabled when false, the argument maxRetryAttemptsLimit will have no effect. + */ + MethodInfo( + Map methodConfig, boolean retryEnabled, int maxRetryAttemptsLimit) { timeoutNanos = ServiceConfigUtil.getTimeoutFromMethodConfig(methodConfig); waitForReady = ServiceConfigUtil.getWaitForReadyFromMethodConfig(methodConfig); maxInboundMessageSize = @@ -134,14 +153,16 @@ final class ServiceConfigInterceptor implements ClientInterceptor { "maxOutboundMessageSize %s exceeds bounds", maxOutboundMessageSize); } - Map policy = ServiceConfigUtil.getRetryPolicyFromMethodConfig(methodConfig); - retryPolicy = policy == null ? null : new RetryPolicy(policy); + Map policy = + retryEnabled ? ServiceConfigUtil.getRetryPolicyFromMethodConfig(methodConfig) : null; + retryPolicy = + policy == null ? RetryPolicy.DEFAULT : retryPolicy(policy, maxRetryAttemptsLimit); } @Override public int hashCode() { return Objects.hashCode( - timeoutNanos, waitForReady, maxInboundMessageSize, maxOutboundMessageSize); + timeoutNanos, waitForReady, maxInboundMessageSize, maxOutboundMessageSize, retryPolicy); } @Override @@ -153,7 +174,8 @@ final class ServiceConfigInterceptor implements ClientInterceptor { return Objects.equal(this.timeoutNanos, that.timeoutNanos) && Objects.equal(this.waitForReady, that.waitForReady) && Objects.equal(this.maxInboundMessageSize, that.maxInboundMessageSize) - && Objects.equal(this.maxOutboundMessageSize, that.maxOutboundMessageSize); + && Objects.equal(this.maxOutboundMessageSize, that.maxOutboundMessageSize) + && Objects.equal(this.retryPolicy, that.retryPolicy); } @Override @@ -163,125 +185,95 @@ final class ServiceConfigInterceptor implements ClientInterceptor { .add("waitForReady", waitForReady) .add("maxInboundMessageSize", maxInboundMessageSize) .add("maxOutboundMessageSize", maxOutboundMessageSize) + .add("retryPolicy", retryPolicy) .toString(); } - static final class RetryPolicy { - final int maxAttempts; - final long initialBackoffNanos; - final long maxBackoffNanos; - final double backoffMultiplier; - final Set retryableStatusCodes; + private static RetryPolicy retryPolicy(Map retryPolicy, int maxAttemptsLimit) { + int maxAttempts = checkNotNull( + ServiceConfigUtil.getMaxAttemptsFromRetryPolicy(retryPolicy), + "maxAttempts cannot be empty"); + checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts); + maxAttempts = Math.min(maxAttempts, maxAttemptsLimit); - RetryPolicy(Map retryPolicy) { - maxAttempts = checkNotNull( - ServiceConfigUtil.getMaxAttemptsFromRetryPolicy(retryPolicy), - "maxAttempts cannot be empty"); - checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts); + long initialBackoffNanos = checkNotNull( + ServiceConfigUtil.getInitialBackoffNanosFromRetryPolicy(retryPolicy), + "initialBackoff cannot be empty"); + checkArgument( + initialBackoffNanos > 0, + "initialBackoffNanos must be greater than 0: %s", + initialBackoffNanos); - initialBackoffNanos = checkNotNull( - ServiceConfigUtil.getInitialBackoffNanosFromRetryPolicy(retryPolicy), - "initialBackoff cannot be empty"); - checkArgument( - initialBackoffNanos > 0, - "initialBackoffNanos must be greater than 0: %s", - initialBackoffNanos); + long maxBackoffNanos = checkNotNull( + ServiceConfigUtil.getMaxBackoffNanosFromRetryPolicy(retryPolicy), + "maxBackoff cannot be empty"); + checkArgument( + maxBackoffNanos > 0, "maxBackoff must be greater than 0: %s", maxBackoffNanos); - maxBackoffNanos = checkNotNull( - ServiceConfigUtil.getMaxBackoffNanosFromRetryPolicy(retryPolicy), - "maxBackoff cannot be empty"); - checkArgument( - maxBackoffNanos > 0, "maxBackoff must be greater than 0: %s", maxBackoffNanos); + double backoffMultiplier = checkNotNull( + ServiceConfigUtil.getBackoffMultiplierFromRetryPolicy(retryPolicy), + "backoffMultiplier cannot be empty"); + checkArgument( + backoffMultiplier > 0, + "backoffMultiplier must be greater than 0: %s", + backoffMultiplier); - backoffMultiplier = checkNotNull( - ServiceConfigUtil.getBackoffMultiplierFromRetryPolicy(retryPolicy), - "backoffMultiplier cannot be empty"); - checkArgument( - backoffMultiplier > 0, - "backoffMultiplier must be greater than 0: %s", - backoffMultiplier); - - List rawCodes = - ServiceConfigUtil.getRetryableStatusCodesFromRetryPolicy(retryPolicy); - checkNotNull(rawCodes, "rawCodes must be present"); - checkArgument(!rawCodes.isEmpty(), "rawCodes can't be empty"); - EnumSet codes = EnumSet.noneOf(Code.class); - // service config doesn't say if duplicates are allowed, so just accept them. - for (String rawCode : rawCodes) { - codes.add(Code.valueOf(rawCode)); - } - retryableStatusCodes = Collections.unmodifiableSet(codes); - } - - @VisibleForTesting - RetryPolicy( - int maxAttempts, - long initialBackoffNanos, - long maxBackoffNanos, - double backoffMultiplier, - Set retryableStatusCodes) { - this.maxAttempts = maxAttempts; - this.initialBackoffNanos = initialBackoffNanos; - this.maxBackoffNanos = maxBackoffNanos; - this.backoffMultiplier = backoffMultiplier; - this.retryableStatusCodes = retryableStatusCodes; - } - - @Override - public int hashCode() { - return Objects.hashCode( - maxAttempts, - initialBackoffNanos, - maxBackoffNanos, - backoffMultiplier, - retryableStatusCodes); - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof RetryPolicy)) { - return false; - } - RetryPolicy that = (RetryPolicy) other; - return Objects.equal(this.maxAttempts, that.maxAttempts) - && Objects.equal(this.initialBackoffNanos, that.initialBackoffNanos) - && Objects.equal(this.maxBackoffNanos, that.maxBackoffNanos) - && Objects.equal(this.backoffMultiplier, that.backoffMultiplier) - && Objects.equal(this.retryableStatusCodes, that.retryableStatusCodes); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("maxAttempts", maxAttempts) - .add("initialBackoffNanos", initialBackoffNanos) - .add("maxBackoffNanos", maxBackoffNanos) - .add("backoffMultiplier", backoffMultiplier) - .add("retryableStatusCodes", retryableStatusCodes) - .toString(); + List rawCodes = + ServiceConfigUtil.getRetryableStatusCodesFromRetryPolicy(retryPolicy); + checkNotNull(rawCodes, "rawCodes must be present"); + checkArgument(!rawCodes.isEmpty(), "rawCodes can't be empty"); + EnumSet codes = EnumSet.noneOf(Code.class); + // service config doesn't say if duplicates are allowed, so just accept them. + for (String rawCode : rawCodes) { + codes.add(Code.valueOf(rawCode)); } + Set retryableStatusCodes = Collections.unmodifiableSet(codes); + return new RetryPolicy( + maxAttempts, initialBackoffNanos, maxBackoffNanos, backoffMultiplier, + retryableStatusCodes); } } - static final CallOptions.Key RETRY_POLICY_KEY = + static final CallOptions.Key RETRY_POLICY_KEY = CallOptions.Key.of("internal-retry-policy", null); @Override public ClientCall interceptCall( - MethodDescriptor method, CallOptions callOptions, Channel next) { - Map localServiceMethodMap = serviceMethodMap.get(); - MethodInfo info = null; - if (localServiceMethodMap != null) { - info = localServiceMethodMap.get(method.getFullMethodName()); - } - if (info == null) { - Map localServiceMap = serviceMap.get(); - if (localServiceMap != null) { - info = localServiceMap.get( - MethodDescriptor.extractFullServiceName(method.getFullMethodName())); + final MethodDescriptor method, CallOptions callOptions, Channel next) { + if (retryEnabled) { + if (nameResolveComplete) { + final RetryPolicy retryPolicy = getRetryPolicyFromConfig(method); + final class ImmediateRetryPolicyProvider implements RetryPolicy.Provider { + @Override + public RetryPolicy get() { + return retryPolicy; + } + } + + callOptions = callOptions.withOption(RETRY_POLICY_KEY, new ImmediateRetryPolicyProvider()); + } else { + final class DelayedRetryPolicyProvider implements RetryPolicy.Provider { + /** + * Returns RetryPolicy.DEFAULT if name resolving is not complete at the moment the method + * is invoked, otherwise returns the RetryPolicy computed from service config. + * + *

Note that this method is used no more than once for each call. + */ + @Override + public RetryPolicy get() { + if (!nameResolveComplete) { + return RetryPolicy.DEFAULT; + } + return getRetryPolicyFromConfig(method); + } + } + + callOptions = callOptions.withOption(RETRY_POLICY_KEY, new DelayedRetryPolicyProvider()); } } + + MethodInfo info = getMethodInfo(method); if (info == null) { return next.newCall(method, callOptions); } @@ -316,10 +308,33 @@ final class ServiceConfigInterceptor implements ClientInterceptor { callOptions = callOptions.withMaxOutboundMessageSize(info.maxOutboundMessageSize); } } - if (info.retryPolicy != null) { - callOptions = callOptions.withOption(RETRY_POLICY_KEY, info.retryPolicy); - } return next.newCall(method, callOptions); } + + @CheckForNull + private MethodInfo getMethodInfo(MethodDescriptor method) { + Map localServiceMethodMap = serviceMethodMap.get(); + MethodInfo info = null; + if (localServiceMethodMap != null) { + info = localServiceMethodMap.get(method.getFullMethodName()); + } + if (info == null) { + Map localServiceMap = serviceMap.get(); + if (localServiceMap != null) { + info = localServiceMap.get( + MethodDescriptor.extractFullServiceName(method.getFullMethodName())); + } + } + return info; + } + + @VisibleForTesting + RetryPolicy getRetryPolicyFromConfig(MethodDescriptor method) { + MethodInfo info = getMethodInfo(method); + if (info == null || info.retryPolicy == null) { + return RetryPolicy.DEFAULT; + } + return info.retryPolicy; + } } diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java b/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java index d5f3c6aec5..51d3a6f94b 100644 --- a/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java +++ b/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java @@ -20,17 +20,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.math.LongMath.checkedAdd; -import io.grpc.MethodDescriptor; -import io.grpc.Status.Code; -import io.grpc.internal.RetriableStream.RetryPolicies; -import io.grpc.internal.RetriableStream.RetryPolicy; import io.grpc.internal.RetriableStream.Throttle; import java.text.ParseException; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -65,109 +58,6 @@ final class ServiceConfigUtil { private ServiceConfigUtil() {} - /** - * Gets retry policies from the service config. - * - * @throws ClassCastException if the service config doesn't parse properly - */ - static RetryPolicies getRetryPolicies( - @Nullable Map serviceConfig, int maxAttemptsLimit) { - final Map fullMethodNameMap = new HashMap(); - final Map serviceNameMap = new HashMap(); - - if (serviceConfig != null) { - - /* schema as follows - { - "methodConfig": [ - { - "name": [ - { - "service": string, - "method": string, // Optional - } - ], - "retryPolicy": { - "maxAttempts": number, - "initialBackoff": string, // Long decimal with "s" appended - "maxBackoff": string, // Long decimal with "s" appended - "backoffMultiplier": number - "retryableStatusCodes": [] - } - } - ] - } - */ - - if (serviceConfig.containsKey("methodConfig")) { - List methodConfigs = getList(serviceConfig, "methodConfig"); - for (int i = 0; i < methodConfigs.size(); i++) { - Map methodConfig = getObject(methodConfigs, i); - if (methodConfig.containsKey("retryPolicy")) { - Map retryPolicy = getObject(methodConfig, "retryPolicy"); - - int maxAttempts = getDouble(retryPolicy, "maxAttempts").intValue(); - maxAttempts = Math.min(maxAttempts, maxAttemptsLimit); - - String initialBackoffStr = getString(retryPolicy, "initialBackoff"); - checkState( - initialBackoffStr.charAt(initialBackoffStr.length() - 1) == 's', - "invalid value of initialBackoff"); - double initialBackoff = - Double.parseDouble(initialBackoffStr.substring(0, initialBackoffStr.length() - 1)); - - String maxBackoffStr = getString(retryPolicy, "maxBackoff"); - checkState( - maxBackoffStr.charAt(maxBackoffStr.length() - 1) == 's', - "invalid value of maxBackoff"); - double maxBackoff = - Double.parseDouble(maxBackoffStr.substring(0, maxBackoffStr.length() - 1)); - - double backoffMultiplier = getDouble(retryPolicy, "backoffMultiplier"); - - List retryableStatusCodes = getList(retryPolicy, "retryableStatusCodes"); - Set codeSet = new HashSet(retryableStatusCodes.size()); - for (int j = 0; j < retryableStatusCodes.size(); j++) { - String code = getString(retryableStatusCodes, j); - codeSet.add(Code.valueOf(code)); - } - - RetryPolicy pojoPolicy = new RetryPolicy( - maxAttempts, initialBackoff, maxBackoff, backoffMultiplier, codeSet); - - List names = getList(methodConfig, "name"); - for (int j = 0; j < names.size(); j++) { - Map name = getObject(names, j); - String service = getString(name, "service"); - if (name.containsKey("method")) { - String method = getString(name, "method"); - fullMethodNameMap.put( - MethodDescriptor.generateFullMethodName(service, method), pojoPolicy); - } else { - serviceNameMap.put(service, pojoPolicy); - } - } - } - } - } - } - - return new RetryPolicies() { - @Override - public RetryPolicy get(MethodDescriptor method) { - RetryPolicy retryPolicy = fullMethodNameMap.get(method.getFullMethodName()); - if (retryPolicy == null) { - retryPolicy = serviceNameMap - .get(MethodDescriptor.extractFullServiceName(method.getFullMethodName())); - } - if (retryPolicy == null) { - retryPolicy = RetryPolicy.DEFAULT; - } - return retryPolicy; - } - }; - } - @Nullable static Throttle getThrottlePolicy(@Nullable Map serviceConfig) { String retryThrottlingKey = "retryThrottling"; diff --git a/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java b/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java index 21df0e9be4..4c9b5e4a3c 100644 --- a/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java +++ b/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java @@ -393,16 +393,16 @@ public class AbstractManagedChannelImplBuilderTest { Builder builder = new Builder("target"); builder.enableRetry(); - assertFalse(builder.retryDisabled); + assertTrue(builder.retryEnabled); builder.disableRetry(); - assertTrue(builder.retryDisabled); + assertFalse(builder.retryEnabled); builder.enableRetry(); - assertFalse(builder.retryDisabled); + assertTrue(builder.retryEnabled); builder.disableRetry(); - assertTrue(builder.retryDisabled); + assertFalse(builder.retryEnabled); } static class Builder extends AbstractManagedChannelImplBuilder { diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java index 5df1f0d4bd..c989f5e41e 100644 --- a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java +++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.MoreExecutors; import io.grpc.CallOptions; import io.grpc.ClientStreamTracer; @@ -52,12 +53,10 @@ import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.StringMarshaller; import io.grpc.internal.RetriableStream.ChannelBufferMeter; -import io.grpc.internal.RetriableStream.RetryPolicy; import io.grpc.internal.RetriableStream.Throttle; import io.grpc.internal.StreamListener.MessageProducer; import java.io.InputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.Executor; @@ -86,8 +85,8 @@ public class RetriableStreamTest { private static final long PER_RPC_BUFFER_LIMIT = 1000; private static final long CHANNEL_BUFFER_LIMIT = 2000; private static final int MAX_ATTEMPTS = 6; - private static final double INITIAL_BACKOFF_IN_SECONDS = 100D; - private static final double MAX_BACKOFF_IN_SECONDS = 700D; + private static final long INITIAL_BACKOFF_IN_SECONDS = 100; + private static final long MAX_BACKOFF_IN_SECONDS = 700; private static final double BACKOFF_MULTIPLIER = 2D; private static final double FAKE_RANDOM = .5D; @@ -107,8 +106,11 @@ public class RetriableStreamTest { private static final Code NON_RETRIABLE_STATUS_CODE = Code.INTERNAL; private static final RetryPolicy RETRY_POLICY = new RetryPolicy( - MAX_ATTEMPTS, INITIAL_BACKOFF_IN_SECONDS, MAX_BACKOFF_IN_SECONDS, BACKOFF_MULTIPLIER, - Arrays.asList(RETRIABLE_STATUS_CODE_1, RETRIABLE_STATUS_CODE_2)); + MAX_ATTEMPTS, + TimeUnit.SECONDS.toNanos(INITIAL_BACKOFF_IN_SECONDS), + TimeUnit.SECONDS.toNanos(MAX_BACKOFF_IN_SECONDS), + BACKOFF_MULTIPLIER, + ImmutableSet.of(RETRIABLE_STATUS_CODE_1, RETRIABLE_STATUS_CODE_2)); private final RetriableStreamRecorder retriableStreamRecorder = mock(RetriableStreamRecorder.class); @@ -128,10 +130,18 @@ public class RetriableStreamTest { ChannelBufferMeter channelBufferUsed, long perRpcBufferLimit, long channelBufferLimit, Executor callExecutor, ScheduledExecutorService scheduledExecutorService, - RetryPolicy retryPolicy, + final RetryPolicy retryPolicy, @Nullable Throttle throttle) { - super(method, headers, channelBufferUsed, perRpcBufferLimit, channelBufferLimit, callExecutor, - scheduledExecutorService, retryPolicy, throttle); + super( + method, headers, channelBufferUsed, perRpcBufferLimit, channelBufferLimit, callExecutor, + scheduledExecutorService, + new RetryPolicy.Provider() { + @Override + public RetryPolicy get() { + return retryPolicy; + } + }, + throttle); } @Override diff --git a/core/src/test/java/io/grpc/internal/RetryPolicyTest.java b/core/src/test/java/io/grpc/internal/RetryPolicyTest.java index bf98637967..d0ab506907 100644 --- a/core/src/test/java/io/grpc/internal/RetryPolicyTest.java +++ b/core/src/test/java/io/grpc/internal/RetryPolicyTest.java @@ -16,24 +16,30 @@ package io.grpc.internal; +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY; import static java.lang.Double.parseDouble; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import com.google.common.collect.ImmutableSet; +import io.grpc.CallOptions; +import io.grpc.Channel; import io.grpc.MethodDescriptor; import io.grpc.Status.Code; -import io.grpc.internal.RetriableStream.RetryPolicies; -import io.grpc.internal.RetriableStream.RetryPolicy; import io.grpc.internal.RetriableStream.Throttle; import io.grpc.testing.TestMethodDescriptors; import java.io.BufferedReader; import java.io.InputStreamReader; -import java.util.Arrays; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; /** Unit tests for RetryPolicy. */ @RunWith(JUnit4.class) @@ -55,37 +61,96 @@ public class RetryPolicyTest { @SuppressWarnings("unchecked") Map serviceConfig = (Map) serviceConfigObj; - RetryPolicies retryPolicies = ServiceConfigUtil.getRetryPolicies(serviceConfig, 4); - assertNotNull(retryPolicies); + + ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( + true /* retryEnabled */, 4 /* maxRetryAttemptsLimit */); + serviceConfigInterceptor.handleUpdate(serviceConfig); MethodDescriptor.Builder builder = TestMethodDescriptors.voidMethod().toBuilder(); + MethodDescriptor method = builder.setFullMethodName("not/exist").build(); assertEquals( RetryPolicy.DEFAULT, - retryPolicies.get(builder.setFullMethodName("not/exist").build())); + serviceConfigInterceptor.getRetryPolicyFromConfig(method)); + + method = builder.setFullMethodName("not_exist/Foo1").build(); assertEquals( RetryPolicy.DEFAULT, - retryPolicies.get(builder.setFullMethodName("not_exist/Foo1").build())); + serviceConfigInterceptor.getRetryPolicyFromConfig(method)); + + method = builder.setFullMethodName("SimpleService1/not_exist").build(); assertEquals( new RetryPolicy( - 3, parseDouble("2.1"), parseDouble("2.2"), parseDouble("3"), - Arrays.asList(Code.UNAVAILABLE, Code.RESOURCE_EXHAUSTED)), - retryPolicies.get(builder.setFullMethodName("SimpleService1/not_exist").build())); + 3, + TimeUnit.MILLISECONDS.toNanos(2100), + TimeUnit.MILLISECONDS.toNanos(2200), + parseDouble("3"), + ImmutableSet.of(Code.UNAVAILABLE, Code.RESOURCE_EXHAUSTED)), + serviceConfigInterceptor.getRetryPolicyFromConfig(method)); + + method = builder.setFullMethodName("SimpleService1/Foo1").build(); assertEquals( new RetryPolicy( - 4, parseDouble(".1"), parseDouble("1"), parseDouble("2"), - Arrays.asList(Code.UNAVAILABLE)), - retryPolicies.get(builder.setFullMethodName("SimpleService1/Foo1").build())); + 4, + TimeUnit.MILLISECONDS.toNanos(100), + TimeUnit.MILLISECONDS.toNanos(1000), + parseDouble("2"), + ImmutableSet.of(Code.UNAVAILABLE)), + serviceConfigInterceptor.getRetryPolicyFromConfig(method)); + method = builder.setFullMethodName("SimpleService2/not_exist").build(); assertEquals( RetryPolicy.DEFAULT, - retryPolicies.get(builder.setFullMethodName("SimpleService2/not_exist").build())); + serviceConfigInterceptor.getRetryPolicyFromConfig(method)); + + method = builder.setFullMethodName("SimpleService2/Foo2").build(); assertEquals( new RetryPolicy( - 4, parseDouble(".1"), parseDouble("1"), parseDouble("2"), - Arrays.asList(Code.UNAVAILABLE)), - retryPolicies.get(builder.setFullMethodName("SimpleService2/Foo2").build())); + 4, + TimeUnit.MILLISECONDS.toNanos(100), + TimeUnit.MILLISECONDS.toNanos(1000), + parseDouble("2"), + ImmutableSet.of(Code.UNAVAILABLE)), + serviceConfigInterceptor.getRetryPolicyFromConfig(method)); + } finally { + if (reader != null) { + reader.close(); + } + } + } + + @Test + public void getRetryPolicies_retryDisabled() throws Exception { + Channel channel = mock(Channel.class); + ArgumentCaptor callOptionsCap = ArgumentCaptor.forClass(CallOptions.class); + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(RetryPolicyTest.class.getResourceAsStream( + "/io/grpc/internal/test_retry_service_config.json"), "UTF-8")); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append('\n'); + } + Object serviceConfigObj = JsonParser.parse(sb.toString()); + assertTrue(serviceConfigObj instanceof Map); + + @SuppressWarnings("unchecked") + Map serviceConfig = (Map) serviceConfigObj; + + ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor( + false /* retryEnabled */, 4 /* maxRetryAttemptsLimit */); + serviceConfigInterceptor.handleUpdate(serviceConfig); + + MethodDescriptor.Builder builder = TestMethodDescriptors.voidMethod().toBuilder(); + + MethodDescriptor method = + builder.setFullMethodName("SimpleService1/Foo1").build(); + + serviceConfigInterceptor.interceptCall(method, CallOptions.DEFAULT, channel); + verify(channel).newCall(eq(method), callOptionsCap.capture()); + assertThat(callOptionsCap.getValue().getOption(RETRY_POLICY_KEY)).isNull(); } finally { if (reader != null) { reader.close(); diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java index d4888167fd..060878030f 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java @@ -17,6 +17,7 @@ package io.grpc.internal; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; @@ -58,7 +59,8 @@ public class ServiceConfigInterceptorTest { MockitoAnnotations.initMocks(this); } - private final ServiceConfigInterceptor interceptor = new ServiceConfigInterceptor(); + private final ServiceConfigInterceptor interceptor = new ServiceConfigInterceptor( + true /* retryEnabled */, 5 /* maxRetryAttemptsLimit */); private final String fullMethodName = MethodDescriptor.generateFullMethodName("service", "method"); @@ -98,6 +100,16 @@ public class ServiceConfigInterceptorTest { assertThat(callOptionsCap.getValue().isWaitForReady()).isTrue(); } + @Test + public void handleUpdateNotCalledBeforeInterceptCall() { + interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel); + + verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); + assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse(); + assertThat(callOptionsCap.getValue().getOption(RETRY_POLICY_KEY).get()) + .isEqualTo(RetryPolicy.DEFAULT); + } + @Test public void withMaxRequestSize() { JsonObj name = new JsonObj("service", "service"); @@ -351,9 +363,9 @@ public class ServiceConfigInterceptorTest { assertThat(interceptor.serviceMethodMap.get()) .containsExactly( methodDescriptor.getFullMethodName(), - new MethodInfo(methodConfig)); - assertThat(interceptor.serviceMap.get()) - .containsExactly("service2", new MethodInfo(methodConfig)); + new MethodInfo(methodConfig, false, 1)); + assertThat(interceptor.serviceMap.get()).containsExactly( + "service2", new MethodInfo(methodConfig, false, 1)); } @@ -364,7 +376,7 @@ public class ServiceConfigInterceptorTest { thrown.expectMessage("Duration value is out of range"); - new MethodInfo(methodConfig); + new MethodInfo(methodConfig, false, 1); } @Test @@ -372,7 +384,7 @@ public class ServiceConfigInterceptorTest { JsonObj name = new JsonObj("service", "service"); JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "315576000000s"); - MethodInfo info = new MethodInfo(methodConfig); + MethodInfo info = new MethodInfo(methodConfig, false, 1); assertThat(info.timeoutNanos).isEqualTo(Long.MAX_VALUE); } @@ -386,7 +398,7 @@ public class ServiceConfigInterceptorTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("exceeds bounds"); - new MethodInfo(methodConfig); + new MethodInfo(methodConfig, false, 1); } @Test @@ -397,7 +409,7 @@ public class ServiceConfigInterceptorTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("exceeds bounds"); - new MethodInfo(methodConfig); + new MethodInfo(methodConfig, false, 1); } private static final class NoopMarshaller implements MethodDescriptor.Marshaller { diff --git a/core/src/test/resources/io/grpc/internal/test_retry_service_config.json b/core/src/test/resources/io/grpc/internal/test_retry_service_config.json index ceb14f357f..7274bf7a1e 100644 --- a/core/src/test/resources/io/grpc/internal/test_retry_service_config.json +++ b/core/src/test/resources/io/grpc/internal/test_retry_service_config.json @@ -41,7 +41,7 @@ "waitForReady":true, "retryPolicy":{ "maxAttempts":5, - "initialBackoff":".1s", + "initialBackoff":"0.1s", "maxBackoff":"1s", "backoffMultiplier":2, "retryableStatusCodes":[