@marcel91200 / caddy Mirror · read-only / commits / e3271947
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 10, 23:45 UTC

reverseproxy: preserve weights among available upstreams (#8380)

zjubiology committed Nov 8, 2026 at 19:32 UTC e3271947ee053b0c43df2fad88a36728482bc3ff
2 files changed +33 -35
modules/caddyhttp/reverseproxy/selectionpolicies.go
+18 -33
@@ -142,46 +142,31 @@ func (r *WeightedRoundRobinSelection) Validate() error {
142
143 // Select returns an available host, if any.
144 func (r *WeightedRoundRobinSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream {
145 - if len(pool) == 0 {
146 - return nil
147 - }
148 - if len(r.Weights) < 2 {
149 - if !pool[0].Available() {
150 - return nil
151 - }
152 - return pool[0]
153 - }
154 - var index, totalWeight int
155 - weights := make([]int, 0, len(r.Weights))
156 -
157 - for _, w := range r.Weights {
158 - if w > 0 {
159 - weights = append(weights, w)
160 - }
161 - }
162 - currentWeight := int(r.index.Add(1)) % r.totalWeight
163 - for i, weight := range weights {
164 - totalWeight += weight
165 - if currentWeight < totalWeight {
166 - index = i
167 - break
168 - }
145 + type weightedUpstream struct {
146 + upstream *Upstream
147 + weight int
148 }
170 -
171 - upstreams := make([]*Upstream, 0, len(weights))
149 + upstreams := make([]weightedUpstream, 0, len(pool))
150 + var totalWeight int
151 for i, upstream := range pool {
173 - if i >= len(r.Weights) || !upstream.Available() || r.Weights[i] == 0 {
152 + if i >= len(r.Weights) || r.Weights[i] <= 0 || !upstream.Available() {
153 continue
154 }
176 - upstreams = append(upstreams, upstream)
177 - if len(upstreams) == cap(upstreams) {
178 - break
179 - }
155 + upstreams = append(upstreams, weightedUpstream{upstream, r.Weights[i]})
156 + totalWeight += r.Weights[i]
157 }
181 - if len(upstreams) == 0 {
158 + if totalWeight == 0 {
159 return nil
160 }
184 - return upstreams[index%len(upstreams)]
161 +
162 + currentWeight := uint64(r.index.Add(1)) % uint64(totalWeight)
163 + for _, upstream := range upstreams {
164 + if currentWeight < uint64(upstream.weight) {
165 + return upstream.upstream
166 + }
167 + currentWeight -= uint64(upstream.weight)
168 + }
169 + return nil
170 }
171
172 // RandomChoiceSelection is a policy that selects
modules/caddyhttp/reverseproxy/selectionpolicies_test.go
+15 -2
@@ -126,8 +126,8 @@ func TestWeightedRoundRobinPolicy(t *testing.T) {
126 pool[1].countRequest(1)
127 pool[1].MaxRequests = 1
128 h = wrrPolicy.Select(pool, req, nil)
129 - if h != pool[2] {
130 - t.Error("Expected to skip full host.")
129 + if h != pool[0] {
130 + t.Error("Expected to use the weights of the available hosts.")
131 }
132 }
133
@@ -1008,3 +1008,16 @@ func TestCookieHashPolicyWithSecret(t *testing.T) {
1008 t.Error("Expected a new cookie to be set for a non-matching value")
1009 }
1010 }
1011 +
1012 +func TestWeightedRoundRobinPolicyAvailableWeights(t *testing.T) {
1013 + pool := testPool()
1014 + pool[1].setHealthy(false)
1015 + policy := WeightedRoundRobinSelection{Weights: []int{1, 8, 3}, totalWeight: 12}
1016 + counts := make(map[*Upstream]int)
1017 + for i := 0; i < 12; i++ {
1018 + counts[policy.Select(pool, nil, nil)]++
1019 + }
1020 + if counts[pool[0]] != 3 || counts[pool[2]] != 9 || len(counts) != 2 {
1021 + t.Fatalf("Expected a 1:3 distribution among available hosts, got %v", counts)
1022 + }
1023 +}