Eliminate NPE after recovering from a temporary name resolution failure. (#11298)

* Eliminate NPE after recovering from a temporary name resolution failure.

* Add test case for 2 failing subchannels to make sure it causes channel to go into TF.
This commit is contained in:
Larry Safran 2024-06-21 13:20:44 -07:00 committed by GitHub
parent 0fea7dd32e
commit 9b39b3ec6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 69 additions and 1 deletions

View File

@ -180,22 +180,32 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
@Override
public void handleNameResolutionError(Status error) {
if (rawConnectivityState == SHUTDOWN) {
return;
}
for (SubchannelData subchannelData : subchannels.values()) {
subchannelData.getSubchannel().shutdown();
}
subchannels.clear();
if (addressIndex != null) {
addressIndex.updateGroups(null);
}
rawConnectivityState = TRANSIENT_FAILURE;
updateBalancingState(TRANSIENT_FAILURE, new Picker(PickResult.withError(error)));
}
void processSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
ConnectivityState newState = stateInfo.getState();
SubchannelData subchannelData = subchannels.get(getAddress(subchannel));
// Shutdown channels/previously relevant subchannels can still callback with state updates.
// To prevent pickers from returning these obsolete subchannels, this logic
// is included to check if the current list of active subchannels includes this subchannel.
SubchannelData subchannelData = subchannels.get(getAddress(subchannel));
if (subchannelData == null || subchannelData.getSubchannel() != subchannel) {
return;
}
if (newState == SHUTDOWN) {
return;
}

View File

@ -657,6 +657,37 @@ public class PickFirstLeafLoadBalancerTest {
assertEquals(mockSubchannel1, pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel());
}
@Test
public void nameResolutionTemporaryError() {
List<EquivalentAddressGroup> newServers = Lists.newArrayList(servers.get(0));
InOrder inOrder = inOrder(mockHelper, mockSubchannel1);
loadBalancer.acceptResolvedAddresses(
ResolvedAddresses.newBuilder().setAddresses(newServers).setAttributes(affinity).build());
inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture());
SubchannelStateListener stateListener1 = stateListenerCaptor.getValue();
stateListener1.onSubchannelState(ConnectivityStateInfo.forNonError(READY));
inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture());
assertEquals(mockSubchannel1, pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel());
loadBalancer.handleNameResolutionError(
Status.UNAVAILABLE.withDescription("nameResolutionError"));
inOrder.verify(mockHelper).updateBalancingState(
eq(TRANSIENT_FAILURE), any(SubchannelPicker.class));
loadBalancer.acceptResolvedAddresses(
ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build());
inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture());
SubchannelStateListener stateListener2 = stateListenerCaptor.getValue();
assertNull(pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel());
stateListener2.onSubchannelState(ConnectivityStateInfo.forNonError(READY));
inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture());
assertEquals(mockSubchannel1, pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel());
}
@Test
public void nameResolutionErrorWithStateChanges() {
List<EquivalentAddressGroup> newServers = Lists.newArrayList(servers.get(0));
@ -725,6 +756,33 @@ public class PickFirstLeafLoadBalancerTest {
}
}
@Test
public void failChannelWhenSubchannelsFail() {
List<EquivalentAddressGroup> newServers = Lists.newArrayList(servers.get(0), servers.get(1));
when(mockSubchannel1.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(0)));
when(mockSubchannel2.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(1)));
// accept resolved addresses
loadBalancer.acceptResolvedAddresses(
ResolvedAddresses.newBuilder().setAddresses(newServers).setAttributes(affinity).build());
InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2);
verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
verify(mockHelper).createSubchannel(createArgsCaptor.capture());
inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture());
SubchannelStateListener stateListener = stateListenerCaptor.getValue();
assertNull(pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel());
inOrder.verify(mockSubchannel1).requestConnection();
stateListener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(CONNECTION_ERROR));
inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture());
SubchannelStateListener stateListener2 = stateListenerCaptor.getValue();
stateListener2.onSubchannelState(ConnectivityStateInfo.forTransientFailure(CONNECTION_ERROR));
inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
assertEquals(CONNECTION_ERROR, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus());
}
@Test
public void updateAddresses_emptyEagList_returns_false() {
loadBalancer.acceptResolvedAddresses(