Skip to content

Return NotFound to WatchList clients when a shard does not serve the resource - #4384

Merged
kcp-ci-bot merged 1 commit into
kcp-dev:mainfrom
waldemar-kindler:fix/vw-empty-watch-initial-events
Sep 23, 2026
Merged

kcp-ci-bot merged 1 commit into
kcp-dev:mainfrom
waldemar-kindler:fix/vw-empty-watch-initial-events

Conversation

@waldemar-kindler

@waldemar-kindler waldemar-kindler commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A controller consuming an APIExport through its virtual workspace never syncs its informers, and never reconciles anything, if it starts before any workspace has bound that export. There is no error anywhere — it simply sits there.

DefaultDynamicDelegatedStoreFuncs' watcher answers a NotFound from the delegate with an empty watch that yields no events and stays open, added in 8d385dd so that a wildcard consumer aggregating across shards is not wedged by a shard that holds none of these objects.

A WatchList client cannot use that stream. It treats the watch as its initial list and waits for the server to close the list with an initial-events-end bookmark before considering its store synced. An empty watch sends no bookmark and never errors, so nothing makes it relist — the comment's "objects that later appear on this shard are picked up on the next relist" never happens, because there is no next relist:

reflector.go:327: awaiting required bookmark event for initial events stream,
                  no events received for 10.004121875s      (every 10s, forever)

This is easy to hit in a normal deployment: a controller starts, the APIExportEndpointSlice is still empty, the first binding appears, the controller connects and watches a resource this shard does not serve yet, and from then on its informers never sync.

The change

Surface the NotFound to WatchList clients, which retry and pick the resource up once the shard serves it. Ordinary watches are untouched and keep the empty watch.

Alternative, and a question for reviewers

The fuller fix would be for the empty watch to emit the initial-events-end bookmark itself, so a WatchList client can sync against a shard that holds nothing. That needs a resourceVersion to report as synced-at, and I did not want to invent one — if there is a right value to use here, that is the better fix and I am happy to rework this.

How it was found, and how to reproduce

Bisected from a downstream controller (fleet) whose integration suite passes on v0.32.3 and fails on v0.33.0. The variable is when the controller starts, each run in its own process:

controller starts v0.32.3 v0.33.0
after a workspace already binds the export ok ok
before any binding exists (empty endpoint slice) ok hangs, informers never sync

Reproducing it needs only that ordering: publish an APIExport, start a controller against its virtual workspace with nothing bound yet, then create a workspace that binds it. On v0.32.3 the controller engages; on v0.33.0 it logs the reflector warning above every ten seconds until it gives up.

Everything else was ruled out by measurement along the way: the client's sdk/apimachinery version (fails with both 2.32.3 and the matching 2.33.0), multicluster-provider including its unreleased main, kcp main, single-shard vs sharded topology, and every hand-made LIST/WATCH shape — including a direct WatchList request, which returns its bookmark correctly when the resource is served.

Testing

  • New unit test covers both paths: a plain watch still gets the empty watch, a WatchList request gets the NotFound. It fails without the change (expected a NotFound, got <nil>).
  • pkg/forwardingregistry package tests pass.
  • Built v0.33.0 with this change and ran the downstream suite that first surfaced it: the previously hanging ordering passes, and the full suite passes.
Return NotFound to WatchList clients when a shard does not serve the resource

@kcp-ci-bot kcp-ci-bot added dco-signoff: yes Indicates the PR's author has signed the DCO. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Sep 22, 2026
@kcp-ci-bot

Copy link
Copy Markdown
Contributor

Hi @waldemar-kindler. Thanks for your PR.

I'm waiting for a kcp-dev member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@mjudeikis

Copy link
Copy Markdown
Contributor

/ok-to-test

@kcp-ci-bot kcp-ci-bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Sep 22, 2026
…resource

A watch for a resource that is not served on this shard is answered with an
empty watch that yields no events and stays open, so that a wildcard consumer
aggregating across shards is not wedged by a shard that simply holds none of
these objects.

