Skip to content

Commit 512d4a2

Browse files
committed
core: Don't set firstPass=true on address update in pick_first
Updating the addresses has no bearing if pick_first is doing the first pass, for the sake of when refreshNameResolution() is triggered. In the first pass pick_first is in CONNECTING and after the first pass it is in TRANSIENT_FAILURE. If pick_first already did a first pass and is in TF, then when it gets updated addresses it _stays_ in TF and the regular "We will count the number of connection failures, and when that number reaches the number of subchannels, we will request re-resolution" should apply. Setting firstPass=true on each address update can cause us to request refreshes too frequently. So we only need to adjust firstPass when changing the rawConnectivityState. And if we do that, we don't actually need firstPass; we can use numTf instead.
1 parent fa7b52b commit 512d4a2

2 files changed

Lines changed: 20 additions & 12 deletions

File tree

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
6969
private final Map<SocketAddress, SubchannelData> subchannels = new HashMap<>();
7070
private final Index addressIndex = new Index(ImmutableList.of(), this.enableHappyEyeballs);
7171
private int numTf = 0;
72-
private boolean firstPass = true;
7372
@Nullable
7473
private ScheduledHandle scheduleConnectionTask = null;
7574
private ConnectivityState rawConnectivityState = IDLE;
@@ -120,9 +119,6 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
120119
}
121120
}
122121

123-
// Since we have a new set of addresses, we are again at first pass
124-
firstPass = true;
125-
126122
List<EquivalentAddressGroup> cleanServers = deDupAddresses(servers);
127123

128124
// We can optionally be configured to shuffle the address list. This can help better distribute
@@ -340,10 +336,15 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo
340336
shutdownRemaining(subchannelData);
341337
addressIndex.seekTo(getAddress(subchannelData.subchannel));
342338
rawConnectivityState = READY;
339+
// Reset for clarity, but isPassComplete() prevents any counts currently present from being
340+
// read until they point they no longer matter
341+
numTf = 0;
343342
updateHealthCheckedState(subchannelData);
344343
break;
345344

346345
case TRANSIENT_FAILURE:
346+
numTf++;
347+
347348
// If we are looking at current channel, request a connection if possible
348349
if (addressIndex.isValid()
349350
&& subchannels.get(addressIndex.getCurrentAddress()) == subchannelData) {
@@ -372,9 +373,8 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo
372373
// * have had status reported for all subchannels.
373374
// * And one of the following conditions:
374375
// * Have had enough TF reported since we completed first pass
375-
// * Just completed the first pass
376-
if (++numTf >= addressIndex.size() || firstPass) {
377-
firstPass = false;
376+
// * Just completed the first pass (in which case enough TF have been reported)
377+
if (numTf >= addressIndex.size()) {
378378
numTf = 0;
379379
helper.refreshNameResolution();
380380
}

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,7 @@ public void updateAddresses_identical_transient_failure() {
19401940
loadBalancer.acceptResolvedAddresses(
19411941
ResolvedAddresses.newBuilder().setAddresses(oldServers).setAttributes(affinity).build());
19421942
inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
1943+
inOrder.verify(mockHelper).createSubchannel(any());
19431944
inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture());
19441945
SubchannelStateListener stateListener = stateListenerCaptor.getValue();
19451946
assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState());
@@ -1949,6 +1950,7 @@ public void updateAddresses_identical_transient_failure() {
19491950
assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState());
19501951

19511952
// Second connection attempt is unsuccessful
1953+
inOrder.verify(mockHelper).createSubchannel(any());
19521954
inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture());
19531955
assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState());
19541956
SubchannelStateListener stateListener2 = stateListenerCaptor.getValue();
@@ -1961,18 +1963,24 @@ public void updateAddresses_identical_transient_failure() {
19611963
loadBalancer.acceptResolvedAddresses(
19621964
ResolvedAddresses.newBuilder().setAddresses(oldServers).setAttributes(affinity).build());
19631965

1964-
// Verify that no new subchannels were created or started
1965-
verify(mockHelper, times(2)).createSubchannel(createArgsCaptor.capture());
1966-
verify(mockSubchannel1, times(1)).start(stateListenerCaptor.capture());
1967-
verify(mockSubchannel2, times(1)).start(stateListenerCaptor.capture());
1966+
// Verify that no new subchannels were created or started, nor resolver refresh
1967+
inOrder.verify(mockHelper, never()).createSubchannel(createArgsCaptor.capture());
1968+
inOrder.verify(mockSubchannel1, never()).start(stateListenerCaptor.capture());
1969+
inOrder.verify(mockSubchannel2, never()).start(stateListenerCaptor.capture());
1970+
inOrder.verify(mockHelper, never()).refreshNameResolution();
19681971
assertEquals(TRANSIENT_FAILURE, loadBalancer.getConcludedConnectivityState());
19691972

19701973
// No new connections are requested, subchannels responsible for completing their own backoff
19711974
verify(mockHelper, atLeast(0)).getSynchronizationContext(); // Don't care
19721975
verify(mockHelper, atLeast(0)).getScheduledExecutorService();
19731976
verifyNoMoreInteractions(mockHelper);
19741977

1975-
// First connection attempt is successful
1978+
// Second connection is unsuccessful (e.g., retry after backoff), and there is no name resolver
1979+
// refresh
1980+
stateListener2.onSubchannelState(ConnectivityStateInfo.forTransientFailure(CONNECTION_ERROR));
1981+
inOrder.verify(mockHelper, never()).refreshNameResolution();
1982+
1983+
// First connection is successful
19761984
stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(READY));
19771985
assertEquals(READY, loadBalancer.getConcludedConnectivityState());
19781986
inOrder.verify(mockSubchannel2).shutdown();

0 commit comments

Comments
 (0)