Skip to content

Commit 65bef5e

Browse files
LSRE-???: Address PR zalando#4033 code review comments — move goroutines to spec level, introduce Options struct
This commit addresses all open code review comments from PR #4033: 1. **Goroutines on spec level (szuecs)**: Move revalidationWorker and lruBytesScraper from per-instance (cacheFilter) to per-spec (cacheSpec) ownership. This prevents 20k+ goroutines for 10k routes and enables proper cleanup on route reloads. Each filter instance enqueues revalidation jobs with a doRevalFn closure that captures per-instance logic. 2. **Introduce cache.Options struct (szuecs)**: Replace 5 positional parameters with a single Options struct containing MaxBytes, ListenAddr, NetOpts, ValkeyRing, L1TTL, and optional Metrics. This enables future extension without signature changes. 3. **Inject metrics (szuecs)**: Make metrics.Metrics injectable via Options (defaults to metrics.Default if nil), removing hidden global dependency and enabling test-scoped assertions without side effects. 4. **Verify remaining comments**: - SetWithExpire error handling (net/valkey.go) already correct - Docs terminology fixed: pod→process, Storage→Cache, dropped "cross-pod" - Single shared valkeyRing already in place (refactoring merged) Files modified: - filters/cache/filter.go: Options struct, spec-level goroutines, doRevalFn closure - filters/cache/filter_test.go: Updated all NewCacheFilter calls, added cleanup - skipper.go: Updated instantiation to use cache.Options{} - docs/reference/filters.md: Terminology and structure fixes docs: document Valkey L2 storage, write-through L1, and cache metrics Signed-off-by: larry-dalmeida <hello@larrydalmeida.com> fix: address code review findings — vary sentinel invalidation, stub accuracy, dead code Signed-off-by: larry-dalmeida <hello@larrydalmeida.com> style: fix gofmt alignment of bgWg struct field comment Signed-off-by: larry-dalmeida <hello@larrydalmeida.com> style: group cache config fields into dedicated //cache section in config.go Signed-off-by: larry-dalmeida <hello@larrydalmeida.com> fix: add CacheL1TTL to defaultConfig and make Close() synchronous config_test.go: defaultConfig() was missing CacheL1TTL, so the expected config had 0s while ParseArgs produced the flag default of 1m0s. filters/cache/filter.go: Close() only closed the revalJobs and lruBytesDone channels without waiting for the background goroutines to finish. Under -race this caused a panic (close of closed channel) when a test's in-flight doRevalidate called f.fetch after the test's local channels had been torn down. Adding bgWg tracks both goroutines and Close() now blocks until both have exited. Signed-off-by: larry-dalmeida <hello@larrydalmeida.com> style: use strings.SplitSeq in stripHopByHop and parseVaryNames Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> refactor: move --cache-l1-ttl config field and flag to Valkey section Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: wire --cache-l1-ttl flag through Options to ValkeyStorage Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> fix: update stale counter assertion after L1-first Get, add l1_hit to godoc Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: L1-first reads with l1_hit counter in ValkeyStorage.Get Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: write-through L1 warming on successful Valkey Set Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> refactor: add l1TTL field to ValkeyStorage (write-through prep) Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> Fix registration of cache filter Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> refactor: ratelimit registry creation to pass ring clients such that we can reuse the valkey ring client in the cache filter creation. Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> cache: injectable metrics, trace spans, lru_bytes scraper Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> Promote log to warn Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> cache: promote storage Set/Delete error logs from Debug to Warn Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: tag cache_status, cache_key, cache_ttl_remaining_ms on trace span Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> test: reval_dropped and L1 fallback write verification Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> test: oversized LRU entry increments lru_oversized and is not stored Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> test: unsafe method + 4xx does not invalidate cached entry Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> test: must-revalidate forces coalesce when stale Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: log storage errors with per-site messages Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: add periodic lru_bytes gauge scrape every 10s Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> fix: address WP6 code review feedback Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: split valkey_fallback counter and add valkey_miss metric Replace the single valkey_fallback counter with three distinct counters for better observability: - valkey_miss — clean cache miss (key absent in Valkey) - valkey_get_fallback — Valkey error on Get; L1 consulted instead - valkey_set_fallback — Valkey error on Set; L1 written instead Inject metrics.Metrics into ValkeyStorage via NewValkeyStorage so tests can assert counter values without relying on the global metrics.Default singleton. Introduce a valkeyClient interface (Get/SetWithExpire/Expire) so unit tests can use an in-memory stub instead of a live Valkey/Docker connection. Two new tests — RecordsValkeyMiss and SplitFallbackCounters — exercise the counter logic with stubs. Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> fix(cache): add compile-time interface guards; assert valkey_get_fallback fires on fallback Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: add ValkeyStorage with L1 fallback for cache() filter Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: wire ValkeyStorage into NewCacheFilter (nil = in-memory only) Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> feat: wire Valkey ring into cache() filter when swarm Valkey is configured Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> doc: redirect location query examples (#4077) Feature: mtls filters (#4044) feature: Add support to opt-out from zone aware routing via Ingress/RouteGroup (#4049) Introduce the annotation `zalando.org/traffic-zone-aware: "false"` on Ingress and RouteGroup resources. When set, the resource receives all endpoints across all zones, bypassing zone filtering both at the dataclient layer (endpoint fetch) and at the routesrv layer (per-zone route serving). Usage: ``` metadata: annotations: zalando.org/traffic-zone-aware: "false" ``` Tasks 1. Define `zalando.org/traffic-zone-aware` annotation constant in the kubernetes package 2. Modify `GetEndpointSlicesByService` and `GetEndpointSlicesByTarget` in `clusterstate.go` to accept ing and rg context param respectively and skip zone filtering when value ing.disableZoneAwareness/rg.disableZoneAwareness is true 3. Add zoneAwarenessAnnotationFilter() helper that returns an annotate() filter when the zone-aware opt-out annotation is present 4. Modify Ingress v1 path rule conversion (`convertPathRuleV1`) and default backend conversion (`convertDefaultBackendV1`) to read the annotation, pass it to endpoint fetch and prepend the annotate() filter when annotation is set 5. Modify RouteGroup backend application (`applyServiceBackend`) to read the annotation, pass it to endpoint fetch and prepend the annotate() filter when annotation is set 6. Add isZoneAwarenessDisabled() in routesrv/polling.go to detect the opt-out by inspecting the route filter chain 7. Wire isZoneAwarenessDisabled() into getRouteForZone() to skip zone filtering for opted-out routes 8. Update makeRoute test helper to inject the annotate() filter when the opt-out flag is passed 9. Add integration test with two Ingresses in zone `eu-central-1a`: one opted out (expects all endpoints across zones), one default (expects only zone-local endpoints) 10. Add unit tests for `filterRoutesByZone` covering opt-out scenarios: both routes opted out, opt-out mixed with normal route, and opt-out with a below-threshold zone Tested the changes: The skipper-ingress with zone aware routing enabled was in Zone-A 1. Deployed ingress with 10 replicas, which means 10 endpoints are available ``` zkubectl -n gmapp1 get pods -L topology.kubernetes.io/zone NAME READY STATUS RESTARTS AGE ZONE app1-v8-5c5965f6d4-7h6zr 1/1 Running 0 69s eu-central-1a app1-v8-5c5965f6d4-7tnq4 1/1 Running 0 68s eu-central-1a app1-v8-5c5965f6d4-98mz7 1/1 Running 0 68s eu-central-1c app1-v8-5c5965f6d4-99n9p 1/1 Running 0 69s eu-central-1c app1-v8-5c5965f6d4-bvdst 1/1 Running 0 69s eu-central-1a app1-v8-5c5965f6d4-htn78 1/1 Running 0 68s eu-central-1b app1-v8-5c5965f6d4-q75b9 1/1 Running 0 69s eu-central-1b app1-v8-5c5965f6d4-wtgm5 1/1 Running 0 69s eu-central-1a app1-v8-5c5965f6d4-xhv9q 1/1 Running 0 69s eu-central-1c app1-v8-5c5965f6d4-zlz9x 1/1 Running 0 69s eu-central-1b ``` This meets the threshold to serve same zone endpoints, but since the ingress has the opt-out annotation, the route contains all the endpoints and not just zone filtered ``` kube_gmapp1__app1_v8_traffic_segment__gmapp1_pg9_test_zalan_do____app1_v8: Host(/^(gmapp1[.]pg9-test[.]zalan[.]do[.]?(:[0-9]+)?)$/) && TrafficSegment(0, 1) -> disableAccessLog(2, 3, 404, 429) -> fifo(2000, 20, "1s") -> annotate("zalando.org/traffic-zone-aware", "false") -> oauthTokeninfoValidate("{optOutAnnotations: [iam.zalando.org/public], optOutHosts: [\"^.*[.]ingress[.]cluster[.]local\"], unauthorizedResponse: \"Authentication required, see https://cloud.docs.zalando.net/howtos/authenticate-requests/#default-authentication\\n\"}") -> stateBagToTag("auth-user", "client.uid") -> <powerOfRandomNChoices, "http://[2a05:d024:90:1103:3e99::6]:8080", "http://[2a05:d024:90:1103:3e99::7]:8080", "http://[2a05:d024:90:1103:3e99::8]:8080", "http://[2a05:d024:90:1103:3e99::9]:8080", "http://[2a05:d024:90:1104:2396::a]:8080", "http://[2a05:d024:90:1104:2396::b]:8080", "http://[2a05:d024:90:1104:2396::c]:8080", "http://[2a05:d024:90:1105:4749::4]:8080", "http://[2a05:d024:90:1105:4749::6]:8080", "http://[2a05:d024:90:1105:4749::7]:8080">; ``` 2. Deployed a routegroup with 15 replicas, which means 15 endpoints are available ``` zkubectl -n gmapp2 get pods -L topology.kubernetes.io/zone NAME READY STATUS RESTARTS AGE ZONE app2-v2-c9694944-598mc 1/1 Running 0 7m6s eu-central-1c app2-v2-c9694944-6bcpm 1/1 Running 0 7m6s eu-central-1c app2-v2-c9694944-7jgpp 1/1 Running 0 7m7s eu-central-1b app2-v2-c9694944-88xzb 1/1 Running 0 7m7s eu-central-1a app2-v2-c9694944-8kl6q 1/1 Running 0 7m6s eu-central-1c app2-v2-c9694944-8wc97 1/1 Running 0 7m7s eu-central-1c app2-v2-c9694944-cv9g8 1/1 Running 0 7m7s eu-central-1c app2-v2-c9694944-dkh2h 1/1 Running 0 7m6s eu-central-1a app2-v2-c9694944-flnx9 1/1 Running 0 7m6s eu-central-1a app2-v2-c9694944-klk6r 1/1 Running 0 7m7s eu-central-1a app2-v2-c9694944-kwddv 1/1 Running 0 7m6s eu-central-1b app2-v2-c9694944-pjbtz 1/1 Running 0 7m6s eu-central-1a app2-v2-c9694944-q5xrh 1/1 Running 0 7m7s eu-central-1b app2-v2-c9694944-rs8hk 1/1 Running 0 7m7s eu-central-1a app2-v2-c9694944-t6crt 1/1 Running 0 7m6s eu-central-1b ``` This meets the threshold to serve same zone endpoints, but since the routegroup has the opt-out annotation, the route contains all the endpoints and not just zone filtered ``` kube_rg__gmapp2__app2_v2_traffic_segment__get__0_0: Path("/test") && Host(/^(app2[.]teapot[.]zalan[.]do[.]?(:[0-9]+)?)$/) && Method("GET") && TrafficSegment(0, 1) -> disableAccessLog(2, 3, 404, 429) -> fifo(2000, 20, "1s") -> annotate("zalando.org/traffic-zone-aware", "false") -> annotate("iam.zalando.org/public", "The application uses its own authentication logic, similar to using Partner IAM.") -> status(200) -> inlineContent("Hello from second backend!") -> oauthTokeninfoValidate("{optOutAnnotations: [iam.zalando.org/public], optOutHosts: [\"^.*[.]ingress[.]cluster[.]local\"], unauthorizedResponse: \"Authentication required, see https://cloud.docs.zalando.net/howtos/authenticate-requests/#default-authentication\\n\"}") -> stateBagToTag("auth-user", "client.uid") -> <powerOfRandomNChoices, "http://[2a05:d024:90:1103:3e99::10]:8080", "http://[2a05:d024:90:1103:3e99::11]:8080", "http://[2a05:d024:90:1103:3e99::12]:8080", "http://[2a05:d024:90:1103:3e99::13]:8080", "http://[2a05:d024:90:1103:3e99::14]:8080", "http://[2a05:d024:90:1103:3e99::f]:8080", "http://[2a05:d024:90:1104:2396::10]:8080", "http://[2a05:d024:90:1104:2396::d]:8080", "http://[2a05:d024:90:1104:2396::e]:8080", "http://[2a05:d024:90:1104:2396::f]:8080", "http://[2a05:d024:90:1105:4749::8]:8080", "http://[2a05:d024:90:1105:4749::9]:8080", "http://[2a05:d024:90:1105:4749::a]:8080", "http://[2a05:d024:90:1105:4749::b]:8080", "http://[2a05:d024:90:1105:4749::c]:8080">; ``` ref: https://github.com/zalando/skipper/issues/3971 --------- Signed-off-by: greeshma1196 <greeshma.mathew@gmail.com> fix: host metrics should be measured and stored correctly in case of HostAny() predicate (#4067) fix: host metrics should be measured and stored correctly in case of HostAny() predicate Before this fix we measured a route like the following in the bucket `"_unknownhost_"`: ``` r: HostAny("www.example.com") -> <shunt> ``` --------- Signed-off-by: Sandor Szuecs <sandor.szuecs@zalando.de> Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> build(deps): bump oss-fuzz-base/base-builder-go from `972dea5` to `72f8378` in /.clusterfuzzlite (#4068) Bumps oss-fuzz-base/base-builder-go from `972dea5` to `72f8378`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=oss-fuzz-base/base-builder-go&package-manager=docker&previous-version=972dea510bfa4539b7041fc7b1f5e3868fb70fa23ca0a58df96b420caaab8e6d&new-version=72f83785e61158ad0fe0a4f571dfc238aebe2f1c731a4ba5d857e24a2350caa9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> build(deps): bump actions/checkout from 6.0.2 to 6.0.3 (#4057) Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p> <blockquote> <h2>v6.0.3</h2> <h2>What's Changed</h2> <ul> <li>Update changelog by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li> <li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> <li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li> <li>Update changelog for v6.0.3 by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/yaananth"><code>@​yaananth</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <h2>v6.0.3</h2> <ul> <li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li> <li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> </ul> <h2>v6.0.2</h2> <ul> <li>Fix tag handling: preserve annotations and explicit fetch-tags by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li> </ul> <h2>v6.0.1</h2> <ul> <li>Add worktree support for persist-credentials includeIf by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li> </ul> <h2>v6.0.0</h2> <ul> <li>Persist creds to a separate file by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li> <li>Update README to include Node.js 24 support details and requirements by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li> </ul> <h2>v5.0.1</h2> <ul> <li>Port v6 cleanup to v5 by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li> </ul> <h2>v5.0.0</h2> <ul> <li>Update actions checkout to use node 24 by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li> </ul> <h2>v4.3.1</h2> <ul> <li>Port v6 cleanup to v4 by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li> </ul> <h2>v4.3.0</h2> <ul> <li>docs: update README.md by <a href="https://github.com/motss"><code>@​motss</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li> <li>Add internal repos for checking out multiple repositories by <a href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li> <li>Documentation update - add recommended permissions to Readme by <a href="https://github.com/benwells"><code>@​benwells</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li> <li>Adjust positioning of user email note and permissions heading by <a href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li> <li>Update README.md by <a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li> <li>Update CODEOWNERS for actions by <a href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li> <li>Update package dependencies by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li> </ul> <h2>v4.2.2</h2> <ul> <li><code>url-helper.ts</code> now leverages well-known environment variables by <a href="https://github.com/jww3"><code>@​jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li> <li>Expand unit test coverage for <code>isGhes</code> by <a href="https://github.com/jww3"><code>@​jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li> </ul> <h2>v4.2.1</h2> <ul> <li>Check out other refs/* by commit if provided, fall back to ref by <a href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li> </ul> <h2>v4.2.0</h2> <ul> <li>Add Ref and Commit outputs by <a href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li> <li>Dependency updates by <a href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>, <a href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li> </ul> <h2>v4.1.7</h2> <ul> <li>Bump the minor-npm-dependencies group across 1 directory with 4 updates by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li> <li>Bump actions/checkout from 3 to 4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li> <li>Check out other refs/* by commit by <a href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/actions/checkout/commit/df4cb1c069e1874edd31b4311f1884172cec0e10"><code>df4cb1c</code></a> Update changelog for v6.0.3 (<a href="https://redirect.github.com/actions/checkout/issues/2446">#2446</a>)</li> <li><a href="https://github.com/actions/checkout/commit/1cce3390c2bfda521930d01229c073c7ff920824"><code>1cce339</code></a> Fix checkout init for SHA-256 repositories (<a href="https://redirect.github.com/actions/checkout/issues/2439">#2439</a>)</li> <li><a href="https://github.com/actions/checkout/commit/900f2210b1d28bbbd0bd22d17926b9e224e8f231"><code>900f221</code></a> fix: expand merge commit SHA regex and add SHA-256 test cases (<a href="https://redirect.github.com/actions/checkout/issues/2414">#2414</a>)</li> <li><a href="https://github.com/actions/checkout/commit/0c366fd6a839edf440554fa01a7085ccba70ac98"><code>0c366fd</code></a> Update changelog (<a href="https://redirect.github.com/actions/checkout/issues/2357">#2357</a>)</li> <li>See full diff in <a href="https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10">compare view</a></li> </ul> </details> <br /> [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6.0.2&new-version=6.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> build(deps): bump the all-go-mod-patch-and-minor group with 7 updates (#4069) Bumps the all-go-mod-patch-and-minor group with 7 updates: | Package | From | To | | --- | --- | --- | | [github.com/instana/go-sensor](https://github.com/instana/go-sensor) | `1.73.1` | `1.73.2` | | [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) | `9.20.0` | `9.20.1` | | [golang.org/x/crypto](https://github.com/golang/crypto) | `0.52.0` | `0.53.0` | | [golang.org/x/net](https://github.com/golang/net) | `0.55.0` | `0.56.0` | | [golang.org/x/sync](https://github.com/golang/sync) | `0.20.0` | `0.21.0` | | [golang.org/x/term](https://github.com/golang/term) | `0.43.0` | `0.44.0` | | [golang.org/x/text](https://github.com/golang/text) | `0.37.0` | `0.38.0` | Updates `github.com/instana/go-sensor` from 1.73.1 to 1.73.2 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/instana/go-sensor/releases">github.com/instana/go-sensor's releases</a>.</em></p> <blockquote> <h2>v1.73.2</h2> <p>New release v1.73.2.</p> <h2>What's Changed</h2> <ul> <li>fix: prevent duplicate meter initialization in InitSensor by <a href="https://github.com/Angith"><code>@​Angith</code></a> in <a href="https://redirect.github.com/instana/go-sensor/pull/1510">instana/go-sensor#1510</a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/instana/go-sensor/commit/a6713a34fdcce14bed4716cd8884de630153b9f1"><code>a6713a3</code></a> Updated version.go to 1.73.2</li> <li><a href="https://github.com/instana/go-sensor/commit/e4775871c9d5588e415518bbe81f0ea00c332bc9"><code>e477587</code></a> fix: prevent duplicate meter initialization in InitSensor (<a href="https://redirect.github.com/instana/go-sensor/issues/1510">#1510</a>)</li> <li><a href="https://github.com/instana/go-sensor/commit/b8f171d37b3e36f860a2e03aaba476e5bc23e304"><code>b8f171d</code></a> Updated version.go to 0.48.7</li> <li><a href="https://github.com/instana/go-sensor/commit/e60c2a0886bfbf73e77bdef0fb783722bef3eb7b"><code>e60c2a0</code></a> feat(currency): updated go.mod, go.sum files, README.md for instaawsv2 (<a href="https://redirect.github.com/instana/go-sensor/issues/1506">#1506</a>)</li> <li><a href="https://github.com/instana/go-sensor/commit/871c797457c058626ddc539c184271e17f29ca60"><code>871c797</code></a> Updated version.go to 1.55.1</li> <li><a href="https://github.com/instana/go-sensor/commit/95a6af6f9399d56e98c946e97ec4c0addbf16393"><code>95a6af6</code></a> feat(currency): updated go.mod, go.sum files, README.md for instasarama (<a href="https://redirect.github.com/instana/go-sensor/issues/1504">#1504</a>)</li> <li><a href="https://github.com/instana/go-sensor/commit/465241d75a26707bc86c48317df28a8f3ab55970"><code>465241d</code></a> Updated version.go to 1.55.0</li> <li><a href="https://github.com/instana/go-sensor/commit/bf6b4802ce0daa23c3a2d7791c7675188c6f374b"><code>bf6b480</code></a> feat(currency): updated go.mod, go.sum files, README.md for instasarama (<a href="https://redirect.github.com/instana/go-sensor/issues/1503">#1503</a>)</li> <li><a href="https://github.com/instana/go-sensor/commit/a4d92189c8698b6edbb32a8775b434571b71b3d9"><code>a4d9218</code></a> Updated version.go to 1.44.2</li> <li><a href="https://github.com/instana/go-sensor/commit/9e04aa63d123f0f47d46009391da3a14beaf8330"><code>9e04aa6</code></a> chore(deps): bump github.com/quic-go/quic-go (<a href="https://redirect.github.com/instana/go-sensor/issues/1507">#1507</a>)</li> <li>Additional commits viewable in <a href="https://github.com/instana/go-sensor/compare/v1.73.1...v1.73.2">compare view</a></li> </ul> </details> <br /> Updates `github.com/redis/go-redis/v9` from 9.20.0 to 9.20.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/redis/go-redis/releases">github.com/redis/go-redis/v9's releases</a>.</em></p> <blockquote> <h2>9.20.1</h2> <p>This is a patch release containing bug fixes only. There are no new features or breaking changes; upgrading from 9.20.0 is a drop-in replacement.</p> <h2>🚀 Highlights</h2> <h3>RESP3 pub/sub message loss fixed</h3> <p><code>PeekPushNotificationName</code> previously inspected only the bytes already buffered by <code>bufio</code>, so when a push frame header straddled a buffer fill boundary it could return a <strong>truncated</strong> notification name (e.g. <code>&quot;messa&quot;</code> instead of <code>&quot;message&quot;</code>). The push processor then mis-routed the frame and <code>ReadReply</code> silently dropped it, causing intermittent RESP3 pub/sub message loss. The peek now grows its window (36 bytes → up to 4 KiB) and reads more from the connection until the header is complete, cleanly separating incomplete prefixes from corrupt frames (including overflow-safe bulk-length handling). Fixes <a href="https://redirect.github.com/redis/go-redis/issues/3839">#3839</a>.</p> <p>(<a href="https://redirect.github.com/redis/go-redis/pull/3842">#3842</a>) by <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></p> <h2>🐛 Bug Fixes</h2> <ul> <li><strong>RESP3 push peeking</strong>: <code>PeekPushNotificationName</code> no longer returns a truncated notification name when a push frame header spans a buffer boundary, preventing silent RESP3 pub/sub message loss (fixes <a href="https://redirect.github.com/redis/go-redis/issues/3839">#3839</a>) (<a href="https://redirect.github.com/redis/go-redis/pull/3842">#3842</a>) by <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></li> <li><strong><code>FT.HYBRID</code> vector params</strong>: Vector data is now always sent via <code>PARAMS</code> with auto-generated param names (<code>__vector_param_N</code>, with collision avoidance) when <code>VectorParamName</code> is omitted, since Redis no longer accepts inline vector blobs; the <code>FTHybridOptions.Params</code> map is no longer mutated, so the same options struct can be reused across calls (<a href="https://redirect.github.com/redis/go-redis/pull/3844">#3844</a>) by <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></li> <li><strong><code>CLUSTER SHARDS</code> forward compatibility</strong>: Unknown shard- and node-level attributes in the <code>CLUSTER SHARDS</code> reply are now skipped via <code>DiscardNext()</code> instead of erroring, so clients keep working when the server introduces new fields (<a href="https://redirect.github.com/redis/go-redis/pull/3843">#3843</a>) by <a href="https://github.com/madolson"><code>@​madolson</code></a></li> <li><strong>PubSub double reconnect</strong>: <code>PubSub.releaseConn</code> no longer reconnects twice when a connection is both unusable (or pending handoff) and reports a bad-connection error, avoiding a wasted connection establish-then-close cycle (<a href="https://redirect.github.com/redis/go-redis/pull/3833">#3833</a>) by <a href="https://github.com/cxljs"><code>@​cxljs</code></a></li> </ul> <h2>👥 Contributors</h2> <p>We'd like to thank all the contributors who worked on this release!</p> <p><a href="https://github.com/cxljs"><code>@​cxljs</code></a>, <a href="https://github.com/madolson"><code>@​madolson</code></a>, <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></p> <hr /> <p><strong>Full Changelog</strong>: <a href="https://github.com/redis/go-redis/compare/v9.20.0...v9.20.1">https://github.com/redis/go-redis/compare/v9.20.0...v9.20.1</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md">github.com/redis/go-redis/v9's changelog</a>.</em></p> <blockquote> <h1>9.20.1 (2026-06-11)</h1> <p>This is a patch release containing bug fixes only. There are no new features or breaking changes; upgrading from 9.20.0 is a drop-in replacement.</p> <h2>🚀 Highlights</h2> <h3>RESP3 pub/sub message loss fixed</h3> <p><code>PeekPushNotificationName</code> previously inspected only the bytes already buffered by <code>bufio</code>, so when a push frame header straddled a buffer fill boundary it could return a <strong>truncated</strong> notification name (e.g. <code>&quot;messa&quot;</code> instead of <code>&quot;message&quot;</code>). The push processor then mis-routed the frame and <code>ReadReply</code> silently dropped it, causing intermittent RESP3 pub/sub message loss. The peek now grows its window (36 bytes → up to 4 KiB) and reads more from the connection until the header is complete, cleanly separating incomplete prefixes from corrupt frames (including overflow-safe bulk-length handling). Fixes <a href="https://redirect.github.com/redis/go-redis/issues/3839">#3839</a>.</p> <p>(<a href="https://redirect.github.com/redis/go-redis/pull/3842">#3842</a>) by <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></p> <h2>🐛 Bug Fixes</h2> <ul> <li><strong>RESP3 push peeking</strong>: <code>PeekPushNotificationName</code> no longer returns a truncated notification name when a push frame header spans a buffer boundary, preventing silent RESP3 pub/sub message loss (fixes <a href="https://redirect.github.com/redis/go-redis/issues/3839">#3839</a>) (<a href="https://redirect.github.com/redis/go-redis/pull/3842">#3842</a>) by <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></li> <li><strong><code>FT.HYBRID</code> vector params</strong>: Vector data is now always sent via <code>PARAMS</code> with auto-generated param names (<code>__vector_param_N</code>, with collision avoidance) when <code>VectorParamName</code> is omitted, since Redis no longer accepts inline vector blobs; the <code>FTHybridOptions.Params</code> map is no longer mutated, so the same options struct can be reused across calls (<a href="https://redirect.github.com/redis/go-redis/pull/3844">#3844</a>) by <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></li> <li><strong><code>CLUSTER SHARDS</code> forward compatibility</strong>: Unknown shard- and node-level attributes in the <code>CLUSTER SHARDS</code> reply are now skipped via <code>DiscardNext()</code> instead of erroring, so clients keep working when the server introduces new fields (<a href="https://redirect.github.com/redis/go-redis/pull/3843">#3843</a>) by <a href="https://github.com/madolson"><code>@​madolson</code></a></li> <li><strong>PubSub double reconnect</strong>: <code>PubSub.releaseConn</code> no longer reconnects twice when a connection is both unusable (or pending handoff) and reports a bad-connection error, avoiding a wasted connection establish-then-close cycle (<a href="https://redirect.github.com/redis/go-redis/pull/3833">#3833</a>) by <a href="https://github.com/cxljs"><code>@​cxljs</code></a></li> </ul> <h2>👥 Contributors</h2> <p>We'd like to thank all the contributors who worked on this release!</p> <p><a href="https://github.com/cxljs"><code>@​cxljs</code></a>, <a href="https://github.com/madolson"><code>@​madolson</code></a>, <a href="https://github.com/ndyakov"><code>@​ndyakov</code></a></p> <hr /> <p><strong>Full Changelog</strong>: <a href="https://github.com/redis/go-redis/compare/v9.20.0...v9.20.1">https://github.com/redis/go-redis/compare/v9.20.0...v9.20.1</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/redis/go-redis/commit/a13416bf1c0556075b843dd0a9cb6db3b3cf2789"><code>a13416b</code></a> chore(release): 9.20.1 (<a href="https://redirect.github.com/redis/go-redis/issues/3847">#3847</a>)</li> <li><a href="https://github.com/redis/go-redis/commit/10dc44f424d58b9c2b4df29e529ca92a2f8fa986"><code>10dc44f</code></a> fix(push): fix peeking when push name is truncated (<a href="https://redirect.github.com/redis/go-redis/issues/3842">#3842</a>)</li> <li><a href="https://github.com/redis/go-redis/commit/e1a2d68b27e70ca8f3ff5eec61d0dd2b74554670"><code>e1a2d68</code></a> fix(ft.hybrid): Always generate vector param names if they are not provided b...</li> <li><a href="https://github.com/redis/go-redis/commit/a4b234f4a4f2551d1179181bbc136db132bf5912"><code>a4b234f</code></a> chore(deps): bump codecov/codecov-action from 6 to 7 (<a href="https://redirect.github.com/redis/go-redis/issues/3845">#3845</a>)</li> <li><a href="https://github.com/redis/go-redis/commit/974e717dbf3008497e54aec34636e34452e6872b"><code>974e717</code></a> fix(command): ignore unknown fields in CLUSTER SHARDS response (<a href="https://redirect.github.com/redis/go-redis/issues/3843">#3843</a>)</li> <li><a href="https://github.com/redis/go-redis/commit/65d6abde0d812234d26763930b5e84ff05963c96"><code>65d6abd</code></a> fix(pubsub): prevent double reconnect in releaseConn (<a href="https://redirect.github.com/redis/go-redis/issues/3833">#3833</a>)</li> <li>See full diff in <a href="https://github.com/redis/go-redis/compare/v9.20.0...v9.20.1">compare view</a></li> </ul> </details> <br /> Updates `golang.org/x/crypto` from 0.52.0 to 0.53.0 <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/golang/crypto/commit/45460e079737ecb64f30d79d3d6fc2914494fa66"><code>45460e0</code></a> go.mod: update golang.org/x dependencies</li> <li><a href="https://github.com/golang/crypto/commit/d37c95e27de65576f42440cdfbc261d810506841"><code>d37c95e</code></a> pkcs12: limit PBKDF iteration count to prevent CPU exhaustion</li> <li><a href="https://github.com/golang/crypto/commit/e2ffffe738fb46531cd8924bab497bdc77d9ecc8"><code>e2ffffe</code></a> ssh: reject incomplete gssapi-with-mic configurations</li> <li><a href="https://github.com/golang/crypto/commit/60e158ad30c226555794539d96f0f104f43395f7"><code>60e158a</code></a> ssh/test: isolate CLI tests from user SSH config and agent</li> <li><a href="https://github.com/golang/crypto/commit/1b77d23dc8afb26a494e331a3c637b6de88398d2"><code>1b77d23</code></a> ssh/knownhosts: reject lines with multiple or unknown markers</li> <li><a href="https://github.com/golang/crypto/commit/3872a2bc69f464352dea49c2856955ef7c5d5851"><code>3872a2b</code></a> ssh/knownhosts: verify declared key type matches decoded key</li> <li><a href="https://github.com/golang/crypto/commit/9f72eccdf6fa09a7c68448a7fdc333dd2deb7d9d"><code>9f72ecc</code></a> ssh/knownhosts: treat only ASCII space and tab as whitespace</li> <li><a href="https://github.com/golang/crypto/commit/8f405a4109d7ce87a44f89185bdc385914ce5c13"><code>8f405a4</code></a> ssh: validate ECDSA curve matches expected algorithm</li> <li><a href="https://github.com/golang/crypto/commit/bb41b3dbe630c21c78e3aeb0ebdd8d06a3a34fcf"><code>bb41b3d</code></a> ssh: improve DH GEX group selection using PreferredBits</li> <li><a href="https://github.com/golang/crypto/commit/e04e7219970912a40f80c4b13029416fdf960eeb"><code>e04e721</code></a> ssh/agent: validate ed25519 private key length in Add</li> <li>Additional commits viewable in <a href="https://github.com/golang/crypto/compare/v0.52.0...v0.53.0">compare view</a></li> </ul> </details> <br /> Updates `golang.org/x/net` from 0.55.0 to 0.56.0 <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/golang/net/commit/9e7fdbfadb32b0cc7524100014c5cf9b6adc7729"><code>9e7fdbf</code></a> internal/http3: fix wrong argument being given when validating header value</li> <li><a href="https://github.com/golang/net/commit/b686e5f3573e5f55120d664fc283ed7e80c1af3c"><code>b686e5f</code></a> internal/http3: add gzip support to transport</li> <li><a href="https://github.com/golang/net/commit/8a348850ed6818306667722af2b42a6fc63473e0"><code>8a34885</code></a> go.mod: update golang.org/x dependencies</li> <li><a href="https://github.com/golang/net/commit/72eaf98743302f6e0ad10883163dfc46dc8e8183"><code>72eaf98</code></a> dns/dnsmessage: correctly validate SVCB record parameter order</li> <li><a href="https://github.com/golang/net/commit/82e7868a02167540748b74780b0bf825985256f7"><code>82e7868</code></a> dns/dnsmessage: avoid panic when parsing SVCB record with truncated data</li> <li><a href="https://github.com/golang/net/commit/b64f1fa4c615965246e5b9e5c9d614916fa8967d"><code>b64f1fa</code></a> internal/http3: add server support for &quot;Trailer:&quot; magic prefix</li> <li><a href="https://github.com/golang/net/commit/2707ee21efea70599446dd35979b1d1aedf44ada"><code>2707ee2</code></a> internal/http3: implement HTTP/3 clientConn methods</li> <li><a href="https://github.com/golang/net/commit/31358cc259a764905194e3d6c597375b0ff366c1"><code>31358cc</code></a> internal/http3: snapshot response headers at WriteHeader time</li> <li><a href="https://github.com/golang/net/commit/8ecbaa95fea823c19fa74c5c3b53e0bccd473828"><code>8ecbaa9</code></a> html: don't adjust xml:base</li> <li><a href="https://github.com/golang/net/commit/8ae811abe5c2daa55c68d51a101af1c5751a4d55"><code>8ae811a</code></a> html: properly handle end script tag in fragment mode</li> <li>Additional commits viewable in <a href="https://github.com/golang/net/compare/v0.55.0...v0.56.0">compare view</a></li> </ul> </details> <br /> Updates `golang.org/x/sync` from 0.20.0 to 0.21.0 <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/golang/sync/commit/5071ed6a9f1617117556b66384f765c934de3698"><code>5071ed6</code></a> all: fix some comments to improve readability</li> <li>See full diff in <a href="https://github.com/golang/sync/compare/v0.20.0...v0.21.0">compare view</a></li> </ul> </details> <br /> Updates `golang.org/x/term` from 0.43.0 to 0.44.0 <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/golang/term/commit/3b43943a9e7de876a5d5e1f5e7da7cdeae0f542a"><code>3b43943</code></a> go.mod: update golang.org/x dependencies</li> <li>See full diff in <a href="https://github.com/golang/term/compare/v0.43.0...v0.44.0">compare view</a></li> </ul> </details> <br /> Updates `golang.org/x/text` from 0.37.0 to 0.38.0 <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/golang/text/commit/f4bb6328041b090f85b93014bd369edfcd24bdef"><code>f4bb632</code></a> go.mod: update golang.org/x dependencies</li> <li>See full diff in <a href="https://github.com/golang/text/compare/v0.37.0...v0.38.0">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> build(deps): bump alpine from `5b10f43` to `a2d49ea` in /packaging (#4064) Bumps alpine from `5b10f43` to `a2d49ea`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11&new-version=a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> fix: apply workaround for readTimeout (#4061) feature: Use go-brrr as brotli backend (#4066) test: fix flaky tests in proxylistener (#4055) test: fix flaky tests in proxylistener Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> migrate: docker image location on GHCR only (#4054) doc: delete reference to registry.opensource.zalan.do/teapot/skipper Please everyone migrate to ghcr.io/zalando/skipper Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> doc: routing is not a security feature (#4052) doc: routing is not a security feature --------- Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> fix: unbounded read of consuming the http.Body in the validation webhook (#4053) fix: unbounded read of consuming the http.Body in the validation webhook Reported by @alcls01111 Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> doc: templating security (#4050) doc: templating security Thanks @alcls01111 for reporting this. Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> doc: setDynamicBackendUrlFromHeader SSRF risk (#4051) doc: setDynamicBackendUrlFromHeader SSRF risk It was forgotten to document the risk and we got a second researcher telling about this filter risk. Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> go: update to 1.26.4 (#4046) remove not needed replace directive Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> Fix oidcClaimsQuery example (#4048) feature: proxy mTLS support (#4037) feature: proxy mTLS support implementation based on net.Client and net.CertReloader Added test cases by ai and validated the code that it is testing as expected. --------- Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de> build(deps): bump oss-fuzz-base/base-builder-go from `3c453ef` to `972dea5` in /.clusterfuzzlite (#4040) Bumps oss-fuzz-base/base-builder-go from `3c453ef` to `972dea5`. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> fix: eliminate race in TestCacheFilter_ColdMissCoalescing (#4034) ## Problem `TestCacheFilter_ColdMissCoalescing` was intermittently failing under `-race` with: ``` --- FAIL: TestCacheFilter_ColdMissCoalescing (0.00s) filter_test.go:401: expected 1 upstream fetch, got 2 ``` The test asserts that 50 concurrent cold-miss requests coalesce into exactly one upstream fetch via `singleflight.DoChan`. The previous synchronization used a `sync.WaitGroup` (`wgIn`) where each goroutine called `wgIn.Done()` **before** calling `f.Request()`. The fetch stub blocked on `wgIn.Wait()`, intending to hold the singleflight open until all goroutines had joined it. This had a [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to-time-of-use) gap: a goroutine could call `wgIn.Done()`, get preempted, and resume while the fetch stub had already unblocked from `wgIn.Wait()` but had not yet called `storage.Set()`. At that point the goroutine would call `storage.Get()` → `nil` and enter `DoChan()`. If the leader's `fetchFn` had returned in the interim, `DoChan()` would start a new singleflight round and fire a second upstream fetch. Under the race detector's overhead this window was wide enough to trigger reliably. ## Fix Replaced `wgIn` with a `missCounting` storage wrapper that embeds a `sync.WaitGroup`. Each goroutine calls `wg.Done()` inside `storage.Get()` at the exact moment it observes a cache miss - the last possible point before `coalesce()` → `DoChan()`. The fetch stub calls `wg.Wait()`, which durably blocks `fetchFn` until all N goroutines have confirmed a miss and are either inside `DoChan`'s waitlist or in the scheduler-opaque gap immediately before it. Only then does `fetchFn` call `storage.Set()` and return, closing the singleflight round. The main goroutine's spin loop was replaced with a `fetchStarted` channel closed by the fetch stub on entry, eliminating all busy-waits. ### Before <img width="6795" height="4395" alt="Goroutine Missed Window-2026-05-26-141411" src="https://github.com/user-attachments/assets/0fb1da49-0707-49b3-a49e-c83fc5e42299" /> ### After <img width="5770" height="2840" alt="Goroutine Missed Window-2026-05-26-141426" src="https://github.com/user-attachments/assets/73b8ffe0-6563-4841-a18d-f2c46f46fe0f" /> No production code was changed. --------- Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> build(deps): bump docker/setup-qemu-action from 4.0.0 to 4.1.0 (#4038) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.0.0 to 4.1.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/docker/setup-qemu-action/releases">docker/setup-qemu-action's releases</a>.</em></p> <blockquote> <h2>v4.1.0</h2> <ul> <li>Add <code>reset</code> input to uninstall current emulators by <a href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/21">docker/setup-qemu-action#21</a></li> <li>Bump <code>@​docker/actions-toolkit</code> from 0.77.0 to 0.91.0 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/250">docker/setup-qemu-action#250</a> <a href="https://redirect.github.com/docker/setup-qemu-action/pull/247">docker/setup-qemu-action#247</a></li> <li>Bump brace-expansion from 1.1.12 to 1.1.15 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/265">docker/setup-qemu-action#265</a></li> <li>Bump fast-xml-builder from 1.0.0 to 1.2.0 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/286">docker/setup-qemu-action#286</a></li> <li>Bump fast-xml-parser from 5.4.2 to 5.8.0 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/255">docker/setup-qemu-action#255</a></li> <li>Bump flatted from 3.3.3 to 3.4.2 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/257">docker/setup-qemu-action#257</a></li> <li>Bump glob from 10.3.15 to 10.5.0 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/254">docker/setup-qemu-action#254</a></li> <li>Bump handlebars from 4.7.8 to 4.7.9 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/262">docker/setup-qemu-action#262</a></li> <li>Bump lodash from 4.17.23 to 4.18.1 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/273">docker/setup-qemu-action#273</a></li> <li>Bump postcss from 8.5.6 to 8.5.10 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/285">docker/setup-qemu-action#285</a></li> <li>Bump tar from 6.2.1 to 7.5.15 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/287">docker/setup-qemu-action#287</a></li> <li>Bump tmp from 0.2.5 to 0.2.6 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/291">docker/setup-qemu-action#291</a></li> <li>Bump undici from 6.23.0 to 6.26.0 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/251">docker/setup-qemu-action#251</a></li> <li>Bump vite from 7.3.1 to 7.3.2 in <a href="https://redirect.github.com/docker/setup-qemu-action/pull/271">docker/setup-qemu-action#271</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/docker/setup-qemu-action/compare/v4.0.0...v4.1.0">https://github.com/docker/setup-qemu-action/compare/v4.0.0...v4.1.0</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/docker/setup-qemu-action/commit/06116385d9baf250c9f4dcb4858b16962ea869c3"><code>0611638</code></a> Merge pull request <a href="https://redirect.github.com/docker/setup-qemu-action/issues/21">#21</a> from crazy-max/uninst</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/ce59c818a5ff16552ddf7407ee7cb00bea682925"><code>ce59c81</code></a> chore: update generated content</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/2ddad4401e17fa807e8a3c4bd289ccdd993f0868"><code>2ddad44</code></a> uninstall current emulators</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/8c37cd6f3456e1f3f3026250eac496709e9e7e10"><code>8c37cd6</code></a> Merge pull request <a href="https://redirect.github.com/docker/setup-qemu-action/issues/250">#250</a> from docker/dependabot/npm_and_yarn/docker/actions-to...</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/d1a0ff34af591b8e290e46f3fa114ef5bb81cd1c"><code>d1a0ff3</code></a> chore: update generated content</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/0a8f3dc12541cc2c3b19c182a1a2c90a2c8b8d93"><code>0a8f3dc</code></a> build(deps): bump <code>@​docker/actions-toolkit</code> from 0.79.0 to 0.91.0</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/9430f61a7691bd1bfdc4d6ba70e558659d36fa7a"><code>9430f61</code></a> Merge pull request <a href="https://redirect.github.com/docker/setup-qemu-action/issues/291">#291</a> from docker/dependabot/npm_and_yarn/tmp-0.2.6</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/978bd7796cb6698377e7af6726b726e5ced642d0"><code>978bd77</code></a> chore: update generated content</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/3479febc62cc0fbcb98c7c7fc0dac778c0d79d6a"><code>3479feb</code></a> build(deps): bump tmp from 0.2.5 to 0.2.6</li> <li><a href="https://github.com/docker/setup-qemu-action/commit/b113c264143c28c2974bed61af25be32d32f4782"><code>b113c26</code></a> Merge pull request <a href="https://redirect.github.com/docker/setup-qemu-action/issues/255">#255</a> from docker/dependabot/npm_and_yarn/fast-xml-parser-5...</li> <li>Additional commits viewable in <a href="https://github.com/docker/setup-qemu-action/compare/ce360397dd3f832beb865e1373c09c0e9f86d70a...06116385d9baf250c9f4dcb4858b16962ea869c3">compare view</a></li> </ul> </details> <br /> [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/setup-qemu-action&package-manager=github_actions&previous-version=4.0.0&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> fix: wait for cipher refresher goroutine to exit on Close (#4036) # Related Issue Fixes a goroutine leak found while working on #4034. ## Problem `Registry.Close()` called `v.Close()` on each encrypter but returned immediately, leaving the `runCipherRefresher` goroutine running until it observed the closed channel on its next select iteration. Under `noleak` this caused a test failure in the `secrets` package. ## Fix Initialize `closedHook` inside `runCipherRefresher` (only when a goroutine is actually started) and drain it in `Registry.Close()` so shutdown blocks until the goroutine has exited. The `nil` guard on the drain ensures encrypters created without a refresh interval (no goroutine started) are unaffected. No behavior change in production - `Close()` is only called at shutdown. --------- Signed-off-by: Larry D Almeida <hello@larrydalmeida.com> introduce timeout for decicion log task (#4035) ## Problem When asyncDecisionLogging is enabled and a new policy bundle is deployed, eopa_dl.Reconfigure() calls Stop() on the running Benthos stream followed by Start() on a new one. If `runDecisionLogger` is inside stream.Consume() on the old stream at that moment, the Benthos producer closure tries to write the decision event to an internal channel whose reader (inproc input) has been destroyed by Stop(). Because `doLogDecision` was called with context.Background() (without deadline) the blocked send never returns. `runDecisionLogger` is permanently stuck on that one call and stops draining `decisionLogChan`. With the goroutine stuck, once the channel fills to capacity (buffer_size_limit_events), every subsequent `logDecision` call hits the non-blocking default branch and drops the event, logging "Decision log dropped: async buffer full." The warning continues indefinitely with no self-recovery. The only fix prior to this change was a pod restart. ## Fix Set timeout for the context passed for decision logging. The value for the timeout is decided using the configured timeout for the eopa_dl output type --------- Signed-off-by: nuwandi-wickramasinghe_zse <nuwandi.wickramasinghe@zalando.de> Co-authored-by: nuwandi-wickramasinghe_zse <nuwandi.wickramasinghe@zalando.de> build(deps): bump the all-go-mod-patch-and-minor group with 13 updates (#4039) Bumps the all-go-mod-patch-and-minor group with 13 updates: | Package | From | To | | --- | --- | --- | | [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) | `9.19.0` | `9.20.0` | | [go.opentelemetry.io/contrib/exporters/autoexport](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.68.0` | `0.69.0` | | [go.opentelemetry.io/contrib/propagators/autoprop](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.68.0` | `0.69.0` | | [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/bridge/opentracing](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/exporters/stdout/stdouttrace](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/metric](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/sdk/metric](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | | [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) | `1.43.0` | `1.44.0` | Updates `github.com/redis/go-redis/v9` from 9.19.0 to 9.20.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/redis/go-redis/releases">github.com/redis/go-redis/v9's releases</a>.</em></p> <blockquote> <h2>9.20.0</h2> <h2>🚀 Highlights</h2> <h3>Redis 8.8 Support</h3> <p>This release adds support for <strong>Redis 8.8</strong>. The README's supported-versions list now includes Redis 8.8 alongside 8.0/8.2/8.4, and CI exercises the <code>8.8</code> client-libs-test image across the full suite (Makefile, build workflow, doctests, run-tests action, and docker-compose).</p> <p>Coverage for the new commands that ship in the 8.x line, rounded out in this release:</p> <ul> <li><strong><code>AR*</code> array data type</strong> (<a href="https://redirect.github.com/redis/go-redis/pull/3813">#3813</a>) — new array data structure, exposed via the <code>ArrayCmdable</code> interface (see the experimental-features highlight below).</li> <li><strong><code>INCREX</code></strong> (<a href="https://redirect.github.com/redis/go-redis/pull/3816">#3816</a>) — atomic increment with expiration in a single round-trip.</li> <li><strong><code>XNACK</code></strong> (<a href="https://redirect.github.com/redis/go-redis/pull/3790">#3790</a>) — explicit negative-acknowledge of pending stream entries.</li> <li><strong><code>XAUTOCLAIM</code> PEL deletes</…
1 parent 8760752 commit 65bef5e

5 files changed

Lines changed: 168 additions & 112 deletions

File tree

docs/operation/operation.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,3 +1839,37 @@ will be changed to
18391839
```
18401840
r: SourceFromLast("9.0.0.0/24","2001:67c:20a0::/48") -> ...`
18411841
```
1842+
1843+
## Cache
1844+
1845+
By default entries are stored in an in-process LRU (L1) local to each Skipper process.
1846+
When `--swarm-valkey-urls` is configured, Valkey becomes the primary shared
1847+
store (L2) accessible by all Skipper instances via a client-side consistent hash ring. Every
1848+
read checks L1 first; an L1 hit returns without contacting Valkey.
1849+
1850+
On every successful Valkey write the entry is also written to L1
1851+
(write-through) with a TTL of `min(--cache-l1-ttl, entry.TTL)`. The default is
1852+
60 seconds, bounding how long Skipper serves a locally-cached entry before
1853+
falling back to Valkey. Set `--cache-l1-ttl=0` to disable L1 warming and
1854+
restore write-around behaviour (L1 used only when Valkey is unavailable).
1855+
1856+
Explicit deletes (unsafe methods or operator-initiated invalidation) always
1857+
remove the L1 entry unconditionally, regardless of `--cache-l1-ttl`.
1858+
1859+
!!! note
1860+
The in-process LRU (L1) is shared across all `cache()` filter instances in the same process.
1861+
When Valkey is configured it acts as a shared store (L2). The L1 storage budget is divided evenly
1862+
across 256 internal shards; a single entry larger than one shard's budget is
1863+
dropped with a warning log.
1864+
1865+
### Metrics
1866+
1867+
- `lru_eviction`: Counter, incremented each time an L1 entry is evicted due to memory pressure
1868+
- `lru_bytes`: Gauge, current L1 usage in bytes
1869+
- `lru_oversized`: Counter, incremented when an entry is too large for any shard and silently dropped
1870+
1871+
When Valkey is configured:
1872+
1873+
- `l1_hit`: Counter, L1 hits that bypassed Valkey
1874+
- `valkey_miss`: Counter, Valkey misses that proceeded to an upstream fetch
1875+
- `valkey_get_fallback`, `valkey_set_fallback`: Counters, reads/writes that fell back to L1 due to Valkey errors

docs/reference/filters.md

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3893,38 +3893,6 @@ matching the same key. It has no awareness of other filters in the chain.
38933893
* **`Cache-Control: private` is ignored in force mode.** Audit the upstream
38943894
response before enabling force mode on any authenticated route.
38953895
3896-
**Storage**
3897-
3898-
By default entries are stored in an in-process LRU (L1) local to each pod.
3899-
When `--swarm-valkey-urls` is configured, Valkey becomes the primary shared
3900-
store (L2) accessible by all pods via a client-side consistent hash ring. Every
3901-
read checks L1 first; an L1 hit returns without contacting Valkey.
3902-
3903-
On every successful Valkey write the entry is also written to L1
3904-
(write-through) with a TTL of `min(--cache-l1-ttl, entry.TTL)`. The default is
3905-
60 seconds, bounding how long a pod serves a locally-cached entry before
3906-
falling back to Valkey. Set `--cache-l1-ttl=0` to disable L1 warming and
3907-
restore write-around behaviour (L1 used only when Valkey is unavailable).
3908-
3909-
Explicit deletes (unsafe methods or operator-initiated invalidation) always
3910-
remove the L1 entry unconditionally, regardless of `--cache-l1-ttl`.
3911-
3912-
!!! note
3913-
The in-process LRU (L1) is shared across all `cache()` filter instances on
3914-
the same pod but is local to that pod. When Valkey is configured it acts as
3915-
the cross-pod shared store (L2). The L1 storage budget is divided evenly
3916-
across 256 internal shards; a single entry larger than one shard's budget is
3917-
dropped with a warning log.
3918-
3919-
!!! note
3920-
Metrics: `lru_eviction` (counter, incremented each time an L1 entry is
3921-
evicted due to memory pressure), `lru_bytes` (gauge, current L1 usage in
3922-
bytes), `lru_oversized` (counter, entry too large for any shard and
3923-
silently dropped). When Valkey is configured: `l1_hit` (counter, L1 hit
3924-
that bypassed Valkey), `valkey_miss` (counter, Valkey miss that proceeded
3925-
to an upstream fetch), `valkey_get_fallback` and `valkey_set_fallback`
3926-
(counters, reads/writes that fell back to L1 due to a Valkey error).
3927-
39283896
!!! note
39293897
`s-maxage` implies `proxy-revalidate` per [RFC 9111 §5.2.2.10](https://www.rfc-editor.org/rfc/rfc9111#section-5.2.2.10): stale entries
39303898
stored under `s-maxage` are never served without revalidation, regardless of

filters/cache/filter.go

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,23 @@ const (
4242
cacheStatusMiss = "MISS"
4343
cacheStatusStale = "STALE"
4444

45-
// revalQueueSize is the capacity of the per-filter revalidation job queue.
45+
// revalQueueSize is the capacity of the shared revalidation job queue.
4646
// Sized to absorb short bursts; jobs are dropped (with reval_dropped metric)
4747
// if the worker cannot keep up.
4848
revalQueueSize = 256
4949
)
5050

51-
// NewCacheFilter returns a Spec for the cache() filter. maxBytes is the
52-
// in-memory storage budget for the shared LRU cache backing all filter
53-
// instances created from this Spec.
54-
// listenAddr is Skipper's own listener address (e.g. ":9090"); revalidation
55-
// requests are sent back through Skipper so the full filter chain runs.
51+
// Options configures the cache filter. All fields are required unless stated otherwise.
52+
type Options struct {
53+
MaxBytes int64 // in-memory storage budget for the LRU
54+
ListenAddr string // Skipper's own listener address (e.g., ":9090")
55+
NetOpts skpnet.Options // network options for revalidation requests
56+
ValkeyRing *skpnet.ValkeyRingClient // optional L2 cache backend; nil = LRU only
57+
L1TTL time.Duration // max TTL to use when warming L1 from Valkey writes
58+
Metrics metrics.Metrics // optional; defaults to metrics.Default if nil
59+
}
60+
61+
// NewCacheFilter returns a Spec for the cache() filter.
5662
//
5763
// Route usage (RFC mode — upstream Cache-Control is fully authoritative):
5864
//
@@ -65,38 +71,67 @@ const (
6571
// Combining force mode with stale-if-error:
6672
//
6773
// -> cache("5m", "15s", "30s", "60s") -> "https://example.org"
68-
func NewCacheFilter(maxBytes int64, listenAddr string, netOpts skpnet.Options, valkeyRing *skpnet.ValkeyRingClient, l1TTL time.Duration) filters.Spec {
69-
m := metrics.Default
70-
lru := NewLRUStorage(maxBytes, func() {
74+
func NewCacheFilter(opts Options) filters.Spec {
75+
if opts.Metrics == nil {
76+
opts.Metrics = metrics.Default
77+
}
78+
79+
m := opts.Metrics
80+
lru := NewLRUStorage(opts.MaxBytes, func() {
7181
m.IncCounter("lru_eviction")
7282
}, m)
7383

7484
var store Storage = lru
75-
if valkeyRing != nil {
76-
store = NewValkeyStorage(valkeyRing, lru, m, l1TTL)
85+
if opts.ValkeyRing != nil {
86+
store = NewValkeyStorage(opts.ValkeyRing, lru, m, opts.L1TTL)
7787
}
7888

79-
return &cacheSpec{
80-
maxBytes: maxBytes,
81-
listenAddr: listenAddr,
82-
client: skpnet.NewClient(netOpts),
83-
storage: store,
84-
lruStorage: lru,
85-
metrics: m,
89+
spec := &cacheSpec{
90+
maxBytes: opts.MaxBytes,
91+
listenAddr: opts.ListenAddr,
92+
client: skpnet.NewClient(opts.NetOpts),
93+
storage: store,
94+
lruStorage: lru,
95+
metrics: m,
96+
revalJobs: make(chan revalJob, revalQueueSize),
97+
lruBytesDone: make(chan struct{}),
8698
}
99+
100+
// Start shared background goroutines (one worker + one scraper for all filter instances)
101+
spec.bgWg.Add(2)
102+
go spec.revalidationWorker()
103+
go spec.lruBytesScraper()
104+
105+
return spec
87106
}
88107

89108
type cacheSpec struct {
90-
maxBytes int64
91-
listenAddr string
92-
client *skpnet.Client
93-
storage Storage // shared across all filter instances
94-
lruStorage *LRUStorage // always non-nil; direct reference to L1, even when storage is ValkeyStorage
95-
metrics metrics.Metrics
109+
maxBytes int64
110+
listenAddr string
111+
client *skpnet.Client
112+
storage Storage // shared across all filter instances
113+
lruStorage *LRUStorage // always non-nil; direct reference to L1, even when storage is ValkeyStorage
114+
metrics metrics.Metrics
115+
revalJobs chan revalJob // shared queue; one spec-level worker drains this
116+
lruBytesDone chan struct{} // closed to signal lruBytesScraper to stop
117+
bgWg sync.WaitGroup // tracks spec-level background goroutines
96118
}
97119

98120
func (s *cacheSpec) Name() string { return filterName }
99121

122+
// Close shuts down the background revalidation worker and lru_bytes scraper.
123+
// Safe to call multiple times; idempotent via a guard.
124+
func (s *cacheSpec) Close() {
125+
select {
126+
case <-s.lruBytesDone:
127+
// Already closed; prevent panic on double-close.
128+
default:
129+
close(s.revalJobs)
130+
close(s.lruBytesDone)
131+
s.bgWg.Wait()
132+
}
133+
}
134+
100135
func (s *cacheSpec) CreateFilter(args []interface{}) (filters.Filter, error) {
101136
if len(args) != 0 && (len(args) < 3 || len(args) > 5) {
102137
return nil, fmt.Errorf("cache: expected 0 or 3-5 args (ttl, errorTTL, swrWindow[, staleIfError[, keyHeaders]]), got %d: %w", len(args), filters.ErrInvalidFilterParameters)
@@ -157,57 +192,50 @@ func (s *cacheSpec) CreateFilter(args []interface{}) (filters.Filter, error) {
157192
rfcMode: rfcMode,
158193
metrics: s.metrics,
159194
keyHeaders: keyHeaders,
160-
revalJobs: make(chan revalJob, revalQueueSize),
161-
lruBytesDone: make(chan struct{}),
195+
revalJobs: s.revalJobs, // use spec-level shared channel
196+
lruBytesDone: s.lruBytesDone, // use spec-level shared signal
162197
}
163198

164199
cf.fetch = s.client.Do
165-
cf.bgWg.Add(2)
166-
go cf.revalidationWorker()
167-
go cf.lruBytesScraper()
168200
return cf, nil
169201
}
170202

171-
// Close shuts down the background revalidation worker and the lru_bytes scraper,
172-
// blocking until both goroutines have exited.
173-
func (f *cacheFilter) Close() {
174-
close(f.revalJobs)
175-
close(f.lruBytesDone)
176-
f.bgWg.Wait()
177-
}
178-
179-
// revalidationWorker is the single background goroutine per filter that
180-
// processes revalidation jobs sequentially. It exits when revalJobs is closed.
181-
func (f *cacheFilter) revalidationWorker() {
182-
defer f.bgWg.Done()
183-
for job := range f.revalJobs {
184-
f.doRevalidate(job.key, job.req)
203+
// revalidationWorker is the single background goroutine (spec-level, shared across
204+
// all filter instances) that processes revalidation jobs sequentially. It calls
205+
// the per-instance doRevalidateFn closure to respect each route's configuration.
206+
func (s *cacheSpec) revalidationWorker() {
207+
defer s.bgWg.Done()
208+
for job := range s.revalJobs {
209+
if job.doRevalFn != nil {
210+
job.doRevalFn()
211+
}
185212
}
186213
log.Debug("cache: revalidation worker stopped")
187214
}
188215

189216
const lruBytesScrapeInterval = 10 * time.Second
190217

191218
// lruBytesScraper periodically updates the lru_bytes gauge so it stays current
192-
// even when no evictions occur (large Sets without exceeding capacity never
193-
// trigger the onEvict callback). It exits when lruBytesDone is closed.
194-
func (f *cacheFilter) lruBytesScraper() {
195-
defer f.bgWg.Done()
219+
// even when no evictions occur. It's spec-level and shared across all filter instances.
220+
// It exits when lruBytesDone is closed (via cacheSpec.Close).
221+
func (s *cacheSpec) lruBytesScraper() {
222+
defer s.bgWg.Done()
196223
ticker := time.NewTicker(lruBytesScrapeInterval)
197224
defer ticker.Stop()
198225
for {
199226
select {
200227
case <-ticker.C:
201-
f.metrics.UpdateGauge("lru_bytes", float64(f.lruStorage.lru.Bytes()))
202-
case <-f.lruBytesDone:
228+
s.metrics.UpdateGauge("lru_bytes", float64(s.lruStorage.lru.Bytes()))
229+
case <-s.lruBytesDone:
203230
return
204231
}
205232
}
206233
}
207234

208235
type revalJob struct {
209-
key string
210-
req *http.Request // pre-cloned, safe to use after the originating request ends
236+
key string
237+
req *http.Request // pre-cloned, safe to use after the originating request ends
238+
doRevalFn func() // closure with access to per-instance doRevalidate
211239
}
212240

213241
type cacheFilter struct {
@@ -225,13 +253,16 @@ type cacheFilter struct {
225253
rfcMode bool
226254
coldSF singleflight.Group // cold-miss coalescing
227255
revalSF singleflight.Group // coalesces concurrent background revalidations per key
228-
revalJobs chan revalJob // background revalidation queue; worker drains this
229-
lruBytesDone chan struct{} // closed by Close() to stop the lruBytesScraper goroutine
230-
bgWg sync.WaitGroup // tracks background goroutines; Wait()ed in Close()
256+
revalJobs chan revalJob // shared background revalidation queue from cacheSpec
257+
lruBytesDone chan struct{} // shared channel from cacheSpec; closed to stop scraper
231258
fetch func(*http.Request) (*http.Response, error)
232259
metrics metrics.Metrics
233260
}
234261

262+
// Close is a no-op on individual filter instances; the real Close is on cacheSpec.
263+
// This exists for test compatibility.
264+
func (f *cacheFilter) Close() {}
265+
235266
// tagSpan sets cache_status, cache_key, and (when >= 0) cache_ttl_remaining_ms
236267
// on the active OpenTracing span. No-op when no span is present.
237268
func tagSpan(ctx filters.FilterContext, status, key string, ttlRemainingMs int64) {
@@ -669,10 +700,18 @@ func (f *cacheFilter) Response(ctx filters.FilterContext) {
669700
// enqueueRevalidation sends a revalidation job to the background worker.
670701
// The request is cloned in the calling goroutine before orig is released.
671702
// If the queue is full the job is dropped and reval_dropped is incremented.
703+
// The closure captures f.doRevalidate so the spec-level worker respects this route's config.
672704
func (f *cacheFilter) enqueueRevalidation(key string, orig *http.Request) {
673705
cloned := orig.Clone(context.Background())
706+
job := revalJob{
707+
key: key,
708+
req: cloned,
709+
doRevalFn: func() {
710+
f.doRevalidate(key, cloned)
711+
},
712+
}
674713
select {
675-
case f.revalJobs <- revalJob{key: key, req: cloned}:
714+
case f.revalJobs <- job:
676715
default:
677716
f.metrics.IncCounter("reval_dropped")
678717
}

0 commit comments

Comments
 (0)