A WatchList client cannot use such a stream. It treats the watch as its initial
list and waits for the server to close that list with an "initial-events-end"
bookmark before it considers its store synced. An empty watch sends no such
bookmark, and never errors either, so nothing makes the client relist — the
comment's "objects that later appear on this shard are picked up on the next
relist" never happens, because there is no next relist. The client waits
forever:

  reflector.go:327: awaiting required bookmark event for initial events stream,
  no events received for 10.004121875s

That is reachable in a normal deployment. A controller consuming an APIExport
through its virtual workspace starts before any workspace has bound the export,
so the endpoint slice is still empty; when the first binding appears, the
controller connects and establishes a watch for a resource this shard does not
serve yet. Its informers never sync and it never reconciles anything, with no
error anywhere to explain it.

Surface the NotFound to those clients instead: they retry, and pick the
resource up once the shard serves it. Ordinary watches keep the empty watch and
the behaviour that was added for them.

An alternative would be for the empty watch to emit the initial-events-end
bookmark itself, which would let a WatchList client sync against a shard that
holds nothing. That needs a resourceVersion to report as synced-at, and I did
not want to invent one; if there is a right value to use here, that is the
better fix.

Signed-off-by: Waldemar Kindler <waldemar.kindler@think-ahead.tech>
@waldemar-kindler
waldemar-kindler force-pushed the fix/vw-empty-watch-initial-events branch from 47405dc to fb5540b Compare September 22, 2026 10:18
@waldemar-kindler

Copy link
Copy Markdown
Contributor Author

/test pull-kcp-lint

@mjudeikis

Copy link
Copy Markdown
Contributor

Thanks @waldemar-kindler for this. Where did you see this? feel link is not working for me. Can you just double-check this still failing on main, as we recently merged a few fixes in a similar code path to this. Is this already fixed, or is it still an issue?

@waldemar-kindler

Copy link
Copy Markdown
Contributor Author

Tried it with today's main branch.

Sorry for the internal link. Project is not published yet.

@mjudeikis

Copy link
Copy Markdown
Contributor

Ok, let see what CI says.

@mjudeikis

Copy link
Copy Markdown
Contributor

/test all

@mjudeikis

Copy link
Copy Markdown
Contributor

/lgtm
/approve

@kcp-ci-bot kcp-ci-bot added the lgtm Indicates that a PR is ready to be merged. label Sep 22, 2026
@kcp-ci-bot

Copy link
Copy Markdown
Contributor

LGTM label has been added.

DetailsGit tree hash: 530788cac86a8ed9ba58993f0b4350a96aee2f27

@kcp-ci-bot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mjudeikis

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kcp-ci-bot kcp-ci-bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 22, 2026
@mjudeikis mjudeikis added the kind/bug Categorizes issue or PR as related to a bug. label Sep 23, 2026
@kcp-ci-bot kcp-ci-bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Sep 23, 2026
@mjudeikis

Copy link
Copy Markdown
Contributor

Next time please fill the PR template as CI will not allow to merge it without it

@kcp-ci-bot
kcp-ci-bot merged commit 4ac7658 into kcp-dev:main Sep 23, 2026
17 checks passed
@waldemar-kindler

Copy link
Copy Markdown
Contributor Author

Next time please fill the PR template as CI will not allow to merge it without it

I'm so sorry. Thanks for letting me know. Will do next time and thanks for the super quick response!

iakmc added a commit to iakmc/platform-mesh that referenced this pull request Oct 12, 2026
Use kcp, kcp-front-proxy and cache-server from the ghcr.io/kcp-dev/kcp:4ac7658dd
image in the envtest suites until a v0.33.x release has kcp-dev/kcp#4384.

Retry WorkspaceType creation in the account-operator and security-operator e2e
setup. The bind permission check can lag behind a new APIExport on another shard.

