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
+}