core: fix a channel panic bug caused by calling NameResolver.refresh() when it's not started (#5223)

Resolves #5222
This commit is contained in:
Kun Zhang 2019-01-10 11:28:58 -08:00 committed by GitHub
parent 08218810fd
commit 7475b7f110
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 26 deletions

View File

@ -308,16 +308,20 @@ final class ManagedChannelImpl extends ManagedChannel implements
}
// Must be called from syncContext
private void shutdownNameResolverAndLoadBalancer(boolean verifyActive) {
if (verifyActive) {
checkState(nameResolver != null, "nameResolver is null");
private void shutdownNameResolverAndLoadBalancer(boolean channelIsActive) {
if (channelIsActive) {
checkState(nameResolverStarted, "nameResolver is not started");
checkState(lbHelper != null, "lbHelper is null");
}
if (nameResolver != null) {
cancelNameResolverBackoff();
nameResolver.shutdown();
nameResolver = null;
nameResolverStarted = false;
if (channelIsActive) {
nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
} else {
nameResolver = null;
}
}
if (lbHelper != null) {
lbHelper.lb.shutdown();
@ -372,7 +376,6 @@ final class ManagedChannelImpl extends ManagedChannel implements
// which are bugs.
shutdownNameResolverAndLoadBalancer(true);
delayedTransport.reprocess(null);
nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
channelLogger.log(ChannelLogLevel.INFO, "Entering IDLE state");
channelStateManager.gotoState(IDLE);
if (inUseStateAggregator.isInUse()) {
@ -399,9 +402,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
@Override
public void run() {
scheduledNameResolverRefresh = null;
if (nameResolver != null) {
nameResolver.refresh();
}
refreshNameResolution();
}
}
@ -421,11 +422,19 @@ final class ManagedChannelImpl extends ManagedChannel implements
}
}
// Must be run from syncContext
private void refreshNameResolutionNow() {
/**
* Force name resolution refresh to happen immediately and reset refresh back-off. Must be run
* from syncContext.
*/
private void refreshAndResetNameResolution() {
syncContext.throwIfNotInThisSynchronizationContext();
cancelNameResolverBackoff();
if (nameResolver != null) {
refreshNameResolution();
}
private void refreshNameResolution() {
syncContext.throwIfNotInThisSynchronizationContext();
if (nameResolverStarted) {
nameResolver.refresh();
}
}
@ -871,7 +880,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
}
if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh.isPending()) {
checkState(nameResolverStarted, "name resolver must be started");
refreshNameResolutionNow();
refreshAndResetNameResolution();
}
for (InternalSubchannel subchannel : subchannels) {
subchannel.resetConnectBackoff();
@ -990,7 +999,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
// Must be called from syncContext
private void handleInternalSubchannelState(ConnectivityStateInfo newState) {
if (newState.getState() == TRANSIENT_FAILURE || newState.getState() == IDLE) {
refreshNameResolutionNow();
refreshAndResetNameResolution();
}
}
@ -1122,7 +1131,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
final class LoadBalancerRefreshNameResolution implements Runnable {
@Override
public void run() {
refreshNameResolutionNow();
refreshAndResetNameResolution();
}
}

View File

@ -1612,24 +1612,33 @@ public class ManagedChannelImplTest {
}
@Test
public void refreshNameResolutionWhenSubchannelConnectionFailed() {
subtestRefreshNameResolutionWhenConnectionFailed(false);
public void refreshNameResolution_whenSubchannelConnectionFailed_notIdle() {
subtestNameResolutionRefreshWhenConnectionFailed(false, false);
}
@Test
public void refreshNameResolutionWhenOobChannelConnectionFailed() {
subtestRefreshNameResolutionWhenConnectionFailed(true);
public void refreshNameResolution_whenOobChannelConnectionFailed_notIdle() {
subtestNameResolutionRefreshWhenConnectionFailed(true, false);
}
private void subtestRefreshNameResolutionWhenConnectionFailed(boolean isOobChannel) {
@Test
public void notRefreshNameResolution_whenSubchannelConnectionFailed_idle() {
subtestNameResolutionRefreshWhenConnectionFailed(false, true);
}
@Test
public void notRefreshNameResolution_whenOobChannelConnectionFailed_idle() {
subtestNameResolutionRefreshWhenConnectionFailed(true, true);
}
private void subtestNameResolutionRefreshWhenConnectionFailed(
boolean isOobChannel, boolean isIdle) {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
.build();
channelBuilder.nameResolverFactory(nameResolverFactory);
createChannel();
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
if (isOobChannel) {
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobAuthority");
oobChannel.getSubchannel().requestConnection();
@ -1641,10 +1650,26 @@ public class ManagedChannelImplTest {
MockClientTransportInfo transportInfo = transports.poll();
assertNotNull(transportInfo);
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.remove(0);
if (isIdle) {
channel.enterIdle();
// Entering idle mode will result in a new resolver
resolver = nameResolverFactory.resolvers.remove(0);
}
assertEquals(0, nameResolverFactory.resolvers.size());
int expectedRefreshCount = 0;
// Transport closed when connecting
assertEquals(0, resolver.refreshCalled);
assertEquals(expectedRefreshCount, resolver.refreshCalled);
transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
assertEquals(1, resolver.refreshCalled);
// When channel enters idle, new resolver is created but not started.
if (!isIdle) {
expectedRefreshCount++;
}
assertEquals(expectedRefreshCount, resolver.refreshCalled);
timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS);
transportInfo = transports.poll();
@ -1653,9 +1678,13 @@ public class ManagedChannelImplTest {
transportInfo.listener.transportReady();
// Transport closed when ready
assertEquals(1, resolver.refreshCalled);
assertEquals(expectedRefreshCount, resolver.refreshCalled);
transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
assertEquals(2, resolver.refreshCalled);
// When channel enters idle, new resolver is created but not started.
if (!isIdle) {
expectedRefreshCount++;
}
assertEquals(expectedRefreshCount, resolver.refreshCalled);
}
@Test
@ -3316,7 +3345,6 @@ public class ManagedChannelImplTest {
}
@Override public void refresh() {
assertNotNull(listener);
refreshCalled++;
resolved();
}