Repository navigation
Release v0.0.316
Summary
Follow-up to #379 (merged into main, gated behind SingleWriterEnabled, default false). A review of #379 (posted after it had already merged) found two functional-breaking bugs in guaranteedUpdateSingleWriter that only surface with the flag on — this fixes both.
-
SaveContainerProfile'signoreNotFound=trueupsert against a missing key never converges.guaranteedUpdateSingleWriteralways built the commit job withcreate:false, baseRV:0for this case.commit()'s compare-and-commit rejected that unconditionally witherrWriteConflict— a nonexistent key can never make!existsfalse — so the retry loop can never succeed. This isn't an edge case: it's a container's first-ever consolidation write (ConsolidateTimeSeries→loadOrInitializeProfilesynthesizes a freshContainerProfile{}on not-found, then callsSaveContainerProfile), so with the flag on every new container's first consolidation retries for 5s, fails with a contention-timeout error, and rolls back the whole consolidation transaction — every 30s tick, forever. The legacyGuaranteedUpdateWithConn/saveObjectpath has always treated a missing key +ignoreNotFoundas a plain upsert (StorageImpl.getreturns a zero object with a nil error, not a not-found error, in that case);commit()now honors the same contract by treatingbaseRV==0(i.e. nothing existed when the caller read state) as "no compare-and-commit required," matching the old path's semantics exactly. -
Finalizer-delete updates are silently dropped. The "before" snapshot used for the no-op-update short-circuit (
orig := origState.obj.DeepCopyObject()) was taken aftertryUpdateran.genericregistry.Store's finalizer-deletetryUpdatemutates its input in place (viamarkAsDeleting) and returns that same reference, so by the time the snapshot was taken it had already observed the mutated state — makingreflect.DeepEqual(orig, ret)always true and silently no-op'ing the delete. This is exactly the bug #372 fixed on the legacyGuaranteedUpdateWithConnpath; the single-writer path reintroduced it. Fixed the same way #372 did: snapshotorigbeforetryUpdateruns, not after.
Both bugs are regression-tested directly against guaranteedUpdateSingleWriter/GuaranteedUpdate with singleWriterEnabled=true; both new tests fail against the pre-fix code and pass after.
Since the flag defaults to false, this is not urgent for anything currently running with it off, but it should land before SingleWriterEnabled is turned on anywhere (including the existing dev/stage soak, which apparently didn't exercise a brand-new container's first consolidation or a finalizer-delete, which is why neither surfaced there).
Test plan
-
go build ./...clean -
go test -race -skip TestFileSystemStorageWatchReturnsDistinctWatchers ./pkg/registry/file/...— 3x clean (the skipped test is the same pre-existing, unrelated flaky test #379 excluded; verified it fails identically on unmodifiedmain) -
go test -race ./pkg/metrics/... ./pkg/config/...clean - New tests (
TestSingleWriter_GuaranteedUpdate_IgnoreNotFound_MissingKey_Upserts,TestSingleWriter_GuaranteedUpdate_MutateInPlacePersists) verified to fail against pre-fixsinglewriter.goand pass after
🤖 Generated with Claude Code