@@ -23,6 +23,7 @@ import (
23
23
"encoding/json"
24
24
"errors"
25
25
"fmt"
26
+ "sort"
26
27
"sync"
27
28
28
29
"google.golang.org/grpc/balancer"
@@ -240,9 +241,24 @@ func (b *ringhashBalancer) updatePickerLocked() {
240
241
// 2. There are four endpoints in the following states: TF, TF,
241
242
// CONNECTING, and IDLE. If the CONNECTING endpoint is removed, the
242
243
// new states become: TF, TF, IDLE.
244
+
245
+ // After calling `ExitIdle` on a child balancer, the child will send a
246
+ // picker update asynchronously. A race condition may occur if another
247
+ // picker update from endpointsharding arrives before the child's
248
+ // picker update. The received picker may trigger a re-execution of the
249
+ // loop below to find an idle child. Since map iteration order is
250
+ // non-deterministic, the list of `endpointState`s must be sorted to
251
+ // ensure `ExitIdle` is called on the same child, preventing unnecessary
252
+ // connections.
253
+ var endpointStates = make ([]* endpointState , b .endpointStates .Len ())
254
+ for i , val := range b .endpointStates .Values () {
255
+ endpointStates [i ] = val .(* endpointState )
256
+ }
257
+ sort .Slice (endpointStates , func (i , j int ) bool {
258
+ return endpointStates [i ].firstAddr < endpointStates [j ].firstAddr
259
+ })
243
260
var idleBalancer balancer.ExitIdler
244
- for _ , val := range b .endpointStates .Values () {
245
- es := val .(* endpointState )
261
+ for _ , es := range endpointStates {
246
262
connState := es .state .ConnectivityState
247
263
if connState == connectivity .Connecting {
248
264
idleBalancer = nil
0 commit comments