xds: Do not failoverpriority when IDLE->CONNECTING

This commit is contained in:
Penn (Dapeng) Zhang 2022-02-16 21:28:11 -08:00 committed by Eric Anderson
parent c4d21410c6
commit 89e53dc875
2 changed files with 111 additions and 7 deletions

View File

@ -41,6 +41,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -66,6 +67,7 @@ final class PriorityLoadBalancer extends LoadBalancer {
private List<String> priorityNames; private List<String> priorityNames;
// Config for each priority. // Config for each priority.
private Map<String, PriorityChildConfig> priorityConfigs; private Map<String, PriorityChildConfig> priorityConfigs;
@Nullable private String currentPriority;
private ConnectivityState currentConnectivityState; private ConnectivityState currentConnectivityState;
private SubchannelPicker currentPicker; private SubchannelPicker currentPicker;
@ -113,7 +115,7 @@ final class PriorityLoadBalancer extends LoadBalancer {
} }
} }
if (gotoTransientFailure) { if (gotoTransientFailure) {
updateOverallState(TRANSIENT_FAILURE, new ErrorPicker(error)); updateOverallState(null, TRANSIENT_FAILURE, new ErrorPicker(error));
} }
} }
@ -134,14 +136,14 @@ final class PriorityLoadBalancer extends LoadBalancer {
new ChildLbState(priority, priorityConfigs.get(priority).ignoreReresolution); new ChildLbState(priority, priorityConfigs.get(priority).ignoreReresolution);
children.put(priority, child); children.put(priority, child);
child.updateResolvedAddresses(); child.updateResolvedAddresses();
updateOverallState(CONNECTING, BUFFER_PICKER); updateOverallState(priority, CONNECTING, BUFFER_PICKER);
return; // Give priority i time to connect. return; // Give priority i time to connect.
} }
ChildLbState child = children.get(priority); ChildLbState child = children.get(priority);
child.reactivate(); child.reactivate();
if (child.connectivityState.equals(READY) || child.connectivityState.equals(IDLE)) { if (child.connectivityState.equals(READY) || child.connectivityState.equals(IDLE)) {
logger.log(XdsLogLevel.DEBUG, "Shifted to priority {0}", priority); logger.log(XdsLogLevel.DEBUG, "Shifted to priority {0}", priority);
updateOverallState(child.connectivityState, child.picker); updateOverallState(priority, child.connectivityState, child.picker);
for (int j = i + 1; j < priorityNames.size(); j++) { for (int j = i + 1; j < priorityNames.size(); j++) {
String p = priorityNames.get(j); String p = priorityNames.get(j);
if (children.containsKey(p)) { if (children.containsKey(p)) {
@ -152,20 +154,28 @@ final class PriorityLoadBalancer extends LoadBalancer {
} }
if (child.failOverTimer != null && child.failOverTimer.isPending()) { if (child.failOverTimer != null && child.failOverTimer.isPending()) {
if (reportConnecting) { if (reportConnecting) {
updateOverallState(CONNECTING, BUFFER_PICKER); updateOverallState(priority, CONNECTING, BUFFER_PICKER);
} }
return; // Give priority i time to connect. return; // Give priority i time to connect.
} }
if (priority.equals(currentPriority) && child.connectivityState != TRANSIENT_FAILURE) {
// If the current priority is not changed into TRANSIENT_FAILURE, keep using it.
updateOverallState(priority, child.connectivityState, child.picker);
return;
}
} }
// TODO(zdapeng): Include error details of each priority. // TODO(zdapeng): Include error details of each priority.
logger.log(XdsLogLevel.DEBUG, "All priority failed"); logger.log(XdsLogLevel.DEBUG, "All priority failed");
String lastPriority = priorityNames.get(priorityNames.size() - 1); String lastPriority = priorityNames.get(priorityNames.size() - 1);
SubchannelPicker errorPicker = children.get(lastPriority).picker; SubchannelPicker errorPicker = children.get(lastPriority).picker;
updateOverallState(TRANSIENT_FAILURE, errorPicker); updateOverallState(lastPriority, TRANSIENT_FAILURE, errorPicker);
} }
private void updateOverallState(ConnectivityState state, SubchannelPicker picker) { private void updateOverallState(
if (!state.equals(currentConnectivityState) || !picker.equals(currentPicker)) { @Nullable String priority, ConnectivityState state, SubchannelPicker picker) {
if (!Objects.equals(priority, currentPriority) || !state.equals(currentConnectivityState)
|| !picker.equals(currentPicker)) {
currentPriority = priority;
currentConnectivityState = state; currentConnectivityState = state;
currentPicker = picker; currentPicker = picker;
helper.updateBalancingState(state, picker); helper.updateBalancingState(state, picker);
@ -201,6 +211,7 @@ final class PriorityLoadBalancer extends LoadBalancer {
picker = new ErrorPicker( picker = new ErrorPicker(
Status.UNAVAILABLE.withDescription("Connection timeout for priority " + priority)); Status.UNAVAILABLE.withDescription("Connection timeout for priority " + priority));
logger.log(XdsLogLevel.DEBUG, "Priority {0} failed over to next", priority); logger.log(XdsLogLevel.DEBUG, "Priority {0} failed over to next", priority);
currentPriority = null; // reset currentPriority to guarantee failover happen
tryNextPriority(true); tryNextPriority(true);
} }
} }

View File

@ -399,6 +399,99 @@ public class PriorityLoadBalancerTest {
verify(balancer3).shutdown(); verify(balancer3).shutdown();
} }
@Test
public void idleToConnectingDoesNotTriggerFailOver() {
PriorityChildConfig priorityChildConfig0 =
new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true);
PriorityChildConfig priorityChildConfig1 =
new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true);
PriorityLbConfig priorityLbConfig =
new PriorityLbConfig(
ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1),
ImmutableList.of("p0", "p1"));
priorityLb.handleResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(ImmutableList.<EquivalentAddressGroup>of())
.setLoadBalancingPolicyConfig(priorityLbConfig)
.build());
assertThat(fooBalancers).hasSize(1);
assertThat(fooHelpers).hasSize(1);
Helper helper0 = Iterables.getOnlyElement(fooHelpers);
// p0 gets IDLE.
helper0.updateBalancingState(
IDLE,
BUFFER_PICKER);
assertCurrentPickerIsBufferPicker();
// p0 goes to CONNECTING
helper0.updateBalancingState(
IDLE,
BUFFER_PICKER);
assertCurrentPickerIsBufferPicker();
// no failover happened
assertThat(fooBalancers).hasSize(1);
assertThat(fooHelpers).hasSize(1);
}
@Test
public void readyToConnectDoesNotFailOverButUpdatesPicker() {
PriorityChildConfig priorityChildConfig0 =
new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true);
PriorityChildConfig priorityChildConfig1 =
new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true);
PriorityLbConfig priorityLbConfig =
new PriorityLbConfig(
ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1),
ImmutableList.of("p0", "p1"));
priorityLb.handleResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(ImmutableList.<EquivalentAddressGroup>of())
.setLoadBalancingPolicyConfig(priorityLbConfig)
.build());
assertThat(fooBalancers).hasSize(1);
assertThat(fooHelpers).hasSize(1);
Helper helper0 = Iterables.getOnlyElement(fooHelpers);
// p0 gets READY.
final Subchannel subchannel0 = mock(Subchannel.class);
helper0.updateBalancingState(
READY,
new SubchannelPicker() {
@Override
public PickResult pickSubchannel(PickSubchannelArgs args) {
return PickResult.withSubchannel(subchannel0);
}
});
assertCurrentPickerPicksSubchannel(subchannel0);
// p0 goes to CONNECTING
helper0.updateBalancingState(
IDLE,
BUFFER_PICKER);
assertCurrentPickerIsBufferPicker();
// no failover happened
assertThat(fooBalancers).hasSize(1);
assertThat(fooHelpers).hasSize(1);
// resolution update without priority change does not trigger failover
Attributes.Key<String> fooKey = Attributes.Key.create("fooKey");
priorityLb.handleResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(ImmutableList.<EquivalentAddressGroup>of())
.setLoadBalancingPolicyConfig(priorityLbConfig)
.setAttributes(Attributes.newBuilder().set(fooKey, "barVal").build())
.build());
assertCurrentPickerIsBufferPicker();
// no failover happened
assertThat(fooBalancers).hasSize(1);
assertThat(fooHelpers).hasSize(1);
}
@Test @Test
public void typicalPriorityFailOverFlowWithIdleUpdate() { public void typicalPriorityFailOverFlowWithIdleUpdate() {
PriorityChildConfig priorityChildConfig0 = PriorityChildConfig priorityChildConfig0 =