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

View File

@ -1612,24 +1612,33 @@ public class ManagedChannelImplTest {
} }
@Test @Test
public void refreshNameResolutionWhenSubchannelConnectionFailed() { public void refreshNameResolution_whenSubchannelConnectionFailed_notIdle() {
subtestRefreshNameResolutionWhenConnectionFailed(false); subtestNameResolutionRefreshWhenConnectionFailed(false, false);
} }
@Test @Test
public void refreshNameResolutionWhenOobChannelConnectionFailed() { public void refreshNameResolution_whenOobChannelConnectionFailed_notIdle() {
subtestRefreshNameResolutionWhenConnectionFailed(true); 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 = FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri) new FakeNameResolverFactory.Builder(expectedUri)
.setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
.build(); .build();
channelBuilder.nameResolverFactory(nameResolverFactory); channelBuilder.nameResolverFactory(nameResolverFactory);
createChannel(); createChannel();
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
if (isOobChannel) { if (isOobChannel) {
OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobAuthority"); OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobAuthority");
oobChannel.getSubchannel().requestConnection(); oobChannel.getSubchannel().requestConnection();
@ -1641,10 +1650,26 @@ public class ManagedChannelImplTest {
MockClientTransportInfo transportInfo = transports.poll(); MockClientTransportInfo transportInfo = transports.poll();
assertNotNull(transportInfo); 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 // Transport closed when connecting
assertEquals(0, resolver.refreshCalled); assertEquals(expectedRefreshCount, resolver.refreshCalled);
transportInfo.listener.transportShutdown(Status.UNAVAILABLE); 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); timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS);
transportInfo = transports.poll(); transportInfo = transports.poll();
@ -1653,9 +1678,13 @@ public class ManagedChannelImplTest {
transportInfo.listener.transportReady(); transportInfo.listener.transportReady();
// Transport closed when ready // Transport closed when ready
assertEquals(1, resolver.refreshCalled); assertEquals(expectedRefreshCount, resolver.refreshCalled);
transportInfo.listener.transportShutdown(Status.UNAVAILABLE); 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 @Test
@ -3316,7 +3345,6 @@ public class ManagedChannelImplTest {
} }
@Override public void refresh() { @Override public void refresh() {
assertNotNull(listener);
refreshCalled++; refreshCalled++;
resolved(); resolved();
} }