@marcel91200 / caddy Mirror · read-only / commits / 88616e86
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 11, 14:45 UTC

api: Add all in-flight requests /reverse_proxy/upstreams (Fixes #7277) (#7517)

This refactors the initial approach in PR #7281, replacing the UsagePool with a dedicated package-level sync.Map and atomic.Int64 to track in-flight requests without global lock contention. It also introduces a lookup map in the admin API to fix a potential O(n^2) iteration over upstreams, ensuring that draining upstreams are correctly exposed across config reloads without leaking memory. Co-authored-by: Y.Horie <u5.horie@gmail.com> reverseproxy: optimize in-flight tracking and admin API - Replaced sync.RWMutex with sync.Map and atomic.Int64 to avoid lock contention under high RPS. - Introduced a lookup map in the admin API to fix a potential O(n^2) iteration over upstreams.

Paulo Henrique committed Mar 3, 2026 at 19:14 UTC 88616e86e6e656738426bb86b4a42dcc20a59f77
2 files changed +49 -2
modules/caddyhttp/reverseproxy/admin.go
+14 -1
@@ -73,6 +73,7 @@ func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.Request) er
73
74 // Collect the results to respond with
75 results := []upstreamStatus{}
76 + knownHosts := make(map[string]struct{})
77
78 // Iterate over the upstream pool (needs to be fast)
79 var rangeErr error
@@ -95,6 +96,8 @@ func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.Request) er
96 return false
97 }
98
99 + knownHosts[address] = struct{}{}
100 +
101 results = append(results, upstreamStatus{
102 Address: address,
103 NumRequests: upstream.NumRequests(),
@@ -103,7 +106,17 @@ func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.Request) er
106 return true
107 })
108
106 - // If an error happened during the range, return it
109 + currentInFlight := getInFlightRequests()
110 + for address, count := range currentInFlight {
111 + if _, exists := knownHosts[address]; !exists && count > 0 {
112 + results = append(results, upstreamStatus{
113 + Address: address,
114 + NumRequests: int(count),
115 + Fails: 0,
116 + })
117 + }
118 + }
119 +
120 if rangeErr != nil {
121 return rangeErr
122 }
modules/caddyhttp/reverseproxy/reverseproxy.go
+35 -1
@@ -32,6 +32,7 @@ import (
32 "strconv"
33 "strings"
34 "sync"
35 + "sync/atomic"
36 "time"
37
38 "go.uber.org/zap"
@@ -46,6 +47,31 @@ import (
47 "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
48 )
49
50 +// inFlightRequests uses sync.Map with atomic.Int64 for lock-free updates on the hot path
51 +var inFlightRequests sync.Map
52 +
53 +func incInFlightRequest(address string) {
54 + v, _ := inFlightRequests.LoadOrStore(address, new(atomic.Int64))
55 + v.(*atomic.Int64).Add(1)
56 +}
57 +
58 +func decInFlightRequest(address string) {
59 + if v, ok := inFlightRequests.Load(address); ok {
60 + if v.(*atomic.Int64).Add(-1) <= 0 {
61 + inFlightRequests.Delete(address)
62 + }
63 + }
64 +}
65 +
66 +func getInFlightRequests() map[string]int64 {
67 + copyMap := make(map[string]int64)
68 + inFlightRequests.Range(func(key, value any) bool {
69 + copyMap[key.(string)] = value.(*atomic.Int64).Load()
70 + return true
71 + })
72 + return copyMap
73 +}
74 +
75 func init() {
76 caddy.RegisterModule(Handler{})
77 }
@@ -904,8 +930,16 @@ func (h Handler) addForwardedHeaders(req *http.Request) error {
930 // Go standard library which was used as the foundation.)
931 func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origReq *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error {
932 _ = di.Upstream.Host.countRequest(1)
933 +
934 + // Increment the in-flight request count
935 + incInFlightRequest(di.Address)
936 +
937 //nolint:errcheck
908 - defer di.Upstream.Host.countRequest(-1)
938 + defer func() {
939 + di.Upstream.Host.countRequest(-1)
940 + // Decrement the in-flight request count
941 + decInFlightRequest(di.Address)
942 + }()
943
944 // point the request to this upstream
945 h.directRequest(req, di)