Repository navigation
Description
Describe the bug
On kcp v0.33.1, one loss of the kcp-controllers lease stops ClusterCachedResource replication.
The process keeps running. Writes never reach the cache server. Status stays Ready with
ReplicationStarted=True. Only a process restart recovers it.
After re-election, every reconcile fails:
handler {…} was not added to shared informer because it has stopped already
Each failed retry leaks one event handler. A single write then logs queuing object N times.
Nothing is replicated.
A second trigger is fully silent: delete and recreate the replicated CRD. Replication stops with no
log line and no error.
We first hit this in our deployment on v0.32.5 (CachedResource): three stalls in two days. The logs
show both signatures (evidence/10-stall-2.txt, evidence/20-stall-3.txt). main has the same
code as v0.33.1.
Steps To Reproduce
Standalone v0.33.1 shard, embedded etcd and cache server:
kcp start --feature-gates=CacheAPIs=true --enable-leader-election=true- In a workspace, create a cluster-scoped CRD
widgets.widgets.example.com, oneWidget, and a
ClusterCachedResource forwidgets.example.com/v1alpha1/widgets. - Annotate the Widget. The cache copy updates at
/services/cache/shards/root/clusters/<cluster>/apis/widgets.example.com/v1alpha1/widgets/<name>.
The log shows onequeuing objectfromlocaland one fromglobal. - Take the lease away. A missed 5 s renew does the same.
The shard logs
kubectl --server …/clusters/system:admin -n kube-system patch lease kcp-controllers --type=merge \ -p '{"spec":{"holderIdentity":"intruder","renewTime":"<now>","leaseDurationSeconds":60}}'stopped leading, thenstarted leadingabout 60 s later. - Every reconcile now fails with
stopped already. Retries back off and never succeed. They were
still failing after 22 min. - Annotate the Widget again. The cache copy does not update. The write logs N
queuing object
lines, allfrom="local". N equals the failed retries so far (13–14 in the first minute).
Status staysReady.
Reproduced 2/2. A restart recovers it. Details are in evidence/40-repro.txt.
Silent variant: delete the CRD and wait for removing dynamic informer for gvr. Recreate the CRD
and Widget, then write. Nothing replicates, and nothing is logged.
Expected Behaviour
- A lease loss does not break replication.
- Global informers belong to a running factory, not to one reconcile's context.
- A controller whose informer stops is re-created.
- Handlers are removed on every exit path.
ReadyandReplicationStartedare set after sync, and cleared when replication stops.
Additional Context
The paths below are v0.33.1, under pkg/; evidence/30-source.txt has more detail.
R = reconciler/cache/clustercachedresources.
Trigger
server/server.go:223buildsCachePartialMetadataDDSIF, andserver/controllers.go:2015
passes it as the Global factory.- Nothing starts that factory. Only
PartialMetadataDDSIFgetsStartWorker(server.go:682). - So the Global informer's only
Runisgo replicated.Global.Run(ctx.Done())
(R/clustercachedresources_reconcile_replication.go:131). - That
ctxisleaderElectionCtx(server.go:870), andRenewDeadlineis 5 s (server.go:859). - On lease loss the informer stops but stays in the factory's map. After re-election,
ForResourcekeeps returning it. - Every ClusterCachedResource on the shard breaks at once.
- Local informers usually survive. Their factory already started them, so the reconcile's
Run
does nothing.
Defects
- Handlers leak (
R/replication/replication_controller.go:101-131).NewControlleradds the Local handler, then the Global one. OnlyStartremoves them
(:153-158).- When the Global add fails, the Local handler stays registered. It fills a queue that has no
workers. That is one leak per retry. - The
WaitForCacheSyncfailure path (R/clustercachedresources_reconcile_replication.go:142-145)
also skipsStart, so it leaks both handlers.
- Registered controllers are never re-checked (
R/clustercachedresources_reconcile_replication.go:70-74).- A controller is built only when the registry has none.
- When the GVR leaves discovery, the local factory drops its informer
(informer/informer.go:691-699). - The registered controller keeps the dead informer. This is the silent variant.
- Status is set before sync (
R/clustercachedresources_reconcile_replication.go:133-136).WaitForCacheSync(:142) has no timeout.- Nothing clears the status when replication stops.
Other versions
- main (373d921): byte-identical to v0.33.1 in these files.
- v0.32.5 (our deployment): same bug, reproduced 2/2. Its handler,
{0x36c2ee0 {0x36c3080 0x36c2fe0 0x36c2f60}}, matches stall 2 exactly.
No existing issue found. #3812 (flake: TestCachedResources) never loses the lease. It may share
defect 1 or 2.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Activity