@@ -23,6 +23,7 @@ import (
2323 "encoding/json"
2424 "errors"
2525 "fmt"
26+ "sort"
2627 "sync"
2728
2829 "google.golang.org/grpc/balancer"
@@ -240,9 +241,24 @@ func (b *ringhashBalancer) updatePickerLocked() {
240241 // 2. There are four endpoints in the following states: TF, TF,
241242 // CONNECTING, and IDLE. If the CONNECTING endpoint is removed, the
242243 // 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+ })
243260 var idleBalancer balancer.ExitIdler
244- for _ , val := range b .endpointStates .Values () {
245- es := val .(* endpointState )
261+ for _ , es := range endpointStates {
246262 connState := es .state .ConnectivityState
247263 if connState == connectivity .Connecting {
248264 idleBalancer = nil
0 commit comments