Skip to content

Commit d55cb28

Browse files
committed
balancer/ringhash: address review comments
Check exact 0.5 distribution in overflow test, add a test for a sum that wraps to a non-zero value, and expand the accumulator comment to cover both wrap outcomes.
1 parent 4024542 commit d55cb28

2 files changed

Lines changed: 43 additions & 4 deletions

File tree

balancer/ringhash/ring.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,11 @@ func newRing(endpoints *resolver.EndpointMap[*endpointState], minRingSize, maxRi
139139
func normalizeWeights(endpoints *resolver.EndpointMap[*endpointState]) ([]endpointInfo, float64) {
140140
// Accumulate in a uint64 so the sum cannot wrap: each weight is a uint32
141141
// and control-plane supplied localities/endpoints can make the total exceed
142-
// math.MaxUint32. A wrapped uint32 sum can land on zero, which would turn the
143-
// division below into +Inf and make newRing spin forever building the ring.
142+
// math.MaxUint32. A wrapped sum is smaller than the real one, so the
143+
// normalized weights come out greater than 1 and the ring grows past its
144+
// configured max size. For example, a wrapped uint32 sum can land on zero,
145+
// which would turn the division below into +Inf and make newRing spin
146+
// forever building the ring.
144147
var weightSum uint64
145148
// Since attributes are explicitly ignored in the EndpointMap key, we need
146149
// to iterate over the values to get the weights.

balancer/ringhash/ring_test.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,44 @@ func (s) TestRingNewWeightSumOverflow(t *testing.T) {
102102
}
103103
}
104104
got := float64(count) / float64(len(r.items))
105-
if !equalApproximately(got, 0.5) {
106-
t.Fatalf("endpoint %q occupies %v of the ring, want ~0.5", hashKey(e), got)
105+
if got != 0.5 {
106+
t.Fatalf("endpoint %q occupies %v of the ring, want 0.5", hashKey(e), got)
107+
}
108+
}
109+
}
110+
111+
// TestRingNewWeightSumOverflowToNonZero checks that endpoint weights whose sum
112+
// exceeds math.MaxUint32 and wraps to a non-zero value still produce a ring
113+
// within the configured size bounds. A wrapped non-zero sum used to make
114+
// normalizeWeights return weights greater than 1, growing the ring past
115+
// maxRingSize.
116+
func (s) TestRingNewWeightSumOverflowToNonZero(t *testing.T) {
117+
endpoints := []resolver.Endpoint{
118+
testEndpoint("a", 1<<31),
119+
testEndpoint("b", 1<<31),
120+
testEndpoint("c", 1<<30), // sum is 2^32 + 2^30, wraps a uint32 to 2^30.
121+
}
122+
m := resolver.NewEndpointMap[*endpointState]()
123+
m.Set(endpoints[0], &endpointState{hashKey: "a", weight: 1 << 31})
124+
m.Set(endpoints[1], &endpointState{hashKey: "b", weight: 1 << 31})
125+
m.Set(endpoints[2], &endpointState{hashKey: "c", weight: 1 << 30})
126+
127+
const min, max uint64 = 1024, 4096
128+
r := newRing(m, min, max, nil)
129+
if got := uint64(len(r.items)); got < min || got > max {
130+
t.Fatalf("newRing built a ring of size %d, want within [%d, %d]", got, min, max)
131+
}
132+
wantFractions := map[string]float64{"a": 0.4, "b": 0.4, "c": 0.2}
133+
for _, e := range endpoints {
134+
var count int
135+
for _, ii := range r.items {
136+
if ii.hashKey == hashKey(e) {
137+
count++
138+
}
139+
}
140+
got := float64(count) / float64(len(r.items))
141+
if want := wantFractions[hashKey(e)]; !equalApproximately(got, want) {
142+
t.Fatalf("endpoint %q occupies %v of the ring, want ~%v", hashKey(e), got, want)
107143
}
108144
}
109145
}

0 commit comments

Comments
 (0)