Skip to content

fix: end a watch-list stream's initial events with the initial-events-end bookmark - #45

Merged
CMGS merged 2 commits into
masterfrom
fix/watchlist-initial-events
Sep 24, 2026
Merged

CMGS merged 2 commits into
masterfrom
fix/watchlist-initial-events

Conversation

@CMGS

@CMGS CMGS commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

What

kubectl v1.37's delete and wait --for=delete, and any client-go reflector with WatchListClient, open the sandboxes watch as a watch-list stream (sendInitialEvents=true&allowWatchBookmarks=true&resourceVersionMatch=NotOlderThan). They read the initial state only after the k8s.io/initial-events-end bookmark. k8s.io/apiserver hands that request straight to sandboxREST.Watch. For etcd-backed resources the cacher synthesizes the bookmark; here nothing did.

On master the flag never reaches the store: toScaleListOptions copies only the namespace and the selectors into scale.ListOptions. runWatch then sends the initial Added events and moves on to its 1 s poll, with no bookmark. The client's initial sync never completes.

The changes:

  • scale.ListOptions gains WatchList. toScaleListOptions sets it under the same condition the apiserver uses to recognise a watch-list request (isListWatchRequest).
  • runWatch sends a Bookmark annotated k8s.io/initial-events-end: "true" after the initial Added events.
  • Plain watches are unchanged.
  • docs/usage.md states when a delete returns and what a watch-list request gets.

Evidence

Two-host E2E on 2026-09-24 against sandbox-operator 1a4fadb, on the L3 path through the aggregated apiserver.

  • kubectl delete sandbox e2el3a never returned after the release succeeded.
  • sandboxd no longer listed the claim, and kubectl get returned NotFound.
  • kubectl wait --for=delete -v=8 shows the watch-list request, answered with 200.
  • kubectl logged awaiting required bookmark event for initial events stream every 10 s for 49 minutes and did not recover when the 583 s server watch timeout passed. The process was killed by hand.

Tests

  • TestScatterGatherWatch_EndsTheInitialEventsWithABookmarkForAWatchList (under testing/synctest): a watch-list stream gets Added, Added, Bookmark, the bookmark carrying the annotation; a plain watch gets Added, Added and nothing else. It fails without the runWatch change.
  • TestToScaleListOptions_CarriesWatchListMode: the request-to-store mapping, including sendInitialEvents without bookmarks and sendInitialEvents=false.

Cost

One extra event at the start of a watch-list watch. Nothing on the claim or release path.

Gates

  • GOWORK=off make fmt-check: clean.
  • make lint (every target OS plus the tagged harnesses): 0 issues ×6.
  • make test (race): green.
  • asl -forwarder=false ./... on darwin and linux: 0 findings. The two advisory forwarder findings in pkg/envdproxy also exist on master.
  • Comment lines against master: +1 / −4.

…-end bookmark

kubectl v1.37 (delete, wait --for=delete) and any client-go reflector with
WatchListClient open the sandboxes watch with sendInitialEvents=true and wait
for the k8s.io/initial-events-end bookmark before they read the initial state.
toScaleListOptions dropped the flag and runWatch never sent the bookmark, so
the initial sync never completed: a `kubectl delete sandbox` whose release had
already succeeded blocked until the server ended the watch.

The store now carries the watch-list mode, emits the bookmark after the
initial Added events, and ends a watch-list stream whose initial list failed
instead of presenting the failure as an empty initial state, so the client
retries. Plain watches are unchanged.
…nctest the bookmark test

The store field is on only for a watch-list request and never stops the
initial Added events, so it is WatchList, not SendInitialEvents. The early
return on a failed initial List is dropped with its test and stub: the served
store reads NewInventoryCache, which syncs before it returns, so that List
cannot fail. The bookmark test runs under testing/synctest instead of real
waits, and the docs say the bookmark ends the initial events, not the stream.
The comment on toScaleListOptions and the second ListOptions godoc line
restated the code.
@CMGS
CMGS merged commit d14d801 into master Sep 24, 2026
2 checks passed
@CMGS
CMGS deleted the fix/watchlist-initial-events branch September 24, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant