Repository: caddyserver/caddy
File: modules/caddyhttp/reverseproxy/reverseproxy.go (Go)

Code before:
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
)

var (
	inFlightRequests   = make(map[string]uint)
	inFlightRequestsMu sync.RWMutex
)

func incInFlightRequest(address string) {
	inFlightRequestsMu.Lock()
	inFlightRequests[address]++
	inFlightRequestsMu.Unlock()
}

func decInFlightRequest(address string) {
	inFlightRequestsMu.Lock()
	defer inFlightRequestsMu.Unlock()

	if inFlightRequests[address] > 0 {
		inFlightRequests[address]--
	}
	if inFlightRequests[address] == 0 {
		delete(inFlightRequests, address)
	}
}

func getInFlightRequests() map[string]uint {
	inFlightRequestsMu.RLock()
	defer inFlightRequestsMu.RUnlock()

	copyMap := make(map[string]uint, len(inFlightRequests))
	for k, v := range inFlightRequests {
		copyMap[k] = v
	}
	return copyMap
}

func init() {
	caddy.RegisterModule(Handler{})
}

// Handler implements a highly configurable and production-ready reverse proxy.
//
// Upon proxying, this module sets the following placeholders (which can be used
// both within and after this handler; for example, in response headers):
//
// Placeholder | Description
// ------------|-------------
// `{http.reverse_proxy.upstream.address}` | The full address to the upstream as given in the config
// `{http.reverse_proxy.upstream.hostport}` | The host:port of the upstream
// `{http.reverse_proxy.upstream.host}` | The host of the upstream

Review comment (suggestion):
Currently, all in-flight updates are protected by a single RWMutex, which serializes every increment/decrement across all upstreams.

Since this code runs on every request, this may introduce contention under high RPS.

We could consider using sync.Map with atomic.Uint64 per address instead. This would:

Avoid global lock contention

Keep counters independent per upstream

Preserve correctness across config reloads

Example approach:
```golang
var inFlightRequests sync.Map // map[string]*atomic.Uint64

func inc(address string) {
    v, _ := inFlightRequests.LoadOrStore(address, new(atomic.Uint64))
    v.(*atomic.Uint64).Add(1)
}
```
This would make the counter lock-free for the common path.

Code after:
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
)

// inFlightRequests uses sync.Map with atomic.Int64 for lock-free updates on the hot path
var inFlightRequests sync.Map

func incInFlightRequest(address string) {
	v, _ := inFlightRequests.LoadOrStore(address, new(atomic.Int64))
	v.(*atomic.Int64).Add(1)
}

func decInFlightRequest(address string) {
	if v, ok := inFlightRequests.Load(address); ok {
		if v.(*atomic.Int64).Add(-1) <= 0 {
			inFlightRequests.Delete(address)
		}
	}
}

func getInFlightRequests() map[string]int64 {
	copyMap := make(map[string]int64)
	inFlightRequests.Range(func(key, value any) bool {
		copyMap[key.(string)] = value.(*atomic.Int64).Load()
		return true
	})
	return copyMap
}

func init() {
	caddy.RegisterModule(Handler{})
}

// Handler implements a highly configurable and production-ready reverse proxy.
//
// Upon proxying, this module sets the following placeholders (which can be used
// both within and after this handler; for example, in response headers):
//
// Placeholder | Description
// ------------|-------------
// `{http.reverse_proxy.upstream.address}` | The full address to the upstream as given in the config
// `{http.reverse_proxy.upstream.hostport}` | The host:port of the upstream
// `{http.reverse_proxy.upstream.host}` | The host of the upstream
// `{http.reverse_proxy.upstream.port}` | The port of the upstream
// `{http.reverse_proxy.upstream.requests}` | The approximate current number of requests to the upstream
// `{http.reverse_proxy.upstream.max_requests}` | The maximum approximate number of requests allowed to the upstream
// `{http.reverse_proxy.upstream.fails}` | The number of recent failed requests to the upstream
// `{http.reverse_proxy.upstream.latency}` | How long it took the proxy upstream to write the response header.
// `{http.reverse_proxy.upstream.latency_ms}` | Same as 'latency', but in milliseconds.
// `{http.reverse_proxy.upstream.duration}` | Time spent proxying to the upstream, including writing response body to client.
// `{http.reverse_proxy.upstream.duration_ms}` | Same as 'upstream.duration', but in milliseconds.