Signed-off-by: Iskren <iskren@kubermatic.com>
johnandersen777 pushed a commit to publicdomainrelay/socialweb-computer-kcp that referenced this pull request Oct 23, 2026
Backend: sigs.k8s.io/multicluster-runtime v0.24.1 with the kcp
github.com/kcp-dev/multicluster-provider v0.8.0 apiexport provider,
on sigs.k8s.io/controller-runtime v0.24.1. Chosen over the client-go
fallback because these are the newest releases built on k8s.io/* v0.36
(no k8s minor bump; multicluster-runtime v0.25 needs v0.37), the
provider builds against kcp-dev/sdk v0.33.1, and kcp v0.33 creates the
default APIExportEndpointSlice per APIExport, so the provider discovers
every workspace that binds an export through the APIExport virtual
workspace, including workspaces created after the core started.

kcp v0.33.x mitigation (kcp-dev/kcp#4384, fixed only on kcp main): an
APIExport virtual workspace answers a WatchList request for an export
that no workspace binds yet with an empty watch and no initial-events
bookmark, so an informer started before the first binding never syncs.
NewManager turns the client-go WatchListClient gate off in code (once,
for the process, before the manager builds any informer), so the
runtime's reflectors use List+Watch. The gate is process wide in
client-go; the core's other reflectors fall back to List+Watch too.

internal/controllers/runtime wraps it: Manager (one multi provider, one
apiexport provider per APIExport), For[T] with a typed Reconciler[T],
Request with cluster, workspace path and name, Result.RequeueAfter,
Owns[C], Watches[R](mapFunc), Export, FieldManager (default
socialweb.computer/<controller>), Finalizer, Backoff and Workers
options, SSA status and spec apply helpers, and a per-item exponential
backoff on error. No ticker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
johnandersen777 pushed a commit to publicdomainrelay/socialweb-computer-kcp that referenced this pull request Oct 23, 2026
kcp now runs from kcp-dev/kcp main at 74e9fb9c03f7. kcp-dev/sdk
91bab06689ae (Kcp-commit f32a28e56fb9) is the sdk of that tree: no
file under staging/src/github.com/kcp-dev/sdk changed between
f32a28e56fb9 and 74e9fb9c03f7. Its pseudo-version
v0.28.1-0.20261021074416-91bab06689ae sorts below v0.33.1, so go.mod
pins it with a replace. Its kcp-dev/apimachinery/v2 commit 31b7d7f63736
is v2.33.1, and it stays on k8s.io/* v0.36.

multicluster-provider moves to main bfda949c3fd1
(v0.8.1-0.20260813225908-bfda949c3fd1): it builds against that sdk,
stays on controller-runtime and multicluster-runtime v0.24.1, retries
endpoints that are not ready yet and no longer logs whole objects at
info level.

kcp main has kcp-dev/kcp#4384 and #4376, so the runtime no longer turns
the client-go WatchListClient gate off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ntnn

ntnn commented Nov 4, 2026

Copy link
Copy Markdown
Member

/cherry-pick release-0.33

@kcp-ci-bot

Copy link
Copy Markdown
Contributor

@ntnn: new pull request created: #4546

Details

In response to this:

/cherry-pick release-0.33

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@kcp-ci-bot

Copy link
Copy Markdown
Contributor

@mjudeikis: new pull request could not be created: failed to create pull request against kcp-dev/kcp#release-0.33 from head kcp-ci-bot:cherry-pick-4384-to-release-0.33: status code 422 not one of [201], body: {"message":"Validation Failed","errors":[{"resource":"PullRequest","code":"custom","message":"A pull request already exists for kcp-ci-bot:cherry-pick-4384-to-release-0.33."}],"documentation_url":"https://docs.github.com/rest/pulls/pulls#create-a-pull-request","status":"422"}

Details

In response to this:

/cherry-pick release-0.33

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes Indicates the PR's author has signed the DCO. kind/bug Categorizes issue or PR as related to a bug. lgtm Indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants