mirror of https://github.com/grpc/grpc-java.git
core: use exponential backoff for name resolution (#4105)
This commit is contained in:
parent
d45e1abd8d
commit
ae1fb9467c
|
|
@ -30,6 +30,10 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||||
* <p>The addresses and attributes of a target may be changed over time, thus the caller registers a
|
* <p>The addresses and attributes of a target may be changed over time, thus the caller registers a
|
||||||
* {@link Listener} to receive continuous updates.
|
* {@link Listener} to receive continuous updates.
|
||||||
*
|
*
|
||||||
|
* <p>A {@code NameResolver} does not need to automatically re-resolve on failure. Instead, the
|
||||||
|
* {@link Listener} is responsible for eventually (after an appropriate backoff period) invoking
|
||||||
|
* {@link #refresh()}.
|
||||||
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
|
||||||
|
|
@ -136,7 +140,8 @@ public abstract class NameResolver {
|
||||||
void onAddresses(List<EquivalentAddressGroup> servers, Attributes attributes);
|
void onAddresses(List<EquivalentAddressGroup> servers, Attributes attributes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an error from the resolver.
|
* Handles an error from the resolver. The listener is responsible for eventually invoking
|
||||||
|
* {@link #refresh()} to re-attempt resolution.
|
||||||
*
|
*
|
||||||
* @param error a non-OK status
|
* @param error a non-OK status
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,6 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
@ -79,29 +76,22 @@ final class DnsNameResolver extends NameResolver {
|
||||||
private final String authority;
|
private final String authority;
|
||||||
private final String host;
|
private final String host;
|
||||||
private final int port;
|
private final int port;
|
||||||
private final Resource<ScheduledExecutorService> timerServiceResource;
|
|
||||||
private final Resource<ExecutorService> executorResource;
|
private final Resource<ExecutorService> executorResource;
|
||||||
private final ProxyDetector proxyDetector;
|
private final ProxyDetector proxyDetector;
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private boolean shutdown;
|
private boolean shutdown;
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private ScheduledExecutorService timerService;
|
|
||||||
@GuardedBy("this")
|
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private ScheduledFuture<?> resolutionTask;
|
|
||||||
@GuardedBy("this")
|
|
||||||
private boolean resolving;
|
private boolean resolving;
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private Listener listener;
|
private Listener listener;
|
||||||
|
|
||||||
DnsNameResolver(@Nullable String nsAuthority, String name, Attributes params,
|
DnsNameResolver(@Nullable String nsAuthority, String name, Attributes params,
|
||||||
Resource<ScheduledExecutorService> timerServiceResource,
|
|
||||||
Resource<ExecutorService> executorResource,
|
Resource<ExecutorService> executorResource,
|
||||||
ProxyDetector proxyDetector) {
|
ProxyDetector proxyDetector) {
|
||||||
// TODO: if a DNS server is provided as nsAuthority, use it.
|
// TODO: if a DNS server is provided as nsAuthority, use it.
|
||||||
// https://www.captechconsulting.com/blogs/accessing-the-dusty-corners-of-dns-with-java
|
// https://www.captechconsulting.com/blogs/accessing-the-dusty-corners-of-dns-with-java
|
||||||
this.timerServiceResource = timerServiceResource;
|
|
||||||
this.executorResource = executorResource;
|
this.executorResource = executorResource;
|
||||||
// Must prepend a "//" to the name when constructing a URI, otherwise it will be treated as an
|
// Must prepend a "//" to the name when constructing a URI, otherwise it will be treated as an
|
||||||
// opaque URI, thus the authority and host of the resulted URI would be null.
|
// opaque URI, thus the authority and host of the resulted URI would be null.
|
||||||
|
|
@ -131,7 +121,6 @@ final class DnsNameResolver extends NameResolver {
|
||||||
@Override
|
@Override
|
||||||
public final synchronized void start(Listener listener) {
|
public final synchronized void start(Listener listener) {
|
||||||
Preconditions.checkState(this.listener == null, "already started");
|
Preconditions.checkState(this.listener == null, "already started");
|
||||||
timerService = SharedResourceHolder.get(timerServiceResource);
|
|
||||||
executor = SharedResourceHolder.get(executorResource);
|
executor = SharedResourceHolder.get(executorResource);
|
||||||
this.listener = Preconditions.checkNotNull(listener, "listener");
|
this.listener = Preconditions.checkNotNull(listener, "listener");
|
||||||
resolve();
|
resolve();
|
||||||
|
|
@ -148,11 +137,6 @@ final class DnsNameResolver extends NameResolver {
|
||||||
public void run() {
|
public void run() {
|
||||||
Listener savedListener;
|
Listener savedListener;
|
||||||
synchronized (DnsNameResolver.this) {
|
synchronized (DnsNameResolver.this) {
|
||||||
// If this task is started by refresh(), there might already be a scheduled task.
|
|
||||||
if (resolutionTask != null) {
|
|
||||||
resolutionTask.cancel(false);
|
|
||||||
resolutionTask = null;
|
|
||||||
}
|
|
||||||
if (shutdown) {
|
if (shutdown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -171,16 +155,6 @@ final class DnsNameResolver extends NameResolver {
|
||||||
try {
|
try {
|
||||||
resolvedInetAddrs = delegateResolver.resolve(host);
|
resolvedInetAddrs = delegateResolver.resolve(host);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
synchronized (DnsNameResolver.this) {
|
|
||||||
if (shutdown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Because timerService is the single-threaded GrpcUtil.TIMER_SERVICE in production,
|
|
||||||
// we need to delegate the blocking work to the executor
|
|
||||||
resolutionTask =
|
|
||||||
timerService.schedule(new LogExceptionRunnable(resolutionRunnableOnExecutor),
|
|
||||||
1, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
savedListener.onError(
|
savedListener.onError(
|
||||||
Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e));
|
Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e));
|
||||||
return;
|
return;
|
||||||
|
|
@ -207,17 +181,6 @@ final class DnsNameResolver extends NameResolver {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final Runnable resolutionRunnableOnExecutor = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
synchronized (DnsNameResolver.this) {
|
|
||||||
if (!shutdown) {
|
|
||||||
executor.execute(resolutionRunnable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private void resolve() {
|
private void resolve() {
|
||||||
if (resolving || shutdown) {
|
if (resolving || shutdown) {
|
||||||
|
|
@ -232,12 +195,6 @@ final class DnsNameResolver extends NameResolver {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
shutdown = true;
|
shutdown = true;
|
||||||
if (resolutionTask != null) {
|
|
||||||
resolutionTask.cancel(false);
|
|
||||||
}
|
|
||||||
if (timerService != null) {
|
|
||||||
timerService = SharedResourceHolder.release(timerServiceResource, timerService);
|
|
||||||
}
|
|
||||||
if (executor != null) {
|
if (executor != null) {
|
||||||
executor = SharedResourceHolder.release(executorResource, executor);
|
executor = SharedResourceHolder.release(executorResource, executor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,12 @@ public final class DnsNameResolverProvider extends NameResolverProvider {
|
||||||
Preconditions.checkArgument(targetPath.startsWith("/"),
|
Preconditions.checkArgument(targetPath.startsWith("/"),
|
||||||
"the path component (%s) of the target (%s) must start with '/'", targetPath, targetUri);
|
"the path component (%s) of the target (%s) must start with '/'", targetPath, targetUri);
|
||||||
String name = targetPath.substring(1);
|
String name = targetPath.substring(1);
|
||||||
return new DnsNameResolver(targetUri.getAuthority(), name, params, GrpcUtil.TIMER_SERVICE,
|
return new DnsNameResolver(
|
||||||
GrpcUtil.SHARED_CHANNEL_EXECUTOR, GrpcUtil.getProxyDetector());
|
targetUri.getAuthority(),
|
||||||
|
name,
|
||||||
|
params,
|
||||||
|
GrpcUtil.SHARED_CHANNEL_EXECUTOR,
|
||||||
|
GrpcUtil.getProxyDetector());
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,7 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
||||||
checkState(lbHelper != null, "lbHelper is null");
|
checkState(lbHelper != null, "lbHelper is null");
|
||||||
}
|
}
|
||||||
if (nameResolver != null) {
|
if (nameResolver != null) {
|
||||||
|
cancelNameResolverBackoff();
|
||||||
nameResolver.shutdown();
|
nameResolver.shutdown();
|
||||||
nameResolver = null;
|
nameResolver = null;
|
||||||
nameResolverStarted = false;
|
nameResolverStarted = false;
|
||||||
|
|
@ -429,6 +430,46 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
||||||
idleTimeoutMillis, TimeUnit.MILLISECONDS);
|
idleTimeoutMillis, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run from channelExecutor
|
||||||
|
@VisibleForTesting
|
||||||
|
class NameResolverRefresh implements Runnable {
|
||||||
|
// Only mutated from channelExecutor
|
||||||
|
boolean cancelled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (cancelled) {
|
||||||
|
// Race detected: this task was scheduled on channelExecutor before
|
||||||
|
// cancelNameResolverBackoff() could cancel the timer.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nameResolverRefreshFuture = null;
|
||||||
|
nameResolverRefresh = null;
|
||||||
|
if (nameResolver != null) {
|
||||||
|
nameResolver.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be used from channelExecutor
|
||||||
|
@Nullable private ScheduledFuture<?> nameResolverRefreshFuture;
|
||||||
|
// Must be used from channelExecutor
|
||||||
|
@Nullable private NameResolverRefresh nameResolverRefresh;
|
||||||
|
// The policy to control backoff between name resolution attempts. Non-null when an attempt is
|
||||||
|
// scheduled. Must be used from channelExecutor
|
||||||
|
@Nullable private BackoffPolicy nameResolverBackoffPolicy;
|
||||||
|
|
||||||
|
// Must be run from channelExecutor
|
||||||
|
private void cancelNameResolverBackoff() {
|
||||||
|
if (nameResolverRefreshFuture != null) {
|
||||||
|
nameResolverRefreshFuture.cancel(false);
|
||||||
|
nameResolverRefresh.cancelled = true;
|
||||||
|
nameResolverRefreshFuture = null;
|
||||||
|
nameResolverRefresh = null;
|
||||||
|
nameResolverBackoffPolicy = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final ClientTransportProvider transportProvider = new ClientTransportProvider() {
|
private final ClientTransportProvider transportProvider = new ClientTransportProvider() {
|
||||||
@Override
|
@Override
|
||||||
public ClientTransport get(PickSubchannelArgs args) {
|
public ClientTransport get(PickSubchannelArgs args) {
|
||||||
|
|
@ -799,24 +840,28 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetConnectBackoff() {
|
public void resetConnectBackoff() {
|
||||||
channelExecutor.executeLater(
|
channelExecutor
|
||||||
new Runnable() {
|
.executeLater(
|
||||||
@Override
|
new Runnable() {
|
||||||
public void run() {
|
@Override
|
||||||
if (shutdown.get()) {
|
public void run() {
|
||||||
return;
|
if (shutdown.get()) {
|
||||||
}
|
return;
|
||||||
if (nameResolverStarted) {
|
}
|
||||||
nameResolver.refresh();
|
if (nameResolverRefreshFuture != null) {
|
||||||
}
|
checkState(nameResolverStarted, "name resolver must be started");
|
||||||
for (InternalSubchannel subchannel : subchannels) {
|
cancelNameResolverBackoff();
|
||||||
subchannel.resetConnectBackoff();
|
nameResolver.refresh();
|
||||||
}
|
}
|
||||||
for (OobChannel oobChannel : oobChannels) {
|
for (InternalSubchannel subchannel : subchannels) {
|
||||||
oobChannel.resetConnectBackoff();
|
subchannel.resetConnectBackoff();
|
||||||
}
|
}
|
||||||
}
|
for (OobChannel oobChannel : oobChannels) {
|
||||||
}).drain();
|
oobChannel.resetConnectBackoff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.drain();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1132,6 +1177,8 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nameResolverBackoffPolicy = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (retryEnabled) {
|
if (retryEnabled) {
|
||||||
retryPolicies = getRetryPolicies(config);
|
retryPolicies = getRetryPolicies(config);
|
||||||
|
|
@ -1156,16 +1203,41 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
||||||
checkArgument(!error.isOk(), "the error status must not be OK");
|
checkArgument(!error.isOk(), "the error status must not be OK");
|
||||||
logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}",
|
logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}",
|
||||||
new Object[] {getLogId(), error});
|
new Object[] {getLogId(), error});
|
||||||
channelExecutor.executeLater(new Runnable() {
|
channelExecutor
|
||||||
@Override
|
.executeLater(
|
||||||
public void run() {
|
new Runnable() {
|
||||||
// Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
|
@Override
|
||||||
if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
|
public void run() {
|
||||||
return;
|
// Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
|
||||||
}
|
if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
|
||||||
balancer.handleNameResolutionError(error);
|
return;
|
||||||
}
|
}
|
||||||
}).drain();
|
balancer.handleNameResolutionError(error);
|
||||||
|
if (nameResolverRefreshFuture != null) {
|
||||||
|
// The name resolver may invoke onError multiple times, but we only want to
|
||||||
|
// schedule one backoff attempt
|
||||||
|
// TODO(ericgribkoff) Update contract of NameResolver.Listener or decide if we
|
||||||
|
// want to reset the backoff interval upon repeated onError() calls
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nameResolverBackoffPolicy == null) {
|
||||||
|
nameResolverBackoffPolicy = backoffPolicyProvider.get();
|
||||||
|
}
|
||||||
|
long delayNanos = nameResolverBackoffPolicy.nextBackoffNanos();
|
||||||
|
if (logger.isLoggable(Level.FINE)) {
|
||||||
|
logger.log(
|
||||||
|
Level.FINE,
|
||||||
|
"[{0}] Scheduling DNS resolution backoff for {1} ns",
|
||||||
|
new Object[] {logId, delayNanos});
|
||||||
|
}
|
||||||
|
nameResolverRefresh = new NameResolverRefresh();
|
||||||
|
nameResolverRefreshFuture =
|
||||||
|
transportFactory
|
||||||
|
.getScheduledExecutorService()
|
||||||
|
.schedule(nameResolverRefresh, delayNanos, TimeUnit.NANOSECONDS);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.drain();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package io.grpc.internal;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertSame;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
|
|
@ -27,7 +26,6 @@ import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
|
|
@ -35,7 +33,6 @@ import com.google.common.collect.Iterables;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.EquivalentAddressGroup;
|
import io.grpc.EquivalentAddressGroup;
|
||||||
import io.grpc.NameResolver;
|
import io.grpc.NameResolver;
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.internal.DnsNameResolver.DelegateResolver;
|
import io.grpc.internal.DnsNameResolver.DelegateResolver;
|
||||||
import io.grpc.internal.DnsNameResolver.ResolutionResults;
|
import io.grpc.internal.DnsNameResolver.ResolutionResults;
|
||||||
import io.grpc.internal.SharedResourceHolder.Resource;
|
import io.grpc.internal.SharedResourceHolder.Resource;
|
||||||
|
|
@ -51,8 +48,6 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
@ -80,17 +75,6 @@ public class DnsNameResolverTest {
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
private final FakeClock fakeExecutor = new FakeClock();
|
private final FakeClock fakeExecutor = new FakeClock();
|
||||||
private MockResolver mockResolver = new MockResolver();
|
private MockResolver mockResolver = new MockResolver();
|
||||||
private final Resource<ScheduledExecutorService> fakeTimerServiceResource =
|
|
||||||
new Resource<ScheduledExecutorService>() {
|
|
||||||
@Override
|
|
||||||
public ScheduledExecutorService create() {
|
|
||||||
return fakeClock.getScheduledExecutorService();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close(ScheduledExecutorService instance) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Resource<ExecutorService> fakeExecutorResource =
|
private final Resource<ExecutorService> fakeExecutorResource =
|
||||||
new Resource<ExecutorService>() {
|
new Resource<ExecutorService>() {
|
||||||
|
|
@ -108,8 +92,6 @@ public class DnsNameResolverTest {
|
||||||
private NameResolver.Listener mockListener;
|
private NameResolver.Listener mockListener;
|
||||||
@Captor
|
@Captor
|
||||||
private ArgumentCaptor<List<EquivalentAddressGroup>> resultCaptor;
|
private ArgumentCaptor<List<EquivalentAddressGroup>> resultCaptor;
|
||||||
@Captor
|
|
||||||
private ArgumentCaptor<Status> statusCaptor;
|
|
||||||
|
|
||||||
private DnsNameResolver newResolver(String name, int port) {
|
private DnsNameResolver newResolver(String name, int port) {
|
||||||
return newResolver(name, port, mockResolver, GrpcUtil.NOOP_PROXY_DETECTOR);
|
return newResolver(name, port, mockResolver, GrpcUtil.NOOP_PROXY_DETECTOR);
|
||||||
|
|
@ -124,7 +106,6 @@ public class DnsNameResolverTest {
|
||||||
null,
|
null,
|
||||||
name,
|
name,
|
||||||
Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, port).build(),
|
Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, port).build(),
|
||||||
fakeTimerServiceResource,
|
|
||||||
fakeExecutorResource,
|
fakeExecutorResource,
|
||||||
proxyDetector);
|
proxyDetector);
|
||||||
dnsResolver.setDelegateResolver(delegateResolver);
|
dnsResolver.setDelegateResolver(delegateResolver);
|
||||||
|
|
@ -190,105 +171,6 @@ public class DnsNameResolverTest {
|
||||||
resolver.shutdown();
|
resolver.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void retry() throws Exception {
|
|
||||||
String name = "foo.googleapis.com";
|
|
||||||
UnknownHostException error = new UnknownHostException(name);
|
|
||||||
List<InetAddress> answer = createAddressList(2);
|
|
||||||
DnsNameResolver resolver = newResolver(name, 81);
|
|
||||||
mockResolver.addAnswer(error).addAnswer(error).addAnswer(answer);
|
|
||||||
resolver.start(mockListener);
|
|
||||||
assertEquals(1, fakeExecutor.runDueTasks());
|
|
||||||
verify(mockListener).onError(statusCaptor.capture());
|
|
||||||
assertEquals(name, mockResolver.invocations.poll());
|
|
||||||
Status status = statusCaptor.getValue();
|
|
||||||
assertEquals(Status.Code.UNAVAILABLE, status.getCode());
|
|
||||||
assertSame(error, status.getCause());
|
|
||||||
|
|
||||||
// First retry scheduled
|
|
||||||
assertEquals(1, fakeClock.numPendingTasks());
|
|
||||||
fakeClock.forwardNanos(TimeUnit.MINUTES.toNanos(1) - 1);
|
|
||||||
assertEquals(1, fakeClock.numPendingTasks());
|
|
||||||
|
|
||||||
// First retry
|
|
||||||
fakeClock.forwardNanos(1);
|
|
||||||
assertEquals(1, fakeExecutor.runDueTasks());
|
|
||||||
verify(mockListener, times(2)).onError(statusCaptor.capture());
|
|
||||||
assertEquals(name, mockResolver.invocations.poll());
|
|
||||||
status = statusCaptor.getValue();
|
|
||||||
assertEquals(Status.Code.UNAVAILABLE, status.getCode());
|
|
||||||
assertSame(error, status.getCause());
|
|
||||||
|
|
||||||
// Second retry scheduled
|
|
||||||
assertEquals(1, fakeClock.numPendingTasks());
|
|
||||||
fakeClock.forwardNanos(TimeUnit.MINUTES.toNanos(1) - 1);
|
|
||||||
assertEquals(1, fakeClock.numPendingTasks());
|
|
||||||
|
|
||||||
// Second retry
|
|
||||||
fakeClock.forwardNanos(1);
|
|
||||||
assertEquals(0, fakeClock.numPendingTasks());
|
|
||||||
assertEquals(1, fakeExecutor.runDueTasks());
|
|
||||||
verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
|
|
||||||
assertEquals(name, mockResolver.invocations.poll());
|
|
||||||
assertAnswerMatches(answer, 81, resultCaptor.getValue());
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(mockListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void refreshCancelsScheduledRetry() throws Exception {
|
|
||||||
String name = "foo.googleapis.com";
|
|
||||||
UnknownHostException error = new UnknownHostException(name);
|
|
||||||
List<InetAddress> answer = createAddressList(2);
|
|
||||||
DnsNameResolver resolver = newResolver(name, 81);
|
|
||||||
mockResolver.addAnswer(error).addAnswer(answer);
|
|
||||||
resolver.start(mockListener);
|
|
||||||
assertEquals(1, fakeExecutor.runDueTasks());
|
|
||||||
verify(mockListener).onError(statusCaptor.capture());
|
|
||||||
assertEquals(name, mockResolver.invocations.poll());
|
|
||||||
Status status = statusCaptor.getValue();
|
|
||||||
assertEquals(Status.Code.UNAVAILABLE, status.getCode());
|
|
||||||
assertSame(error, status.getCause());
|
|
||||||
|
|
||||||
// First retry scheduled
|
|
||||||
assertEquals(1, fakeClock.numPendingTasks());
|
|
||||||
|
|
||||||
resolver.refresh();
|
|
||||||
assertEquals(1, fakeExecutor.runDueTasks());
|
|
||||||
// Refresh cancelled the retry
|
|
||||||
assertEquals(0, fakeClock.numPendingTasks());
|
|
||||||
verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
|
|
||||||
assertEquals(name, mockResolver.invocations.poll());
|
|
||||||
assertAnswerMatches(answer, 81, resultCaptor.getValue());
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(mockListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shutdownCancelsScheduledRetry() throws Exception {
|
|
||||||
String name = "foo.googleapis.com";
|
|
||||||
UnknownHostException error = new UnknownHostException(name);
|
|
||||||
DnsNameResolver resolver = newResolver(name, 81);
|
|
||||||
mockResolver.addAnswer(error);
|
|
||||||
resolver.start(mockListener);
|
|
||||||
assertEquals(1, fakeExecutor.runDueTasks());
|
|
||||||
|
|
||||||
verify(mockListener).onError(statusCaptor.capture());
|
|
||||||
assertEquals(name, mockResolver.invocations.poll());
|
|
||||||
Status status = statusCaptor.getValue();
|
|
||||||
assertEquals(Status.Code.UNAVAILABLE, status.getCode());
|
|
||||||
assertSame(error, status.getCause());
|
|
||||||
|
|
||||||
// Retry scheduled
|
|
||||||
assertEquals(1, fakeClock.numPendingTasks());
|
|
||||||
|
|
||||||
// Shutdown cancelled the retry
|
|
||||||
resolver.shutdown();
|
|
||||||
assertEquals(0, fakeClock.numPendingTasks());
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(mockListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void jdkResolverWorks() throws Exception {
|
public void jdkResolverWorks() throws Exception {
|
||||||
DnsNameResolver.DelegateResolver resolver = new DnsNameResolver.JdkResolver();
|
DnsNameResolver.DelegateResolver resolver = new DnsNameResolver.JdkResolver();
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.CallCredentials;
|
import io.grpc.CallCredentials;
|
||||||
import io.grpc.CallCredentials.MetadataApplier;
|
import io.grpc.CallCredentials.MetadataApplier;
|
||||||
|
|
@ -129,7 +130,7 @@ public class ManagedChannelImplTest {
|
||||||
.build();
|
.build();
|
||||||
private static final Attributes.Key<String> SUBCHANNEL_ATTR_KEY =
|
private static final Attributes.Key<String> SUBCHANNEL_ATTR_KEY =
|
||||||
Attributes.Key.of("subchannel-attr-key");
|
Attributes.Key.of("subchannel-attr-key");
|
||||||
private static final long RECONNECT_BACKOFF_INTERVAL_NANOS = 1;
|
private static final long RECONNECT_BACKOFF_INTERVAL_NANOS = 10;
|
||||||
private final String serviceName = "fake.example.com";
|
private final String serviceName = "fake.example.com";
|
||||||
private final String authority = serviceName;
|
private final String authority = serviceName;
|
||||||
private final String userAgent = "userAgent";
|
private final String userAgent = "userAgent";
|
||||||
|
|
@ -141,6 +142,13 @@ public class ManagedChannelImplTest {
|
||||||
private final FakeClock timer = new FakeClock();
|
private final FakeClock timer = new FakeClock();
|
||||||
private final FakeClock executor = new FakeClock();
|
private final FakeClock executor = new FakeClock();
|
||||||
private final FakeClock oobExecutor = new FakeClock();
|
private final FakeClock oobExecutor = new FakeClock();
|
||||||
|
private static final FakeClock.TaskFilter NAME_RESOLVER_REFRESH_TASK_FILTER =
|
||||||
|
new FakeClock.TaskFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldAccept(Runnable command) {
|
||||||
|
return command instanceof ManagedChannelImpl.NameResolverRefresh;
|
||||||
|
}
|
||||||
|
};
|
||||||
private final CallTracer.Factory channelStatsFactory = new CallTracer.Factory() {
|
private final CallTracer.Factory channelStatsFactory = new CallTracer.Factory() {
|
||||||
@Override
|
@Override
|
||||||
public CallTracer create() {
|
public CallTracer create() {
|
||||||
|
|
@ -238,11 +246,19 @@ public class ManagedChannelImplTest {
|
||||||
channelStatsFactory);
|
channelStatsFactory);
|
||||||
|
|
||||||
if (requestConnection) {
|
if (requestConnection) {
|
||||||
|
int numExpectedTasks = 0;
|
||||||
|
|
||||||
// Force-exit the initial idle-mode
|
// Force-exit the initial idle-mode
|
||||||
channel.exitIdleMode();
|
channel.exitIdleMode();
|
||||||
assertEquals(
|
if (idleTimeoutMillis != ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE) {
|
||||||
idleTimeoutMillis == ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE ? 0 : 1,
|
numExpectedTasks += 1;
|
||||||
timer.numPendingTasks());
|
}
|
||||||
|
|
||||||
|
if (getNameResolverRefresh() != null) {
|
||||||
|
numExpectedTasks += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(numExpectedTasks, timer.numPendingTasks());
|
||||||
|
|
||||||
ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
|
ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
|
||||||
verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
|
verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
|
||||||
|
|
@ -279,7 +295,11 @@ public class ManagedChannelImplTest {
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void idleModeDisabled() {
|
public void idleModeDisabled() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
|
||||||
|
.build(),
|
||||||
|
NO_INTERCEPTOR);
|
||||||
|
|
||||||
// In this test suite, the channel is always created with idle mode disabled.
|
// In this test suite, the channel is always created with idle mode disabled.
|
||||||
// No task is scheduled to enter idle mode
|
// No task is scheduled to enter idle mode
|
||||||
|
|
@ -289,7 +309,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void immediateDeadlineExceeded() {
|
public void immediateDeadlineExceeded() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
ClientCall<String, Integer> call =
|
ClientCall<String, Integer> call =
|
||||||
channel.newCall(method, CallOptions.DEFAULT.withDeadlineAfter(0, TimeUnit.NANOSECONDS));
|
channel.newCall(method, CallOptions.DEFAULT.withDeadlineAfter(0, TimeUnit.NANOSECONDS));
|
||||||
call.start(mockCallListener, new Metadata());
|
call.start(mockCallListener, new Metadata());
|
||||||
|
|
@ -302,7 +322,11 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shutdownWithNoTransportsEverCreated() {
|
public void shutdownWithNoTransportsEverCreated() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
|
||||||
|
.build(),
|
||||||
|
NO_INTERCEPTOR);
|
||||||
verify(executorPool).getObject();
|
verify(executorPool).getObject();
|
||||||
verify(executorPool, never()).returnObject(anyObject());
|
verify(executorPool, never()).returnObject(anyObject());
|
||||||
verifyNoMoreInteractions(mockTransportFactory);
|
verifyNoMoreInteractions(mockTransportFactory);
|
||||||
|
|
@ -314,7 +338,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelzMembership() throws Exception {
|
public void channelzMembership() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
assertTrue(channelz.containsRootChannel(channel.getLogId()));
|
assertTrue(channelz.containsRootChannel(channel.getLogId()));
|
||||||
assertTrue(channelz.containsChannel(channel.getLogId()));
|
assertTrue(channelz.containsChannel(channel.getLogId()));
|
||||||
channel.shutdownNow();
|
channel.shutdownNow();
|
||||||
|
|
@ -325,7 +349,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelzMembership_subchannel() throws Exception {
|
public void channelzMembership_subchannel() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
assertTrue(channelz.containsRootChannel(channel.getLogId()));
|
assertTrue(channelz.containsRootChannel(channel.getLogId()));
|
||||||
assertTrue(channelz.containsChannel(channel.getLogId()));
|
assertTrue(channelz.containsChannel(channel.getLogId()));
|
||||||
|
|
||||||
|
|
@ -361,7 +385,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelzMembership_oob() throws Exception {
|
public void channelzMembership_oob() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
OobChannel oob = (OobChannel) helper.createOobChannel(addressGroup, authority);
|
OobChannel oob = (OobChannel) helper.createOobChannel(addressGroup, authority);
|
||||||
// oob channels are not root channels
|
// oob channels are not root channels
|
||||||
assertFalse(channelz.containsRootChannel(oob.getLogId()));
|
assertFalse(channelz.containsRootChannel(oob.getLogId()));
|
||||||
|
|
@ -412,7 +436,8 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subtestCallsAndShutdown(boolean shutdownNow, boolean shutdownNowAfterShutdown) {
|
private void subtestCallsAndShutdown(boolean shutdownNow, boolean shutdownNowAfterShutdown) {
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
verify(executorPool).getObject();
|
verify(executorPool).getObject();
|
||||||
ClientStream mockStream = mock(ClientStream.class);
|
ClientStream mockStream = mock(ClientStream.class);
|
||||||
|
|
@ -542,7 +567,10 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noMoreCallbackAfterLoadBalancerShutdown() {
|
public void noMoreCallbackAfterLoadBalancerShutdown() {
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
|
||||||
|
.build();
|
||||||
Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed");
|
Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed");
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
|
||||||
|
|
@ -598,7 +626,8 @@ public class ManagedChannelImplTest {
|
||||||
return next.newCall(method, callOptions);
|
return next.newCall(method, callOptions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
createChannel(new FakeNameResolverFactory(true), Arrays.asList(interceptor));
|
createChannel(
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).build(), Arrays.asList(interceptor));
|
||||||
assertNotNull(channel.newCall(method, CallOptions.DEFAULT));
|
assertNotNull(channel.newCall(method, CallOptions.DEFAULT));
|
||||||
assertEquals(1, atomic.get());
|
assertEquals(1, atomic.get());
|
||||||
}
|
}
|
||||||
|
|
@ -608,7 +637,7 @@ public class ManagedChannelImplTest {
|
||||||
Metadata headers = new Metadata();
|
Metadata headers = new Metadata();
|
||||||
ClientStream mockStream = mock(ClientStream.class);
|
ClientStream mockStream = mock(ClientStream.class);
|
||||||
FakeClock callExecutor = new FakeClock();
|
FakeClock callExecutor = new FakeClock();
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
// Start a call with a call executor
|
// Start a call with a call executor
|
||||||
CallOptions options =
|
CallOptions options =
|
||||||
|
|
@ -661,10 +690,81 @@ public class ManagedChannelImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void nameResolutionFailed() {
|
public void nameResolutionFailed() {
|
||||||
Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error"));
|
Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error"));
|
||||||
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
|
||||||
|
.setError(error)
|
||||||
|
.build();
|
||||||
// Name resolution is started as soon as channel is created.
|
// Name resolution is started as soon as channel is created.
|
||||||
createChannel(new FailingNameResolverFactory(error), NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
|
||||||
verify(mockLoadBalancer).handleNameResolutionError(same(error));
|
verify(mockLoadBalancer).handleNameResolutionError(same(error));
|
||||||
|
assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER));
|
||||||
|
|
||||||
|
timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS - 1);
|
||||||
|
assertEquals(0, resolver.refreshCalled);
|
||||||
|
|
||||||
|
timer.forwardNanos(1);
|
||||||
|
assertEquals(1, resolver.refreshCalled);
|
||||||
|
verify(mockLoadBalancer, times(2)).handleNameResolutionError(same(error));
|
||||||
|
|
||||||
|
// Verify an additional name resolution failure does not schedule another timer
|
||||||
|
resolver.refresh();
|
||||||
|
verify(mockLoadBalancer, times(3)).handleNameResolutionError(same(error));
|
||||||
|
assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER));
|
||||||
|
|
||||||
|
// Allow the next refresh attempt to succeed
|
||||||
|
resolver.error = null;
|
||||||
|
|
||||||
|
// For the second attempt, the backoff should occur at RECONNECT_BACKOFF_INTERVAL_NANOS * 2
|
||||||
|
timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS * 2 - 1);
|
||||||
|
assertEquals(2, resolver.refreshCalled);
|
||||||
|
timer.forwardNanos(1);
|
||||||
|
assertEquals(3, resolver.refreshCalled);
|
||||||
|
assertEquals(0, timer.numPendingTasks());
|
||||||
|
|
||||||
|
// Verify that the successful resolution reset the backoff policy
|
||||||
|
resolver.listener.onError(error);
|
||||||
|
timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS - 1);
|
||||||
|
assertEquals(3, resolver.refreshCalled);
|
||||||
|
timer.forwardNanos(1);
|
||||||
|
assertEquals(4, resolver.refreshCalled);
|
||||||
|
assertEquals(0, timer.numPendingTasks());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nameResolutionFailed_delayedTransportShutdownCancelsBackoff() {
|
||||||
|
Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error"));
|
||||||
|
|
||||||
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).setError(error).build();
|
||||||
|
// Name resolution is started as soon as channel is created.
|
||||||
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
verify(mockLoadBalancer).handleNameResolutionError(same(error));
|
||||||
|
|
||||||
|
FakeClock.ScheduledTask nameResolverBackoff = getNameResolverRefresh();
|
||||||
|
assertNotNull(nameResolverBackoff);
|
||||||
|
assertFalse(nameResolverBackoff.isCancelled());
|
||||||
|
|
||||||
|
// Add a pending call to the delayed transport
|
||||||
|
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
|
||||||
|
Metadata headers = new Metadata();
|
||||||
|
call.start(mockCallListener, headers);
|
||||||
|
|
||||||
|
// The pending call on the delayed transport stops the name resolver backoff from cancelling
|
||||||
|
channel.shutdown();
|
||||||
|
assertFalse(nameResolverBackoff.isCancelled());
|
||||||
|
|
||||||
|
// Notify that a subchannel is ready, which drains the delayed transport
|
||||||
|
SubchannelPicker picker = mock(SubchannelPicker.class);
|
||||||
|
Status status = Status.UNAVAILABLE.withDescription("for test");
|
||||||
|
when(picker.pickSubchannel(any(PickSubchannelArgs.class)))
|
||||||
|
.thenReturn(PickResult.withDrop(status));
|
||||||
|
helper.updateBalancingState(READY, picker);
|
||||||
|
executor.runDueTasks();
|
||||||
|
verify(mockCallListener).onClose(same(status), any(Metadata.class));
|
||||||
|
|
||||||
|
assertTrue(nameResolverBackoff.isCancelled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -672,7 +772,7 @@ public class ManagedChannelImplTest {
|
||||||
String errorDescription = "NameResolver returned an empty list";
|
String errorDescription = "NameResolver returned an empty list";
|
||||||
|
|
||||||
// Pass a FakeNameResolverFactory with an empty list
|
// Pass a FakeNameResolverFactory with an empty list
|
||||||
createChannel(new FakeNameResolverFactory(), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
// LoadBalancer received the error
|
// LoadBalancer received the error
|
||||||
verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
|
verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
|
||||||
|
|
@ -686,7 +786,11 @@ public class ManagedChannelImplTest {
|
||||||
public void loadBalancerThrowsInHandleResolvedAddresses() {
|
public void loadBalancerThrowsInHandleResolvedAddresses() {
|
||||||
RuntimeException ex = new RuntimeException("simulated");
|
RuntimeException ex = new RuntimeException("simulated");
|
||||||
// Delay the success of name resolution until allResolved() is called
|
// Delay the success of name resolution until allResolved() is called
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(false);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setResolvedAtStart(false)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
|
||||||
|
.build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
|
||||||
verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
|
verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
|
||||||
|
|
@ -703,7 +807,8 @@ public class ManagedChannelImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void nameResolvedAfterChannelShutdown() {
|
public void nameResolvedAfterChannelShutdown() {
|
||||||
// Delay the success of name resolution until allResolved() is called.
|
// Delay the success of name resolution until allResolved() is called.
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(false);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
|
||||||
channel.shutdown();
|
channel.shutdown();
|
||||||
|
|
@ -736,7 +841,10 @@ public class ManagedChannelImplTest {
|
||||||
InOrder inOrder = inOrder(mockLoadBalancer);
|
InOrder inOrder = inOrder(mockLoadBalancer);
|
||||||
|
|
||||||
List<SocketAddress> resolvedAddrs = Arrays.asList(badAddress, goodAddress);
|
List<SocketAddress> resolvedAddrs = Arrays.asList(badAddress, goodAddress);
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(resolvedAddrs);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(resolvedAddrs)))
|
||||||
|
.build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
|
||||||
// Start the call
|
// Start the call
|
||||||
|
|
@ -817,7 +925,7 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subtestFailRpcFromBalancer(boolean waitForReady, boolean drop, boolean shouldFail) {
|
private void subtestFailRpcFromBalancer(boolean waitForReady, boolean drop, boolean shouldFail) {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
// This call will be buffered by the channel, thus involve delayed transport
|
// This call will be buffered by the channel, thus involve delayed transport
|
||||||
CallOptions callOptions = CallOptions.DEFAULT;
|
CallOptions callOptions = CallOptions.DEFAULT;
|
||||||
|
|
@ -875,7 +983,10 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
List<SocketAddress> resolvedAddrs = Arrays.asList(addr1, addr2);
|
List<SocketAddress> resolvedAddrs = Arrays.asList(addr1, addr2);
|
||||||
|
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(resolvedAddrs);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(resolvedAddrs)))
|
||||||
|
.build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
|
||||||
// Start a wait-for-ready call
|
// Start a wait-for-ready call
|
||||||
|
|
@ -947,7 +1058,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void subchannels() {
|
public void subchannels() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
// createSubchannel() always return a new Subchannel
|
// createSubchannel() always return a new Subchannel
|
||||||
Attributes attrs1 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr1").build();
|
Attributes attrs1 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr1").build();
|
||||||
|
|
@ -1007,7 +1118,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void subchannelsWhenChannelShutdownNow() {
|
public void subchannelsWhenChannelShutdownNow() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
Subchannel sub1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
Subchannel sub1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
Subchannel sub2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
Subchannel sub2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
sub1.requestConnection();
|
sub1.requestConnection();
|
||||||
|
|
@ -1035,7 +1146,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void subchannelsNoConnectionShutdown() {
|
public void subchannelsNoConnectionShutdown() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
Subchannel sub1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
Subchannel sub1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
Subchannel sub2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
Subchannel sub2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
|
|
||||||
|
|
@ -1051,7 +1162,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void subchannelsNoConnectionShutdownNow() {
|
public void subchannelsNoConnectionShutdownNow() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
channel.shutdownNow();
|
channel.shutdownNow();
|
||||||
|
|
@ -1066,7 +1177,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void oobchannels() {
|
public void oobchannels() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1authority");
|
ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1authority");
|
||||||
ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2authority");
|
ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2authority");
|
||||||
|
|
@ -1164,7 +1275,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void oobChannelsWhenChannelShutdownNow() {
|
public void oobChannelsWhenChannelShutdownNow() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority");
|
ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority");
|
||||||
ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority");
|
ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority");
|
||||||
|
|
||||||
|
|
@ -1193,7 +1304,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void oobChannelsNoConnectionShutdown() {
|
public void oobChannelsNoConnectionShutdown() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority");
|
ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority");
|
||||||
ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority");
|
ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority");
|
||||||
channel.shutdown();
|
channel.shutdown();
|
||||||
|
|
@ -1211,7 +1322,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void oobChannelsNoConnectionShutdownNow() {
|
public void oobChannelsNoConnectionShutdownNow() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
helper.createOobChannel(addressGroup, "oob1Authority");
|
helper.createOobChannel(addressGroup, "oob1Authority");
|
||||||
helper.createOobChannel(addressGroup, "oob2Authority");
|
helper.createOobChannel(addressGroup, "oob2Authority");
|
||||||
channel.shutdownNow();
|
channel.shutdownNow();
|
||||||
|
|
@ -1235,7 +1346,10 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subtestRefreshNameResolutionWhenConnectionFailed(boolean isOobChannel) {
|
private void subtestRefreshNameResolutionWhenConnectionFailed(boolean isOobChannel) {
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
|
||||||
|
.build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
|
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
|
||||||
|
|
||||||
|
|
@ -1283,7 +1397,7 @@ public class ManagedChannelImplTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void informationPropagatedToNewStreamAndCallCredentials() {
|
public void informationPropagatedToNewStreamAndCallCredentials() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
CallOptions callOptions = CallOptions.DEFAULT.withCallCredentials(creds);
|
CallOptions callOptions = CallOptions.DEFAULT.withCallCredentials(creds);
|
||||||
final Context.Key<String> testKey = Context.key("testing");
|
final Context.Key<String> testKey = Context.key("testing");
|
||||||
Context ctx = Context.current().withValue(testKey, "testValue");
|
Context ctx = Context.current().withValue(testKey, "testValue");
|
||||||
|
|
@ -1385,7 +1499,7 @@ public class ManagedChannelImplTest {
|
||||||
ClientStream mockStream = mock(ClientStream.class);
|
ClientStream mockStream = mock(ClientStream.class);
|
||||||
ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
|
ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
|
||||||
ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
|
ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
subchannel.requestConnection();
|
subchannel.requestConnection();
|
||||||
MockClientTransportInfo transportInfo = transports.poll();
|
MockClientTransportInfo transportInfo = transports.poll();
|
||||||
|
|
@ -1418,7 +1532,7 @@ public class ManagedChannelImplTest {
|
||||||
ClientStream mockStream = mock(ClientStream.class);
|
ClientStream mockStream = mock(ClientStream.class);
|
||||||
ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
|
ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
|
||||||
ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
|
ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
CallOptions callOptions = CallOptions.DEFAULT.withStreamTracerFactory(factory1);
|
CallOptions callOptions = CallOptions.DEFAULT.withStreamTracerFactory(factory1);
|
||||||
ClientCall<String, Integer> call = channel.newCall(method, callOptions);
|
ClientCall<String, Integer> call = channel.newCall(method, callOptions);
|
||||||
|
|
@ -1450,7 +1564,9 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getState_loadBalancerSupportsChannelState() {
|
public void getState_loadBalancerSupportsChannelState() {
|
||||||
createChannel(new FakeNameResolverFactory(false), NO_INTERCEPTOR);
|
createChannel(
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build(),
|
||||||
|
NO_INTERCEPTOR);
|
||||||
assertEquals(IDLE, channel.getState(false));
|
assertEquals(IDLE, channel.getState(false));
|
||||||
|
|
||||||
helper.updateBalancingState(TRANSIENT_FAILURE, mockPicker);
|
helper.updateBalancingState(TRANSIENT_FAILURE, mockPicker);
|
||||||
|
|
@ -1460,7 +1576,9 @@ public class ManagedChannelImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void getState_withRequestConnect() {
|
public void getState_withRequestConnect() {
|
||||||
createChannel(
|
createChannel(
|
||||||
new FakeNameResolverFactory(false), NO_INTERCEPTOR, false /* requestConnection */,
|
new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build(),
|
||||||
|
NO_INTERCEPTOR,
|
||||||
|
false /* requestConnection */,
|
||||||
ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE);
|
ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE);
|
||||||
|
|
||||||
assertEquals(IDLE, channel.getState(false));
|
assertEquals(IDLE, channel.getState(false));
|
||||||
|
|
@ -1488,7 +1606,9 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
createChannel(new FakeNameResolverFactory(false), NO_INTERCEPTOR);
|
createChannel(
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build(),
|
||||||
|
NO_INTERCEPTOR);
|
||||||
assertEquals(IDLE, channel.getState(false));
|
assertEquals(IDLE, channel.getState(false));
|
||||||
|
|
||||||
channel.notifyWhenStateChanged(IDLE, onStateChanged);
|
channel.notifyWhenStateChanged(IDLE, onStateChanged);
|
||||||
|
|
@ -1519,7 +1639,9 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
createChannel(new FakeNameResolverFactory(false), NO_INTERCEPTOR);
|
createChannel(
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build(),
|
||||||
|
NO_INTERCEPTOR);
|
||||||
assertEquals(IDLE, channel.getState(false));
|
assertEquals(IDLE, channel.getState(false));
|
||||||
channel.notifyWhenStateChanged(IDLE, onStateChanged);
|
channel.notifyWhenStateChanged(IDLE, onStateChanged);
|
||||||
executor.runDueTasks();
|
executor.runDueTasks();
|
||||||
|
|
@ -1543,7 +1665,9 @@ public class ManagedChannelImplTest {
|
||||||
public void stateIsIdleOnIdleTimeout() {
|
public void stateIsIdleOnIdleTimeout() {
|
||||||
long idleTimeoutMillis = 2000L;
|
long idleTimeoutMillis = 2000L;
|
||||||
createChannel(
|
createChannel(
|
||||||
new FakeNameResolverFactory(true), NO_INTERCEPTOR, true /* request connection*/,
|
new FakeNameResolverFactory.Builder(expectedUri).build(),
|
||||||
|
NO_INTERCEPTOR,
|
||||||
|
true /* request connection*/,
|
||||||
idleTimeoutMillis);
|
idleTimeoutMillis);
|
||||||
assertEquals(IDLE, channel.getState(false));
|
assertEquals(IDLE, channel.getState(false));
|
||||||
|
|
||||||
|
|
@ -1577,7 +1701,8 @@ public class ManagedChannelImplTest {
|
||||||
private void subtestPanic(ConnectivityState initialState) {
|
private void subtestPanic(ConnectivityState initialState) {
|
||||||
assertNotEquals("We don't test panic mode if it's already SHUTDOWN", SHUTDOWN, initialState);
|
assertNotEquals("We don't test panic mode if it's already SHUTDOWN", SHUTDOWN, initialState);
|
||||||
long idleTimeoutMillis = 2000L;
|
long idleTimeoutMillis = 2000L;
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR, true, idleTimeoutMillis);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR, true, idleTimeoutMillis);
|
||||||
|
|
||||||
verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
|
verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
|
||||||
|
|
@ -1644,7 +1769,8 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void panic_bufferedCallsWillFail() {
|
public void panic_bufferedCallsWillFail() {
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
|
||||||
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
|
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
|
||||||
|
|
@ -1705,7 +1831,9 @@ public class ManagedChannelImplTest {
|
||||||
public void idleTimeoutAndReconnect() {
|
public void idleTimeoutAndReconnect() {
|
||||||
long idleTimeoutMillis = 2000L;
|
long idleTimeoutMillis = 2000L;
|
||||||
createChannel(
|
createChannel(
|
||||||
new FakeNameResolverFactory(true), NO_INTERCEPTOR, true /* request connection*/,
|
new FakeNameResolverFactory.Builder(expectedUri).build(),
|
||||||
|
NO_INTERCEPTOR,
|
||||||
|
true /* request connection*/,
|
||||||
idleTimeoutMillis);
|
idleTimeoutMillis);
|
||||||
|
|
||||||
timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
|
timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
|
||||||
|
|
@ -1727,7 +1855,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void prepareToLoseNetworkEntersIdle() {
|
public void prepareToLoseNetworkEntersIdle() {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
helper.updateBalancingState(READY, mockPicker);
|
helper.updateBalancingState(READY, mockPicker);
|
||||||
assertEquals(READY, channel.getState(false));
|
assertEquals(READY, channel.getState(false));
|
||||||
|
|
||||||
|
|
@ -1740,7 +1868,9 @@ public class ManagedChannelImplTest {
|
||||||
public void prepareToLoseNetworkAfterIdleTimerIsNoOp() {
|
public void prepareToLoseNetworkAfterIdleTimerIsNoOp() {
|
||||||
long idleTimeoutMillis = 2000L;
|
long idleTimeoutMillis = 2000L;
|
||||||
createChannel(
|
createChannel(
|
||||||
new FakeNameResolverFactory(true), NO_INTERCEPTOR, true /* request connection*/,
|
new FakeNameResolverFactory.Builder(expectedUri).build(),
|
||||||
|
NO_INTERCEPTOR,
|
||||||
|
true /* request connection*/,
|
||||||
idleTimeoutMillis);
|
idleTimeoutMillis);
|
||||||
timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
|
timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
|
||||||
assertEquals(IDLE, channel.getState(false));
|
assertEquals(IDLE, channel.getState(false));
|
||||||
|
|
@ -1753,7 +1883,7 @@ public class ManagedChannelImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void updateBalancingStateDoesUpdatePicker() {
|
public void updateBalancingStateDoesUpdatePicker() {
|
||||||
ClientStream mockStream = mock(ClientStream.class);
|
ClientStream mockStream = mock(ClientStream.class);
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
|
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
|
||||||
call.start(mockCallListener, new Metadata());
|
call.start(mockCallListener, new Metadata());
|
||||||
|
|
@ -1791,7 +1921,9 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateBalancingStateWithShutdownShouldBeIgnored() {
|
public void updateBalancingStateWithShutdownShouldBeIgnored() {
|
||||||
createChannel(new FakeNameResolverFactory(false), NO_INTERCEPTOR);
|
createChannel(
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build(),
|
||||||
|
NO_INTERCEPTOR);
|
||||||
assertEquals(IDLE, channel.getState(false));
|
assertEquals(IDLE, channel.getState(false));
|
||||||
|
|
||||||
Runnable onStateChanged = mock(Runnable.class);
|
Runnable onStateChanged = mock(Runnable.class);
|
||||||
|
|
@ -1805,20 +1937,53 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resetConnectBackoff_refreshesNameResolver() {
|
public void resetConnectBackoff() {
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
// Start with a name resolution failure to trigger backoff attempts
|
||||||
|
Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error"));
|
||||||
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).setError(error).build();
|
||||||
|
// Name resolution is started as soon as channel is created.
|
||||||
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
|
||||||
|
verify(mockLoadBalancer).handleNameResolutionError(same(error));
|
||||||
|
|
||||||
|
FakeClock.ScheduledTask nameResolverBackoff = getNameResolverRefresh();
|
||||||
|
assertNotNull("There should be a name resolver backoff task", nameResolverBackoff);
|
||||||
|
assertEquals(0, resolver.refreshCalled);
|
||||||
|
|
||||||
|
// Verify resetConnectBackoff() calls refresh and cancels the scheduled backoff
|
||||||
|
channel.resetConnectBackoff();
|
||||||
|
assertEquals(1, resolver.refreshCalled);
|
||||||
|
assertTrue(nameResolverBackoff.isCancelled());
|
||||||
|
|
||||||
|
// Simulate a race between cancel and the task scheduler. Should be a no-op.
|
||||||
|
nameResolverBackoff.command.run();
|
||||||
|
assertEquals(1, resolver.refreshCalled);
|
||||||
|
|
||||||
|
// Verify that the reconnect policy was recreated and the backoff multiplier reset to 1
|
||||||
|
timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS);
|
||||||
|
assertEquals(2, resolver.refreshCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resetConnectBackoff_noOpWithoutPendingResolverBackoff() {
|
||||||
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri)
|
||||||
|
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
|
||||||
|
.build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0);
|
FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0);
|
||||||
assertEquals(0, nameResolver.refreshCalled);
|
assertEquals(0, nameResolver.refreshCalled);
|
||||||
|
|
||||||
channel.resetConnectBackoff();
|
channel.resetConnectBackoff();
|
||||||
|
|
||||||
assertEquals(1, nameResolver.refreshCalled);
|
assertEquals(0, nameResolver.refreshCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resetConnectBackoff_noOpWhenChannelShutdown() {
|
public void resetConnectBackoff_noOpWhenChannelShutdown() {
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
createChannel(nameResolverFactory, NO_INTERCEPTOR);
|
||||||
|
|
||||||
channel.shutdown();
|
channel.shutdown();
|
||||||
|
|
@ -1831,7 +1996,8 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resetConnectBackoff_noOpWhenNameResolverNotStarted() {
|
public void resetConnectBackoff_noOpWhenNameResolverNotStarted() {
|
||||||
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory(true);
|
FakeNameResolverFactory nameResolverFactory =
|
||||||
|
new FakeNameResolverFactory.Builder(expectedUri).build();
|
||||||
createChannel(nameResolverFactory, NO_INTERCEPTOR, false /* requestConnection */,
|
createChannel(nameResolverFactory, NO_INTERCEPTOR, false /* requestConnection */,
|
||||||
ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE);
|
ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE);
|
||||||
|
|
||||||
|
|
@ -1843,7 +2009,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelsAndSubchannels_instrumented_name() throws Exception {
|
public void channelsAndSubchannels_instrumented_name() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
assertEquals(target, getStats(channel).target);
|
assertEquals(target, getStats(channel).target);
|
||||||
|
|
||||||
Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
|
||||||
|
|
@ -1852,7 +2018,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelsAndSubchannels_instrumented_state() throws Exception {
|
public void channelsAndSubchannels_instrumented_state() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
|
ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
|
||||||
verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
|
verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
|
||||||
|
|
@ -1886,7 +2052,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelStat_callStarted() throws Exception {
|
public void channelStat_callStarted() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
|
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
|
||||||
assertEquals(0, getStats(channel).callsStarted);
|
assertEquals(0, getStats(channel).callsStarted);
|
||||||
call.start(mockCallListener, new Metadata());
|
call.start(mockCallListener, new Metadata());
|
||||||
|
|
@ -1905,7 +2071,7 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void channelsAndSubchannels_instrumented0(boolean success) throws Exception {
|
private void channelsAndSubchannels_instrumented0(boolean success) throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
|
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
|
||||||
|
|
||||||
|
|
@ -1976,7 +2142,7 @@ public class ManagedChannelImplTest {
|
||||||
private void channelsAndSubchannels_oob_instrumented0(boolean success) throws Exception {
|
private void channelsAndSubchannels_oob_instrumented0(boolean success) throws Exception {
|
||||||
// set up
|
// set up
|
||||||
ClientStream mockStream = mock(ClientStream.class);
|
ClientStream mockStream = mock(ClientStream.class);
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority");
|
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority");
|
||||||
AbstractSubchannel oobSubchannel = (AbstractSubchannel) oobChannel.getSubchannel();
|
AbstractSubchannel oobSubchannel = (AbstractSubchannel) oobChannel.getSubchannel();
|
||||||
|
|
@ -2037,7 +2203,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelsAndSubchannels_oob_instrumented_name() throws Exception {
|
public void channelsAndSubchannels_oob_instrumented_name() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
String authority = "oobauthority";
|
String authority = "oobauthority";
|
||||||
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, authority);
|
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, authority);
|
||||||
|
|
@ -2046,7 +2212,7 @@ public class ManagedChannelImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void channelsAndSubchannels_oob_instrumented_state() throws Exception {
|
public void channelsAndSubchannels_oob_instrumented_state() throws Exception {
|
||||||
createChannel(new FakeNameResolverFactory(true), NO_INTERCEPTOR);
|
createChannel(new FakeNameResolverFactory.Builder(expectedUri).build(), NO_INTERCEPTOR);
|
||||||
|
|
||||||
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority");
|
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority");
|
||||||
assertEquals(IDLE, getStats(oobChannel).state);
|
assertEquals(IDLE, getStats(oobChannel).state);
|
||||||
|
|
@ -2146,7 +2312,7 @@ public class ManagedChannelImplTest {
|
||||||
Metadata headers = new Metadata();
|
Metadata headers = new Metadata();
|
||||||
ClientStream mockStream = mock(ClientStream.class);
|
ClientStream mockStream = mock(ClientStream.class);
|
||||||
createChannel(
|
createChannel(
|
||||||
new FakeNameResolverFactory(true),
|
new FakeNameResolverFactory.Builder(expectedUri).build(),
|
||||||
ImmutableList.<ClientInterceptor>of(userInterceptor));
|
ImmutableList.<ClientInterceptor>of(userInterceptor));
|
||||||
CallOptions options =
|
CallOptions options =
|
||||||
CallOptions.DEFAULT.withExecutor(executor.getScheduledExecutorService());
|
CallOptions.DEFAULT.withExecutor(executor.getScheduledExecutorService());
|
||||||
|
|
@ -2229,7 +2395,7 @@ public class ManagedChannelImplTest {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
createChannel(
|
createChannel(
|
||||||
new FakeNameResolverFactory(true),
|
new FakeNameResolverFactory.Builder(expectedUri).build(),
|
||||||
Collections.<ClientInterceptor>singletonList(userInterceptor));
|
Collections.<ClientInterceptor>singletonList(userInterceptor));
|
||||||
ClientCall<String, Integer> call =
|
ClientCall<String, Integer> call =
|
||||||
channel.newCall(method, CallOptions.DEFAULT.withDeadlineAfter(0, TimeUnit.NANOSECONDS));
|
channel.newCall(method, CallOptions.DEFAULT.withDeadlineAfter(0, TimeUnit.NANOSECONDS));
|
||||||
|
|
@ -2254,32 +2420,32 @@ public class ManagedChannelImplTest {
|
||||||
@Override
|
@Override
|
||||||
public BackoffPolicy get() {
|
public BackoffPolicy get() {
|
||||||
return new BackoffPolicy() {
|
return new BackoffPolicy() {
|
||||||
|
private int multiplier = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long nextBackoffNanos() {
|
public long nextBackoffNanos() {
|
||||||
return RECONNECT_BACKOFF_INTERVAL_NANOS;
|
return RECONNECT_BACKOFF_INTERVAL_NANOS * multiplier++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeNameResolverFactory extends NameResolver.Factory {
|
private static class FakeNameResolverFactory extends NameResolver.Factory {
|
||||||
final List<EquivalentAddressGroup> servers;
|
private final URI expectedUri;
|
||||||
final boolean resolvedAtStart;
|
private final List<EquivalentAddressGroup> servers;
|
||||||
final ArrayList<FakeNameResolver> resolvers = new ArrayList<FakeNameResolver>();
|
private final boolean resolvedAtStart;
|
||||||
|
private final Status error;
|
||||||
|
private final ArrayList<FakeNameResolver> resolvers = new ArrayList<FakeNameResolver>();
|
||||||
|
|
||||||
FakeNameResolverFactory(boolean resolvedAtStart) {
|
private FakeNameResolverFactory(
|
||||||
|
URI expectedUri,
|
||||||
|
List<EquivalentAddressGroup> servers,
|
||||||
|
boolean resolvedAtStart,
|
||||||
|
Status error) {
|
||||||
|
this.expectedUri = expectedUri;
|
||||||
|
this.servers = servers;
|
||||||
this.resolvedAtStart = resolvedAtStart;
|
this.resolvedAtStart = resolvedAtStart;
|
||||||
servers = Collections.singletonList(new EquivalentAddressGroup(socketAddress));
|
this.error = error;
|
||||||
}
|
|
||||||
|
|
||||||
FakeNameResolverFactory(List<SocketAddress> servers) {
|
|
||||||
resolvedAtStart = true;
|
|
||||||
this.servers = Collections.singletonList(new EquivalentAddressGroup(servers));
|
|
||||||
}
|
|
||||||
|
|
||||||
public FakeNameResolverFactory() {
|
|
||||||
resolvedAtStart = true;
|
|
||||||
this.servers = ImmutableList.of();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -2288,7 +2454,7 @@ public class ManagedChannelImplTest {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
assertSame(NAME_RESOLVER_PARAMS, params);
|
assertSame(NAME_RESOLVER_PARAMS, params);
|
||||||
FakeNameResolver resolver = new FakeNameResolver();
|
FakeNameResolver resolver = new FakeNameResolver(error);
|
||||||
resolvers.add(resolver);
|
resolvers.add(resolver);
|
||||||
return resolver;
|
return resolver;
|
||||||
}
|
}
|
||||||
|
|
@ -2308,6 +2474,11 @@ public class ManagedChannelImplTest {
|
||||||
Listener listener;
|
Listener listener;
|
||||||
boolean shutdown;
|
boolean shutdown;
|
||||||
int refreshCalled;
|
int refreshCalled;
|
||||||
|
Status error;
|
||||||
|
|
||||||
|
FakeNameResolver(Status error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public String getServiceAuthority() {
|
@Override public String getServiceAuthority() {
|
||||||
return expectedUri.getAuthority();
|
return expectedUri.getAuthority();
|
||||||
|
|
@ -2323,9 +2494,14 @@ public class ManagedChannelImplTest {
|
||||||
@Override public void refresh() {
|
@Override public void refresh() {
|
||||||
assertNotNull(listener);
|
assertNotNull(listener);
|
||||||
refreshCalled++;
|
refreshCalled++;
|
||||||
|
resolved();
|
||||||
}
|
}
|
||||||
|
|
||||||
void resolved() {
|
void resolved() {
|
||||||
|
if (error != null) {
|
||||||
|
listener.onError(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
listener.onAddresses(servers, Attributes.EMPTY);
|
listener.onAddresses(servers, Attributes.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2333,33 +2509,35 @@ public class ManagedChannelImplTest {
|
||||||
shutdown = true;
|
shutdown = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static class FailingNameResolverFactory extends NameResolver.Factory {
|
private static class Builder {
|
||||||
final Status error;
|
private final URI expectedUri;
|
||||||
|
List<EquivalentAddressGroup> servers = ImmutableList.<EquivalentAddressGroup>of();
|
||||||
|
boolean resolvedAtStart = true;
|
||||||
|
Status error = null;
|
||||||
|
|
||||||
FailingNameResolverFactory(Status error) {
|
private Builder(URI expectedUri) {
|
||||||
this.error = error;
|
this.expectedUri = expectedUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Builder setServers(List<EquivalentAddressGroup> servers) {
|
||||||
public NameResolver newNameResolver(URI notUsedUri, Attributes params) {
|
this.servers = servers;
|
||||||
return new NameResolver() {
|
return this;
|
||||||
@Override public String getServiceAuthority() {
|
}
|
||||||
return "irrelevant-authority";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void start(final Listener listener) {
|
private Builder setResolvedAtStart(boolean resolvedAtStart) {
|
||||||
listener.onError(error);
|
this.resolvedAtStart = resolvedAtStart;
|
||||||
}
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void shutdown() {}
|
private Builder setError(Status error) {
|
||||||
};
|
this.error = error;
|
||||||
}
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private FakeNameResolverFactory build() {
|
||||||
public String getDefaultScheme() {
|
return new FakeNameResolverFactory(expectedUri, servers, resolvedAtStart, error);
|
||||||
return "fake";
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2371,4 +2549,8 @@ public class ManagedChannelImplTest {
|
||||||
Instrumented<ChannelStats> instrumented) throws Exception {
|
Instrumented<ChannelStats> instrumented) throws Exception {
|
||||||
return instrumented.getStats().get();
|
return instrumented.getStats().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FakeClock.ScheduledTask getNameResolverRefresh() {
|
||||||
|
return Iterables.getOnlyElement(timer.getPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER), null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue