Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A74 subclass move #11923

Draft
wants to merge 44 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
fd069e3
Framework definition to support A74
larry-safran Dec 4, 2024
b2cb05b
Test that update works (with associated fixes)
larry-safran Jan 6, 2025
8e668b5
Cleanup
larry-safran Jan 6, 2025
5b14a3e
Cleanup
larry-safran Jan 6, 2025
5f8d479
Add verification of data changing
larry-safran Jan 7, 2025
ec50490
Support aggregate clusters correctly
larry-safran Jan 7, 2025
5dceeaf
Fix class name referenced in javadoc
larry-safran Jan 7, 2025
fd64f20
Address a number of code review comments and add a test for missing C…
larry-safran Jan 8, 2025
b2e924e
Remove syncContext from watchers. Add checkNotNull, private and fina…
larry-safran Jan 8, 2025
5a75b10
Add a test for corrupt LDS
larry-safran Jan 8, 2025
d3b713f
Change aggregate cluster handling to correctly handle cluster names a…
larry-safran Jan 9, 2025
6089730
Errorprone
larry-safran Jan 9, 2025
d5dea83
Add max recursion limit for clusters to match c++.
larry-safran Jan 14, 2025
28d29fb
Fix handling of route and cluster updates.
larry-safran Jan 15, 2025
4a53fce
Make data private and XdsWatcherBase static
larry-safran Jan 15, 2025
c97118f
In LDS onChanged(), get old activeVirtualHost before possibly doing c…
larry-safran Jan 16, 2025
06466fc
Change comment for clarity
larry-safran Jan 16, 2025
b898e34
Allow EdsWatcher to have multiple CdsWatcher parents
larry-safran Jan 16, 2025
954ced3
Allow EdsWatcher to have multiple CdsWatcher parents
larry-safran Jan 16, 2025
ef13712
Fully support inlined RouteConfig
larry-safran Jan 17, 2025
518cef1
Add lots of `checkNotNull()`
larry-safran Jan 17, 2025
68071e6
Add test case testMultipleParentsInCdsTree, make a couple of cluster …
larry-safran Jan 18, 2025
f99fc56
Add test case testMultipleParentsInCdsTree, make a couple of cluster …
larry-safran Jan 18, 2025
482cd9d
Add virtual host to XdsConfig as per spec
larry-safran Jan 21, 2025
36602b6
Eliminate clusterSubscriptions and use objects directly instead of st…
larry-safran Feb 1, 2025
7042ed9
Add tests. Fix some places that were still using string instead of o…
larry-safran Feb 5, 2025
7ab45bb
core: Alternate ipV4 and ipV6 addresses for Happy Eyeballs in PickFir…
larry-safran Jan 14, 2025
f2701d9
interop-testing: fix peer extraction issue in soak test iterations
zbilun Jan 14, 2025
8b4391f
interop-testing: Move soak out of AbstractInteropTest
ejona86 Jan 14, 2025
42270da
stub: Eliminate invalid test cases where different threads were calli…
larry-safran Jan 15, 2025
ae0fa25
xds: Envoy proto sync to 2024-11-11 (#11816)
shivaspeaks Jan 17, 2025
bdf6987
compiler: Prepare for C++ protobuf using string_view
ejona86 Jan 15, 2025
31966be
Update README etc to reference 1.69.1
ejona86 Jan 16, 2025
d4e5064
xds: Rename grpc.xds.cluster to grpc.lb.backend_service
ejona86 Jan 18, 2025
27e32b1
xds: Fix fallback test FakeClock TSAN failure
ejona86 Jan 22, 2025
9efd2f2
xds: Include max concurrent request limit in the error status for con…
kannanjgithub Jan 23, 2025
e6503ff
Much progress in A74 using DependencyManager
larry-safran Jan 31, 2025
0ba5c84
Merge with master
larry-safran Feb 24, 2025
86ea936
Merge with master
larry-safran Feb 25, 2025
d72d674
Handle Logical DNS clusters
larry-safran Feb 27, 2025
5f4813d
don't publish if any logical dns clusters are still unresolved.
larry-safran Feb 27, 2025
7e32c0a
Fix tests and some underlying bugs
larry-safran Feb 28, 2025
3569db8
Fix tests and some underlying bugs
larry-safran Mar 1, 2025
9c896a2
Fix tests and some underlying bugs
larry-safran Mar 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
core: Alternate ipV4 and ipV6 addresses for Happy Eyeballs in PickFir…
…stLeafLoadBalancer (#11624)

* Interweave ipV4 and ipV6 addresses as per gRFC.
larry-safran committed Feb 5, 2025
commit 7ab45bb62fa41882b1b0b30abdabe38a5ffec24d
159 changes: 116 additions & 43 deletions core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java
Original file line number Diff line number Diff line change
@@ -34,6 +34,8 @@
import io.grpc.LoadBalancer;
import io.grpc.Status;
import io.grpc.SynchronizationContext.ScheduledHandle;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
@@ -58,17 +60,17 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer {
private static final Logger log = Logger.getLogger(PickFirstLeafLoadBalancer.class.getName());
@VisibleForTesting
static final int CONNECTION_DELAY_INTERVAL_MS = 250;
private final boolean enableHappyEyeballs = !isSerializingRetries()
&& PickFirstLoadBalancerProvider.isEnabledHappyEyeballs();
private final Helper helper;
private final Map<SocketAddress, SubchannelData> subchannels = new HashMap<>();
private final Index addressIndex = new Index(ImmutableList.of());
private final Index addressIndex = new Index(ImmutableList.of(), this.enableHappyEyeballs);
private int numTf = 0;
private boolean firstPass = true;
@Nullable
private ScheduledHandle scheduleConnectionTask = null;
private ConnectivityState rawConnectivityState = IDLE;
private ConnectivityState concludedState = IDLE;
private final boolean enableHappyEyeballs = !isSerializingRetries()
&& PickFirstLoadBalancerProvider.isEnabledHappyEyeballs();
private boolean notAPetiolePolicy = true; // means not under a petiole policy
private final BackoffPolicy.Provider bkoffPolProvider = new ExponentialBackoffPolicy.Provider();
private BackoffPolicy reconnectPolicy;
@@ -610,27 +612,26 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
}

/**
* Index as in 'i', the pointer to an entry. Not a "search index."
* This contains both an ordered list of addresses and a pointer(i.e. index) to the current entry.
* All updates should be done in a synchronization context.
*/
@VisibleForTesting
static final class Index {
private List<EquivalentAddressGroup> addressGroups;
private int size;
private int groupIndex;
private int addressIndex;
private List<UnwrappedEag> orderedAddresses;
private int activeElement = 0;
private boolean enableHappyEyeballs;

public Index(List<EquivalentAddressGroup> groups) {
Index(List<EquivalentAddressGroup> groups, boolean enableHappyEyeballs) {
this.enableHappyEyeballs = enableHappyEyeballs;
updateGroups(groups);
}

public boolean isValid() {
// Is invalid if empty or has incremented off the end
return groupIndex < addressGroups.size();
return activeElement < orderedAddresses.size();
}

public boolean isAtBeginning() {
return groupIndex == 0 && addressIndex == 0;
return activeElement == 0;
}

/**
@@ -642,79 +643,150 @@ public boolean increment() {
return false;
}

EquivalentAddressGroup group = addressGroups.get(groupIndex);
addressIndex++;
if (addressIndex >= group.getAddresses().size()) {
groupIndex++;
addressIndex = 0;
return groupIndex < addressGroups.size();
}
activeElement++;

return true;
return isValid();
}

public void reset() {
groupIndex = 0;
addressIndex = 0;
activeElement = 0;
}

public SocketAddress getCurrentAddress() {
if (!isValid()) {
throw new IllegalStateException("Index is past the end of the address group list");
}
return addressGroups.get(groupIndex).getAddresses().get(addressIndex);
return orderedAddresses.get(activeElement).address;
}

public Attributes getCurrentEagAttributes() {
if (!isValid()) {
throw new IllegalStateException("Index is off the end of the address group list");
}
return addressGroups.get(groupIndex).getAttributes();
return orderedAddresses.get(activeElement).attributes;
}

public List<EquivalentAddressGroup> getCurrentEagAsList() {
return Collections.singletonList(
new EquivalentAddressGroup(getCurrentAddress(), getCurrentEagAttributes()));
return Collections.singletonList(getCurrentEag());
}

private EquivalentAddressGroup getCurrentEag() {
if (!isValid()) {
throw new IllegalStateException("Index is past the end of the address group list");
}
return orderedAddresses.get(activeElement).asEag();
}

/**
* Update to new groups, resetting the current index.
*/
public void updateGroups(List<EquivalentAddressGroup> newGroups) {
addressGroups = checkNotNull(newGroups, "newGroups");
checkNotNull(newGroups, "newGroups");
orderedAddresses = enableHappyEyeballs
? updateGroupsHE(newGroups)
: updateGroupsNonHE(newGroups);
reset();
int size = 0;
for (EquivalentAddressGroup eag : newGroups) {
size += eag.getAddresses().size();
}
this.size = size;
}

/**
* Returns false if the needle was not found and the current index was left unchanged.
*/
public boolean seekTo(SocketAddress needle) {
for (int i = 0; i < addressGroups.size(); i++) {
EquivalentAddressGroup group = addressGroups.get(i);
int j = group.getAddresses().indexOf(needle);
if (j == -1) {
continue;
checkNotNull(needle, "needle");
for (int i = 0; i < orderedAddresses.size(); i++) {
if (orderedAddresses.get(i).address.equals(needle)) {
this.activeElement = i;
return true;
}
this.groupIndex = i;
this.addressIndex = j;
return true;
}
return false;
}

public int size() {
return size;
return orderedAddresses.size();
}

private List<UnwrappedEag> updateGroupsNonHE(List<EquivalentAddressGroup> newGroups) {
List<UnwrappedEag> entries = new ArrayList<>();
for (int g = 0; g < newGroups.size(); g++) {
EquivalentAddressGroup eag = newGroups.get(g);
for (int a = 0; a < eag.getAddresses().size(); a++) {
SocketAddress addr = eag.getAddresses().get(a);
entries.add(new UnwrappedEag(eag.getAttributes(), addr));
}
}

return entries;
}

private List<UnwrappedEag> updateGroupsHE(List<EquivalentAddressGroup> newGroups) {
Boolean firstIsV6 = null;
List<UnwrappedEag> v4Entries = new ArrayList<>();
List<UnwrappedEag> v6Entries = new ArrayList<>();
for (int g = 0; g < newGroups.size(); g++) {
EquivalentAddressGroup eag = newGroups.get(g);
for (int a = 0; a < eag.getAddresses().size(); a++) {
SocketAddress addr = eag.getAddresses().get(a);
boolean isIpV4 = addr instanceof InetSocketAddress
&& ((InetSocketAddress) addr).getAddress() instanceof Inet4Address;
if (isIpV4) {
if (firstIsV6 == null) {
firstIsV6 = false;
}
v4Entries.add(new UnwrappedEag(eag.getAttributes(), addr));
} else {
if (firstIsV6 == null) {
firstIsV6 = true;
}
v6Entries.add(new UnwrappedEag(eag.getAttributes(), addr));
}
}
}

return firstIsV6 != null && firstIsV6
? interleave(v6Entries, v4Entries)
: interleave(v4Entries, v6Entries);
}

private List<UnwrappedEag> interleave(List<UnwrappedEag> firstFamily,
List<UnwrappedEag> secondFamily) {
if (firstFamily.isEmpty()) {
return secondFamily;
}
if (secondFamily.isEmpty()) {
return firstFamily;
}

List<UnwrappedEag> result = new ArrayList<>(firstFamily.size() + secondFamily.size());
for (int i = 0; i < Math.max(firstFamily.size(), secondFamily.size()); i++) {
if (i < firstFamily.size()) {
result.add(firstFamily.get(i));
}
if (i < secondFamily.size()) {
result.add(secondFamily.get(i));
}
}
return result;
}

private static final class UnwrappedEag {
private final Attributes attributes;
private final SocketAddress address;

public UnwrappedEag(Attributes attributes, SocketAddress address) {
this.attributes = attributes;
this.address = address;
}

private EquivalentAddressGroup asEag() {
return new EquivalentAddressGroup(address, attributes);
}
}
}

@VisibleForTesting
int getGroupIndex() {
return addressIndex.groupIndex;
int getIndexLocation() {
return addressIndex.activeElement;
}

@VisibleForTesting
@@ -778,4 +850,5 @@ public PickFirstLeafLoadBalancerConfig(@Nullable Boolean shuffleAddressList) {
this.randomSeed = randomSeed;
}
}

}
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import static org.mockito.AdditionalAnswers.delegatesTo;
import static org.mockito.ArgumentMatchers.any;
@@ -67,6 +68,7 @@
import io.grpc.Status.Code;
import io.grpc.SynchronizationContext;
import io.grpc.internal.PickFirstLeafLoadBalancer.PickFirstLeafLoadBalancerConfig;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
@@ -2618,7 +2620,7 @@ public void serialized_retries_two_passes() {
forwardTimeByBackoffDelay(); // should trigger retry again
for (int i = 0; i < subchannels.length; i++) {
inOrder.verify(subchannels[i]).requestConnection();
assertEquals(i, loadBalancer.getGroupIndex());
assertEquals(i, loadBalancer.getIndexLocation());
listeners[i].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); // cascade
}
}
@@ -2637,7 +2639,7 @@ public void index_looping() {
PickFirstLeafLoadBalancer.Index index = new PickFirstLeafLoadBalancer.Index(Arrays.asList(
new EquivalentAddressGroup(Arrays.asList(addr1, addr2), attr1),
new EquivalentAddressGroup(Arrays.asList(addr3), attr2),
new EquivalentAddressGroup(Arrays.asList(addr4, addr5), attr3)));
new EquivalentAddressGroup(Arrays.asList(addr4, addr5), attr3)), enableHappyEyeballs);
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr1);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attr1);
assertThat(index.isAtBeginning()).isTrue();
@@ -2696,7 +2698,7 @@ public void index_updateGroups_resets() {
SocketAddress addr3 = new FakeSocketAddress("addr3");
PickFirstLeafLoadBalancer.Index index = new PickFirstLeafLoadBalancer.Index(Arrays.asList(
new EquivalentAddressGroup(Arrays.asList(addr1)),
new EquivalentAddressGroup(Arrays.asList(addr2, addr3))));
new EquivalentAddressGroup(Arrays.asList(addr2, addr3))), enableHappyEyeballs);
index.increment();
index.increment();
// We want to make sure both groupIndex and addressIndex are reset
@@ -2713,7 +2715,7 @@ public void index_seekTo() {
SocketAddress addr3 = new FakeSocketAddress("addr3");
PickFirstLeafLoadBalancer.Index index = new PickFirstLeafLoadBalancer.Index(Arrays.asList(
new EquivalentAddressGroup(Arrays.asList(addr1, addr2)),
new EquivalentAddressGroup(Arrays.asList(addr3))));
new EquivalentAddressGroup(Arrays.asList(addr3))), enableHappyEyeballs);
assertThat(index.seekTo(addr3)).isTrue();
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr3);
assertThat(index.seekTo(addr1)).isTrue();
@@ -2725,6 +2727,83 @@ public void index_seekTo() {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr2);
}

@Test
public void index_interleaving() {
InetSocketAddress addr1_6 = new InetSocketAddress("f38:1:1", 1234);
InetSocketAddress addr1_4 = new InetSocketAddress("10.1.1.1", 1234);
InetSocketAddress addr2_4 = new InetSocketAddress("10.1.1.2", 1234);
InetSocketAddress addr3_4 = new InetSocketAddress("10.1.1.3", 1234);
InetSocketAddress addr4_4 = new InetSocketAddress("10.1.1.4", 1234);
InetSocketAddress addr4_6 = new InetSocketAddress("f38:1:4", 1234);

Attributes attrs1 = Attributes.newBuilder().build();
Attributes attrs2 = Attributes.newBuilder().build();
Attributes attrs3 = Attributes.newBuilder().build();
Attributes attrs4 = Attributes.newBuilder().build();

PickFirstLeafLoadBalancer.Index index = new PickFirstLeafLoadBalancer.Index(Arrays.asList(
new EquivalentAddressGroup(Arrays.asList(addr1_4, addr1_6), attrs1),
new EquivalentAddressGroup(Arrays.asList(addr2_4), attrs2),
new EquivalentAddressGroup(Arrays.asList(addr3_4), attrs3),
new EquivalentAddressGroup(Arrays.asList(addr4_4, addr4_6), attrs4)), enableHappyEyeballs);

assertThat(index.getCurrentAddress()).isSameInstanceAs(addr1_4);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attrs1);
assertThat(index.isAtBeginning()).isTrue();

index.increment();
assertThat(index.isValid()).isTrue();
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr1_6);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attrs1);
assertThat(index.isAtBeginning()).isFalse();

index.increment();
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr2_4);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attrs2);

index.increment();
if (enableHappyEyeballs) {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr4_6);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attrs4);
} else {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr3_4);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attrs3);
}

index.increment();
if (enableHappyEyeballs) {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr3_4);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attrs3);
} else {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr4_4);
assertThat(index.getCurrentEagAttributes()).isSameInstanceAs(attrs4);
}

// Move to last entry
assertThat(index.increment()).isTrue();
assertThat(index.isValid()).isTrue();
if (enableHappyEyeballs) {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr4_4);
} else {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr4_6);
}

// Move off of the end
assertThat(index.increment()).isFalse();
assertThat(index.isValid()).isFalse();
assertThrows(IllegalStateException.class, index::getCurrentAddress);

// Reset
index.reset();
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr1_4);
assertThat(index.isAtBeginning()).isTrue();
assertThat(index.isValid()).isTrue();

// Seek to an address
assertThat(index.seekTo(addr4_4)).isTrue();
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr4_4);
}

private static class FakeSocketAddress extends SocketAddress {
final String name;