Skip to content

Commit 9a1d8d8

Browse files
authored
pickfirstleaf: fix bug in address de-duplication (#8611)
Due to a bug in the new pickfirst balancer, it wasn't de-duplicating addresses in the resolver update. The only user visible impact of this seems to be less frequent picker updates after the first pass in happy eyeballs and incorrect interleaving of IPv4/IPv6 addresses during the first happy eyeballs pass. RELEASE NOTES: * balancer/pickfirst: Fix a bug where duplicate addresses were not being ignored as intended.
1 parent 8a396e2 commit 9a1d8d8

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

balancer/pickfirst/pickfirstleaf/pickfirstleaf.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,13 +381,14 @@ func (b *pickfirstBalancer) closeSubConnsLocked() {
381381

382382
// deDupAddresses ensures that each address appears only once in the slice.
383383
func deDupAddresses(addrs []resolver.Address) []resolver.Address {
384-
seenAddrs := resolver.NewAddressMapV2[*scData]()
384+
seenAddrs := resolver.NewAddressMapV2[bool]()
385385
retAddrs := []resolver.Address{}
386386

387387
for _, addr := range addrs {
388388
if _, ok := seenAddrs.Get(addr); ok {
389389
continue
390390
}
391+
seenAddrs.Set(addr, true)
391392
retAddrs = append(retAddrs, addr)
392393
}
393394
return retAddrs

balancer/pickfirst/pickfirstleaf/pickfirstleaf_ext_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,7 @@ func (s) TestPickFirstLeaf_InterleavingIPv6Preffered(t *testing.T) {
11521152
ResolverState: resolver.State{
11531153
Endpoints: []resolver.Endpoint{
11541154
{Addresses: []resolver.Address{{Addr: "[0001:0001:0001:0001:0001:0001:0001:0001]:8080"}}},
1155+
{Addresses: []resolver.Address{{Addr: "[0001:0001:0001:0001:0001:0001:0001:0001]:8080"}}}, // duplicate, should be ignored.
11551156
{Addresses: []resolver.Address{{Addr: "1.1.1.1:1111"}}},
11561157
{Addresses: []resolver.Address{{Addr: "2.2.2.2:2"}}},
11571158
{Addresses: []resolver.Address{{Addr: "3.3.3.3:3"}}},

balancer/pickfirst/pickfirstleaf/pickfirstleaf_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ func (s) TestPickFirstLeaf_TFPickerUpdate(t *testing.T) {
201201
ResolverState: resolver.State{
202202
Endpoints: []resolver.Endpoint{
203203
{Addresses: []resolver.Address{{Addr: "1.1.1.1:1"}}},
204+
{Addresses: []resolver.Address{{Addr: "1.1.1.1:1"}}}, // duplicate, should be ignored.
204205
{Addresses: []resolver.Address{{Addr: "2.2.2.2:2"}}},
206+
{Addresses: []resolver.Address{{Addr: "1.1.1.1:1"}}}, // duplicate, should be ignored.
205207
},
206208
},
207209
}
@@ -213,14 +215,35 @@ func (s) TestPickFirstLeaf_TFPickerUpdate(t *testing.T) {
213215
// once.
214216
tfErr := fmt.Errorf("test err: connection refused")
215217
sc1 := <-cc.NewSubConnCh
218+
select {
219+
case <-sc1.ConnectCh:
220+
case <-ctx.Done():
221+
t.Fatal("Context timed out waiting for Connect() to be called on sc1.")
222+
}
216223
sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting})
217224
sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure, ConnectionError: tfErr})
218225

226+
// Move the subconn back to IDLE, it should not be re-connected until the
227+
// first pass is complete.
228+
shortCtx, shortCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
229+
defer shortCancel()
230+
sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Idle})
231+
select {
232+
case <-sc1.ConnectCh:
233+
t.Fatal("Connect() unexpectedly called on sc1.")
234+
case <-shortCtx.Done():
235+
}
236+
219237
if err := cc.WaitForPickerWithErr(ctx, balancer.ErrNoSubConnAvailable); err != nil {
220238
t.Fatalf("cc.WaitForPickerWithErr(%v) returned error: %v", balancer.ErrNoSubConnAvailable, err)
221239
}
222240

223241
sc2 := <-cc.NewSubConnCh
242+
select {
243+
case <-sc2.ConnectCh:
244+
case <-ctx.Done():
245+
t.Fatal("Context timed out waiting for Connect() to be called on sc2.")
246+
}
224247
sc2.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting})
225248
sc2.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure, ConnectionError: tfErr})
226249

@@ -230,6 +253,29 @@ func (s) TestPickFirstLeaf_TFPickerUpdate(t *testing.T) {
230253

231254
// Subsequent TRANSIENT_FAILUREs should be reported only after seeing "# of SubConns"
232255
// TRANSIENT_FAILUREs.
256+
257+
// Both the subconns should be connected in parallel.
258+
select {
259+
case <-sc1.ConnectCh:
260+
case <-ctx.Done():
261+
t.Fatal("Context timed out waiting for Connect() to be called on sc1.")
262+
}
263+
264+
shortCtx, shortCancel = context.WithTimeout(ctx, defaultTestShortTimeout)
265+
defer shortCancel()
266+
select {
267+
case <-sc2.ConnectCh:
268+
t.Fatal("Connect() called on sc2 before it completed backing-off.")
269+
case <-shortCtx.Done():
270+
}
271+
272+
sc2.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Idle})
273+
select {
274+
case <-sc2.ConnectCh:
275+
case <-ctx.Done():
276+
t.Fatal("Context timed out waiting for Connect() to be called on sc2.")
277+
}
278+
233279
newTfErr := fmt.Errorf("test err: unreachable")
234280
sc2.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure, ConnectionError: newTfErr})
235281
select {

0 commit comments

Comments
 (0)