26 Commits
Author SHA1 Message Date
bbea66f2ec reverseproxy: reduce lock contention in dynamic SRV and A upstreams (#7949)
* reverseproxy: reduce lock contention in dynamic SRV and A upstreams (#7946)

The shared SRV and A/AAAA caches held a global write lock across the DNS
lookup, so one slow or timing-out record blocked every unrelated
upstream source.

Lookups now run without the cache lock held, deduplicated per key with
singleflight. A per-key generation (stored on the cache entry, so it is
bounded by the cache cap) and a cache-wide epoch keep a lookup that was
in flight during ResetSRV/ResetAllSRV from writing its result back, and
are part of the singleflight key so callers after a reset do not join a
pre-reset lookup.

The shared lookup is detached from the request that started it and
bounded by its own 30s timeout; each caller still returns as soon as its
own context is done. Distinct lookups across both caches are capped by a
shared gate (64 running, 256 queued); beyond that they are shed, serving
a stale SRV entry if a grace period is set and one is cached.

SRV and A now share one cache implementation. AUpstreams also implements
CachingUpstreamSource (via new ResetA/ResetAllA), so retries purge its
cache entry like SRVUpstreams already did.

Rebased onto the move of this code into modules/internal/network (#7790,
#8252).

Fixes #7946

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* network: backdate cache entries in TestShedLookups instead of a 1ns refresh

On Windows two close time.Now() calls can report no elapsed time, so an
entry seeded with a 1ns refresh could still be fresh and skip the shed
path entirely.

* network: move the shared cache and lookup gate into cache.go

dynamicupstreams.go keeps the SRV/A API; the generic cache, its
singleflight/generation handling and the lookup gate are not specific to
either record type. No functional change.

* network: preserve cache reset and grace semantics

---------

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: steadytao <mail@steadytao.com>
2026-11-03 08:37:50 +00:00
tannevaledandZen Dodd f1bc314fbc reverseproxy: make the shared DNS discovery cache resolver-aware (#8252)
Co-authored-by: Zen Dodd <mail@steadytao.com>
2026-10-31 17:23:41 -04:00
tannevaledandFrancis Lavoie f2c20fb810 reverseproxy: deduplicate DNS SRV+A discovery into modules/internal/network (#7790)
* reverseproxy: extract DNS SRV+A discovery into a reusable package

Move the SRV and A/AAAA resolution + caching logic out of reverseproxy
into a new, transport-neutral package (dynamicupstreams) that returns
neutral targets instead of *reverseproxy.Upstream. reverseproxy's
SRVUpstreams and AUpstreams now delegate to it and build their own
upstreams from the targets; behavior is unchanged.

This lets other proxies (e.g. third-party layer4 proxies) reuse the same
DNS discovery + caching instead of copying it, reducing duplication and
maintenance burden. reverseproxy/upstreams.go shrinks substantially.

RFC for the de-duplication discussed in caddyserver/caddy-l4#429.

* test(dynamicupstreams): cover ResetAllSRV, filtered records, cache bound, concurrent refresh

Brings the package to 96.8% statement coverage. Adds:
- TestResetAllSRV: full-cache drop
- TestSRVFilteredRecords: LookupSRV partial-error semantics (usable
  records returned, error downgraded to a warning)
- TestSRVCacheBound / TestACacheBound: eviction keeps the cache bounded
  at maxCacheEntries
- TestSRVConcurrentRefreshDeduplicates / TestAConcurrentRefreshDeduplicates:
  two goroutines missing the read-lock fast path trigger only one lookup
  (verified under -race)

The grace-period and filtered-records logs now use a level-enabled logger
so the log-write branches execute. The only uncovered statements are the
two double-checked-locking re-check returns, which are reachable solely in
a narrow race window (the same untested idiom as reverseproxy's SRV/A).

* reverseproxy: move DNS SRV+A discovery into modules/internal/network

Per review feedback, the shared discovery code does not belong in a
top-level package: it is internal helper code, not part of Caddy's
runtime surface. Move it from the top-level dynamicupstreams package into
the existing modules/internal/network package and update reverse_proxy's
upstreams.go to consume it from there.

Being under modules/internal means third-party modules (e.g. layer4)
cannot import it yet; they keep their own copy until layer4 is part of
Caddy's standard distribution, at which point they can switch over.

* test(dynamicupstreams): correct the concurrent-refresh test's claim

The two concurrent-refresh tests documented themselves as covering the
write-lock re-check branch ("the second re-checks under the write lock"),
but they do not: while the first goroutine holds the write lock the second
cannot acquire the read lock either, so by the time it acquires srvMu the
cache is already populated and it is served by the read-lock fast path. The
50ms sleep was presented as letting the second goroutine "queue on the write
lock" but proved nothing.

Make the comments and failure messages honest: these tests assert
deduplication (two concurrent callers for one uncached key share a single
lookup) and, under -race, race-safety on the shared cache -- not a specific
locking branch. Ordering is enforced by the channels, so the sleep is removed.

No production code change.

* reverseproxy: preserve DNS discovery logging and grace behavior

Keep refresh and discovery logs in the DNS lookup path, retaining the original SRV field types. Preserve error suppression when an SRV lookup fails with grace enabled before any result has been cached.

Add regression tests for refresh-only logging and cold-cache grace handling.

AI-assisted-by: OpenAI Codex (GPT-6)

---------

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2026-10-12 10:37:55 +00:00
Julien Voisin 22e976e5ae reverseproxy: preallocate upstream slices with known sizes (#7926)
Preallocate the sources slice in MultiUpstreams.Provision and the
netAddrs slice in UpstreamResolver.ParseAddresses now that their final
lengths are known, avoiding the intermediate append regrowth
allocations. For a 4-address resolver this reduces ParseAddresses from
11 to 9 allocs/op and 528 to 384 B/op; the benefit scales with the
number of sources/addresses.

                                 |  before  |            after            |
                                 |  sec/op  |  sec/op     vs base         |
UpstreamResolverParseAddresses-8 | 2.289µ   |1.946µ     ~ (p=0.165) noisy |

B/op:      528 → 384   -27.27%  (p=0.000)
allocs/op:  11 →   9   -18.18%  (p=0.000)

Adds BenchmarkUpstreamResolverParseAddresses as per policy.
2026-08-10 15:51:50 +10:00
Matt Holt 4d6945769d reverseproxy: Add ability to clear dynamic upstreams cache during retries (#7662)
* reverseproxy: Add ability to clear dynamic upstreams cache during retries

This is an optional interface for dynamic upstream modules to implement if they cache results.

TODO: More documentation; this is an experiment.

* Add some godoc

* Export interface; update godoc
2026-04-28 09:16:18 -06:00
WeidiDeng 47f3e8f8dc use math/rand/v2 instead of math/rand (#7413) 2026-02-11 09:15:51 -07:00
Matthew Holt 3bb22672f9 reverseproxy: Customizable dial network for SRV upstreams
By request of a sponsor
2026-02-02 11:25:51 -07:00
Mohammed Al Sahaf 25be2f26fc chore: ugh, lint fix... (#7275)
* chore: ugh, lint fix...

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* more lint fixes

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-09-26 03:14:48 -04:00
Kévin Dunglas f4bf4e0097 perf: use zap's Check() to prevent useless allocs (#6560)
* perf: use zap's Check() to prevent useless allocs

* fix

* fix

* fix

* fix

* restore previous replacer behavior

* fix linter
2024-09-13 11:16:37 -06:00
Aziz Rmadi 630c62b313 fixed bug in resolving ip version in dynamic upstreams (#6448) 2024-07-09 03:06:30 -04:00
Matt Holt 72ce78d9af reverseproxy: SRV dynamic upstream failover (#5832)
* Implement grace period, but probably needs sync

* Update cached freshness value

* D'oh, actually use the grace period

* Fix freshness math
2024-03-05 12:08:31 -07:00
Francis Lavoie a8586b05aa reverseproxy: Add logging for dynamic A upstreams (#5857) 2023-10-11 09:50:44 -06:00
Pascal Vorwerk 1e0dea59ef reverseproxy: fix nil pointer dereference in AUpstreams.GetUpstreams (#5811)
fix a nil pointer dereference in AUpstreams.GetUpstreams when AUpstreams.Versions is not set (fixes caddyserver#5809)

Signed-off-by: Pascal Vorwerk <info@fossores.de>
2023-09-10 19:08:02 -04:00
Matt Holt 936ee918ee reverseproxy: Always return new upstreams (fix #5736) (#5752)
* reverseproxy: Always return new upstreams (fix #5736)

* Fix healthcheck logger race
2023-08-17 11:33:40 -06:00
Jacob Gadikian d6f86cccf5 ci: use gci linter (#5708)
* use gofmput to format code

* use gci to format imports

* reconfigure gci

* linter autofixes

* rearrange imports a little

* export GOOS=windows golangci-lint run ./... --fix
2023-08-14 09:41:15 -06:00
Omar Ramadan d7d16360d4 reverseproxy: Export ipVersions type (#5648)
allows AUpstreams to be instantiated externally
2023-07-25 12:50:21 -06:00
Francis Lavoieanditsxaos 3f20a7c9f3 acmeserver: Configurable resolvers, fix smallstep deprecations (#5500)
* acmeserver: Configurable `resolvers`, fix smallstep deprecations

* Improve default net/port

* Update proxy resolvers parsing to use the new function

* Update listeners.go

Co-authored-by: itsxaos <33079230+itsxaos@users.noreply.github.com>

---------

Co-authored-by: itsxaos <33079230+itsxaos@users.noreply.github.com>
2023-05-03 17:07:22 +00:00
Emily LangeandFrancis Lavoie 941eae5f61 reverseproxy: allow specifying ip version for dynamic a upstream (#5401)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2023-02-27 17:23:09 +00:00
Matthew Holt e43b6d8178 core: Variadic Context.Logger(); soft deprecation
Ideally I'd just remove the parameter to caddy.Context.Logger(), but
this would break most Caddy plugins.

Instead, I'm making it variadic and marking it as partially deprecated.
In the future, I might completely remove the parameter once most
plugins have updated.
2022-09-16 16:55:36 -06:00
Abirdcfly e1801fdb19 Remove duplicate words in comments (#4986) 2022-08-27 14:39:26 -06:00
Matthew Holt 5fb5b81439 reverseproxy: Multiple dynamic upstreams
This allows users to, for example, get upstreams from multiple SRV
endpoints in order (such as primary and secondary clusters).

Also, gofmt went to town on the comments, sigh
2022-08-25 21:42:48 -06:00
Francis Lavoie e7fbee8c82 reverseproxy: Permit resolver addresses to not specify a port (#4760)
Context: https://caddy.community/t/caddy-2-5-dynamic-upstreams-and-consul-srv-dns/15839

I realized it probably makes sense to allow `:53` to be omitted, since it's the default port for DNS.
2022-05-04 12:40:39 -06:00
Matthew Holt b8dbecb841 reverseproxy: Include port in A upstreams cache
Should fix #4659
2022-03-24 10:44:36 -06:00
Matthew Holt 2196c92c0e reverseproxy: Don't clear name in SRV upstreams
Fix for dc4d147388
2022-03-21 08:33:24 -06:00
Matthew Holt dc4d147388 reverseproxy: Expand SRV/A addrs for cache key
Hopefully fix #4645
2022-03-18 13:42:29 -06:00
Matt HoltandFrancis Lavoie ab0455922a reverseproxy: Dynamic upstreams (with SRV and A/AAAA support) (#4470)
* reverseproxy: Begin refactor to enable dynamic upstreams

Streamed here: https://www.youtube.com/watch?v=hj7yzXb11jU

* Implement SRV and A/AAA upstream sources

Also get upstreams at every retry loop iteration instead of just once
before the loop. See #4442.

* Minor tweaks from review

* Limit size of upstreams caches

* Add doc notes deprecating LookupSRV

* Provision dynamic upstreams

Still WIP, preparing to preserve health checker functionality

* Rejigger health checks

Move active health check results into handler-specific Upstreams.

Improve documentation regarding health checks and upstreams.

* Deprecation notice

* Add Caddyfile support, use `caddy.Duration`

* Interface guards

* Implement custom resolvers, add resolvers to http transport Caddyfile

* SRV: fix Caddyfile `name` inline arg, remove proto condition

* Use pointer receiver

* Add debug logs

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2022-03-06 17:43:39 -07:00