mirror of https://github.com/grpc/grpc-java.git
core: fix a channel panic bug caused by calling NameResolver.refresh() when it's not started (#5223)
Resolves #5222
This commit is contained in:
parent
08218810fd
commit
7475b7f110
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue