fix: revert per-client xDS readiness gates (#13868, #13958) in favor of a first-connect grace period#14380
Merged
davidjumani merged 4 commits intoJul 10, 2026
Conversation
chandler-solo
force-pushed
the
chandler/14184revert13868andsleep2
branch
from
July 10, 2026 15:07
525170c to
96091fe
Compare
chandler-solo
marked this pull request as ready for review
July 10, 2026 15:14
Contributor
There was a problem hiding this comment.
Pull request overview
This PR mitigates per-client xDS snapshot starvation by reverting the whole-snapshot readiness gates (from #13868/#13958) and instead introducing a per-stream “first-connect” grace period to give per-client translation time to converge before the first response is served. It also adds targeted unit and e2e coverage to pin the anti-starvation behavior across rollouts and controller restarts.
Changes:
- Add a configurable per-xDS-stream first-connect delay (
KGW_XDS_FIRST_CONNECT_DELAY) implemented in the uniquely-connected-clients callbacks. - Remove the per-client “referenced clusters / referenced endpoints” readiness gates and their proto-walk machinery, keeping only the minimal “inputs not ready yet” deferral.
- Add regression tests (unit + e2e) for the anti-starvation properties and wire the new e2e suite into CI.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/e2e/tests/kgateway_tests.go | Registers the new XdsStarvation e2e suite. |
| test/e2e/features/xds_starvation/testdata/setup.yaml | Adds Gateway/Routes including an ExternalName backend to model a never-ready reference. |
| test/e2e/features/xds_starvation/testdata/postrestart-route.yaml | Adds a post-restart HTTPRoute to verify warm clients still receive updates. |
| test/e2e/features/xds_starvation/suite.go | Implements e2e assertions covering serving, gateway rollout, and controller restart behavior under a never-ready reference. |
| pkg/krtcollections/uniqueclients.go | Implements atomic-backed, env-tunable first-connect delay applied once per new stream. |
| pkg/krtcollections/uniqueclients_test.go | Disables first-connect delay for existing unit test suite that creates many streams. |
| pkg/krtcollections/uniqueclients_delay_test.go | Adds a focused unit test pinning per-stream delay semantics. |
| pkg/krtcollections/export_test.go | Exposes a test-only delay override helper for race-safe test control. |
| pkg/kgateway/setup/main_test.go | Sets default KGW_XDS_FIRST_CONNECT_DELAY=0 for live ADS server tests unless explicitly overridden. |
| pkg/kgateway/proxy_syncer/proxy_syncer.go | Removes referenced-cluster tracking and updates comments documenting deferred snapshot behavior. |
| pkg/kgateway/proxy_syncer/perclient.go | Removes whole-snapshot readiness gates and updates defer rationale/comments. |
| pkg/kgateway/proxy_syncer/perclient_test.go | Removes gate-specific tests and retains/adds regression tests ensuring snapshots still publish under certain failure modes. |
| Makefile | Tightens KinD cluster existence check matching. |
| .github/workflows/e2e.yaml | Adds XdsStarvation to the e2e run matrix. |
chandler-solo
force-pushed
the
chandler/14184revert13868andsleep2
branch
from
July 10, 2026 16:00
83aac5b to
d1cba91
Compare
chandler-solo
force-pushed
the
chandler/14184revert13868andsleep2
branch
from
July 10, 2026 16:08
d1cba91 to
88cb65e
Compare
…ns (kgateway-dev#13958)" This reverts commit 3c926bb. Signed-off-by: David L. Chandler <[email protected]>
This reverts commit 91ce39f. Signed-off-by: David L. Chandler <[email protected]>
Now that we have done pure reverts of 13958 and 13868, a few changes held over, an updated comment, and sleeping upon new UCC connection to mitigate but not perfectly solve kgateway-dev#14184 Signed-off-by: David L. Chandler <[email protected]>
And a Makefile fix for many local kind clusters. Signed-off-by: David L. Chandler <[email protected]>
chandler-solo
force-pushed
the
chandler/14184revert13868andsleep2
branch
from
July 10, 2026 16:08
88cb65e to
a326f78
Compare
davidjumani
approved these changes
Jul 10, 2026
This was referenced Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Greatly helps #14184, with an explicit trade-off: it reintroduces the race that #13868 closed (a chance of brief Envoy 5xx with
NCresponse flags around controller restarts), but shrinks that window with a configurable first-connect grace period — trading #13868's unbounded failure mode for a bounded, self-healing one.The problem
#13868 gated per-client xDS publication behind whole-snapshot readiness checks: if any route-referenced cluster was missing from the per-client CDS map, or any referenced EDS cluster lacked a
ClusterLoadAssignment, the client's entire snapshot was withheld. When the gate stays unsatisfiable — ExternalName references, per-client derivation backlog under large unique-client fan-out — this produces the two failure modes in #14184: warm proxies stranded indefinitely on stale endpoints (upstream connect failures to deleted pods), and first-time proxies starved of any config until they fail their startup probe and crashloop. Because the gate was level-triggered and unconditional, no restart or rollout could clear it.The fix
This PR removes the gates and replaces them with a time-based guard at the other end of the pipe: when a new xDS stream sends its first
DiscoveryRequest, the control plane registers the client (kicking off per-client translation) and then sleeps briefly — default 1s, tunable viaKGW_XDS_FIRST_CONNECT_DELAY,0or negative disables — before returning from theOnStreamRequestcallback.Why that placement works (verified against the pinned go-control-plane,
pkg/server/sotw/v3/ads.go): the server invokesOnStreamRequestbefore creating the stream's first watch/subscription, so delaying the callback's return delays the first response — and each stream's requests are processed on that stream's own goroutine, so the sleep delays only this client. The sleep runs after the registration lock is released and fires at most once per stream (guarded by a new-stream flag from client registration). The delay value lives in an atomic so the test override can't race live stream goroutines. By the time the first watch opens, per-client translation has had a head start, so the first snapshot the client observes is almost always fully converged.Notes on scope of the delay: it applies to every new stream, including a warm Envoy reconnecting after a controller restart — that proxy keeps serving its existing config during the delay and merely receives its first post-reconnect update up to one grace period later. Other clients and translation are unaffected.
The trade-off, quantified
This restores the original (pre-#13868) reconnect race in the tail case where per-client translation takes longer than the grace period. It manifests as a brief, self-healing burst of NC/500s rather than the gate's indefinite starvation. Field data from an affected user (5,000 req/s, ~1,500 failed requests during a controller restart) puts the pre-gate race window around 300ms; the 1s default covers that with margin. Environments with much larger unique-client fan-out × backend counts can raise the delay; the fan-out itself is being addressed separately (#14343 base+overlay restructuring;
DISABLE_POD_LOCALITY_XDSas an immediate lever).Commit structure (please review commit-by-commit)
Four commits, ordered so the mechanical reverts don't obscure the reviewable delta. Every commit compiles and passes its tests on its own (the reverts leave no dangling references — the
BlackholeClusterNameconstant-ification in the third commit replaces string literals that exist at the second).git revert. proxy-syncer bug fix, but for a bug that never actually happens #13958's empty-cluster substitution existed only to serve the gates; without them it would publish a near-empty state-of-the-world CDS to a new client. Reverted first since it was built on top of fix: remedies a reconnect-time xDS race #13868.git revertof the readiness gates, theReferencedClustersproto-walk machinery, and their tests.fix: delay first xDS response per new stream instead of gating snapshots(+402/−26 across 9 files) — the core change:pkg/krtcollections/uniqueclients.go: atomic-backed and env-tunable, slept once per new stream (guarded by a new-stream flag from client registration) after registration kicks off per-client translation, outside all locks, with a test shim (export_test.go) and delay-zeroing inTestUniqueClients;wellknown.BlackholeClusterNameand constant-ifies its uses (kept from fix: remedies a reconnect-time xDS race #13868 as standalone cleanup);perclient.goand the no-op Delete branch comment inproxy_syncer.goto record the fix: remedies a reconnect-time xDS race #13868 -> v2.3.0 upstream connect failures bug #14184 history (including the pre-existing SnapshotCache leak for departed clients) instead of restoring the 2024-era comment text.test(e2e): pin anti-starvation properties of per-client xDS publication(+428/−20 across 10 files) — the test strategy for the delay:TestMain;TestFirstConnectDelayGatesFirstRequestPerStream) pinning the delay's five properties: the first request of a new stream absorbs the grace period; registration (which kicks off per-client translation) precedes the sleep; follow-up requests on the same stream do not sleep; a new stream sleeps again; zero disables. This is where the delay's behavior is verified — not the slow live-ADS setup suite;pkg/kgateway/setuptests now run with the delay zeroed viaTestMain(unless the environment overrides it): the delay is a mitigation, not a correctness requirement, so those eventually-consistent assertions must hold against the raw reconnect race the delay merely narrows — running at zero both surfaces bugs the grace period would mask and cuts the suite from ~226s to ~86s;XdsStarvation(test/e2e/features/xds_starvation, registered and assigned to cluster-seven): the end-to-end regression pin for the starvation family, built on the credible absence shape (an ExternalName Service, which never produces EndpointSlices). With that never-ready reference in the config, the valid route still serves, a gateway rollout's fresh pods become Ready instead of crash-looping, and a route created after a controller restart still reaches the reconnected proxies. Each of these deadlocked under the reverted gates;kind-create's cluster-existence guard usedgrep $(CLUSTER_NAME), which false-matches any cluster whose name merely contains the target (e.g.kind-foomatcheskind), silently skipping creation and letting subsequent kubectl steps run against whatever cluster owns the current context. Nowgrep -x.Change Type
/kind fix
Changelog
Additional Notes
BackendTLSPolicy) predates fix: remedies a reconnect-time xDS race #13868 and is unaffected: errored clusters are still excluded from CDS; commit 3 adds a regression test pinning this.KGW_XDS_FIRST_CONNECT_DELAYis the escape hatch; the structural fixes to the window itself are tracked separately ([WIP] perf: split shared vs per-client cluster state #14343,DISABLE_POD_LOCALITY_XDS).