fix: a list or watch pinned to one name agrees with Get before the node publishes - #46
Conversation
…de publishes kubectl wait --for=delete and kubectl delete read the sandbox with Get, then open a watch-list with fieldSelector=metadata.name=<name>. Get asks the nodes for a claim the NodeInventory does not hold yet; List and Watch read only the inventories. So a sandbox claimed before its node published (up to 30 s) was found by the Get, missing from the watch's initial sync, and reported deleted at once. Before the watch-list bookmark fix the same wait hung instead. A list pinned to one name in a namespace now resolves through Get. A watch with that pin starts from it, and before it reports a known entry deleted it asks the entry's node, keeping the entry while the node holds it and it still matches the selectors. An absent name polls the inventories only, so the pin adds no node traffic. Fleet lists and watches are unchanged.
The SandboxStore and ClaimIDResolver interfaces, WithClaimRouting and WithWatchPollInterval already state every fact these five godocs repeated; the lifecycle verbs in the same package carry none (Comment Style: interface-implementing methods omit godoc).
|
Hardware verification on 2026-09-25, on two bare-metal hosts with the two-host E2E kit. Every kit binary, the firmware and the running operator, webhook and vk-cocoon processes were checked by sha256 before and after each lane. Both arms ran against the same live stack. Only
After the PR arm, master was reinstalled and the version gate passed again. Report and evidence: cocoonstack/cocoon-specs |
…de publishes A read by name now takes the claim id, deadline and claim time from the node's own row, so synthAnnotations, lifecycle.md, usage.md and the lifecycle example no longer say these wait for a publish, and the example limits the lag to fleet List and Watch. scaling-design.md adds that a list or watch pinned to a name no node holds asks every node once when it opens.
Multi-line godoc and const comments become one fact per line. Comments that restated a name, a signature, the interface godoc or scaling-design.md are gone: warmCandidate, scatterGatherStore, its claim-routing fields, matchOnNode, parseSelectors, the embedded SandboxLifecycle note, and the watch cost figures the design doc already measures. The NodeInventoryGVK reason moves onto its var entry. The files drop from 82 and 146 comment lines to 32 and 59.
…ilent-node test runs under synctest heldByNode passes objKey as the claim ref that Claim and lookupName spell with namespacedName, so objKey now calls it. TestASilentNodeBoundsAMiss waited out two real 500 ms timeouts; under synctest they elapse on the fake clock, and dropping the timeout still fails it as a deadlock. unpublishedStore moves below the countingSource type it builds.
Problem
kubectl wait --for=deleteandkubectl deleteread the sandbox withGet, then open a watch-list withfieldSelector=metadata.name=<name>.Getasks the nodes for a claim the NodeInventory does not hold yet.ListandWatchread only the inventories, which nodes republish every 30 s. So for a sandbox claimed less than one publish ago, theGetfound it, the watch's initial sync was empty, and kubectl's delete precondition reported it deleted at once.The 2026-09-24 hardware round hit this:
kubectl wait --for=deleteon a fresh L3 sandbox returned rc 0 in 177 ms, about 1.8 s before the case issued the delete. Before #45 the same wait hung instead, because the watch-list never got its initial-events-end bookmark.Change
Listpinned to one name in a namespace resolves through the same lookup asGet, then applies the label and field selectors.Watchwith that pin takes its initial events from that list.A node that does not answer within 500 ms counts as a miss, the rule
Getalready follows. A pinned watch therefore agrees withGetin that case too.Cost
Get.Tests
pkg/scale/sandboxstore_live_test.go:The first two fail on master. The selector case fails when the selector check on the live answer is removed.
Gates, all with
GOWORK=off:make fmt-checkmake lint: 6 ×0 issues.make testasl -forwarder=false ./...on darwin and linuxDocs
usage.md, lifecycle.md, index.md and scaling-design.md now state that a list or watch pinned to one name in a namespace reads like
Getwhen it opens. A claim made after the watch opened still appears at the next publish.Commits
578feefthe fix.54fab65review: interface-implementing store methods drop the godoc theSandboxStoreandClaimIDResolverinterfaces already carry.284cc0areview:synthAnnotations,lifecycle.md,usage.mdand the lifecycle example no longer say a name read waits for a publish, andscaling-design.mdadds that a pinned list or watch for a name no node holds asks every node once when it opens.7285d60review:sandboxstore.goandsandboxstore_impl.gomeet the comment budget, one fact per line, dropping from 82 and 146 comment lines to 32 and 59.e88c7dfreview:objKeygoes throughnamespacedName, andTestASilentNodeBoundsAMissruns under synctest.Hardware verification
Verified on 2026-09-25 at
54fab65on two bare-metal hosts, as an A/B pair against the same live stack. SBL-08 fails on master and passes on this branch; the corrected SBL-06b passes on both. Results: #46 (comment)The later commits change comments, docs and tests;
objKeyreturns the same string as before.Not in this change
A pinned watch still re-derives the whole fleet's inventories on every tick to follow one name. #47, stacked on this PR, makes that tick read only the entry's node.