Skip to content

Commit 5e8af56

Browse files
committed
core: Fix NPE during address update with Happy Eyeballs
Fixes #12168
1 parent 795ce02 commit 5e8af56

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,13 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
137137
final ImmutableList<EquivalentAddressGroup> newImmutableAddressGroups =
138138
ImmutableList.<EquivalentAddressGroup>builder().addAll(cleanServers).build();
139139

140-
if (rawConnectivityState == READY || rawConnectivityState == CONNECTING) {
140+
if (rawConnectivityState == READY
141+
|| (rawConnectivityState == CONNECTING
142+
&& (!enableHappyEyeballs || addressIndex.isValid()))) {
141143
// If the previous ready (or connecting) subchannel exists in new address list,
142-
// keep this connection and don't create new subchannels
144+
// keep this connection and don't create new subchannels. Happy Eyeballs is excluded when
145+
// connecting, because it allows multiple attempts simultaneously, thus is fine to start at
146+
// the beginning.
143147
SocketAddress previousAddress = addressIndex.getCurrentAddress();
144148
addressIndex.updateGroups(newImmutableAddressGroups);
145149
if (addressIndex.seekTo(previousAddress)) {

core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,6 +1872,45 @@ public void updateAddresses_identical_transient_failure() {
18721872
assertEquals(PickResult.withSubchannel(mockSubchannel1), picker.pickSubchannel(mockArgs));
18731873
}
18741874

1875+
@Test
1876+
public void updateAddresses_identicalSingleAddress_connecting() {
1877+
// Creating first set of endpoints/addresses
1878+
List<EquivalentAddressGroup> oldServers = Lists.newArrayList(servers.get(0));
1879+
1880+
// Accept Addresses and verify proper connection flow
1881+
assertEquals(IDLE, loadBalancer.getConcludedConnectivityState());
1882+
loadBalancer.acceptResolvedAddresses(
1883+
ResolvedAddresses.newBuilder().setAddresses(oldServers).setAttributes(affinity).build());
1884+
verify(mockSubchannel1).start(stateListenerCaptor.capture());
1885+
SubchannelStateListener stateListener = stateListenerCaptor.getValue();
1886+
assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState());
1887+
1888+
// First connection attempt is successful
1889+
stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING));
1890+
assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState());
1891+
fakeClock.forwardTime(CONNECTION_DELAY_INTERVAL_MS, TimeUnit.MILLISECONDS);
1892+
1893+
// verify that picker returns no subchannel
1894+
verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
1895+
SubchannelPicker picker = pickerCaptor.getValue();
1896+
assertEquals(PickResult.withNoResult(), picker.pickSubchannel(mockArgs));
1897+
1898+
// Accept same resolved addresses to update
1899+
reset(mockHelper);
1900+
loadBalancer.acceptResolvedAddresses(
1901+
ResolvedAddresses.newBuilder().setAddresses(oldServers).setAttributes(affinity).build());
1902+
fakeClock.forwardTime(CONNECTION_DELAY_INTERVAL_MS, TimeUnit.MILLISECONDS);
1903+
1904+
// Verify that no new subchannels were created or started
1905+
verify(mockSubchannel2, never()).start(any());
1906+
assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState());
1907+
1908+
// verify that picker hasn't changed via checking mock helper's interactions
1909+
verify(mockHelper, atLeast(0)).getSynchronizationContext(); // Don't care
1910+
verify(mockHelper, atLeast(0)).getScheduledExecutorService();
1911+
verifyNoMoreInteractions(mockHelper);
1912+
}
1913+
18751914
@Test
18761915
public void twoAddressesSeriallyConnect() {
18771916
// Starting first connection attempt

0 commit comments

Comments
 (0)