Repository navigation
fix(store): charge the path_json fallback and pin the hash-migration accounting (#202) - #209
Conversation
Hash migration: a test merges two duplicate transmissions, evicts both and asserts trackedBytes returns exactly to the live ballast, which cannot hide a double credit behind the clamp at zero. It passes on master: the in-memory merge branch in migrateContentHashesAsync is dead code (it tests u.newHash, set on a loop copy), so the duplicates keep their own observations and each is credited once. The test guards the accounting if that branch is ever made live. path_json fallback: tests for the five ways a fallback relay reaches the store (Load, LoadChunked, background fill, IngestNewFromDB, IngestNewObservations). They fail on master: byNode/nodeHashes entries the fallback adds are not charged to trackedBytes, and eviction never removes them, so evicted transmissions stay pinned. Relates to #202. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…202) indexObservationRelayHops fed fallback relays (observations without a persisted resolved_path) into byNode and nodeHashes without charging trackedBytes, and eviction never removed those entries, so evicted transmissions stayed pinned in byNode. addFallbackRelay now charges each new (relay, tx) pair, records it in a per-tx fallbackByNode map and txChargedBytes adds the record's allowance, so eviction removes the entries and credits exactly what was charged. No StoreTx field is added (still 320 bytes) and nothing new runs per packet beyond one map lookup per added relay. hash_migrate.go: the in-memory merge branch, which would have copied the duplicate's observations to the survivor while the duplicate stayed in s.packets (a double credit at eviction), never ran: it tested u.newHash, which was cleared on a loop copy. Remove the dead branch and the dead marker; behaviour is unchanged and the new test keeps the accounting pinned. acct113Sum, the shared accounting invariant, gains the fallback term. Relates to #202. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With CORESCOPE_PERF_202=<runs> the tests time the path_json fallback Load and eviction and the hash-migration merge, and compare the fallback's charge with its marginal heap. They skip otherwise and use only functions that exist on master, so the same file builds against it for before/after runs. Relates to #202. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rapport — CS-MacBook PR#209 #202 — head 4073d40Status: Draft PR open, local tests green, CI pending (one check queued); leak 2 fixed, leak 1 does not exist as described (the double credit is unreachable), so there is no red-before test for it. Evidence: [T] = ran it, [A] = read in source or diff, [K] = known from earlier work or another report, not re-verified here. Base Leak 1: hash-migration double credit — not reachable on master
Leak 2: path_json fallback — real, and larger than described
Tests run [T]
Benchmark [T]Interleaved master and PR binaries, 5 rounds of 7 runs each (median per round), same fixture. The tests are in the PR (
CIPending: the app's PR monitor shows 1 check queued, 0 passing, 0 failing, PR mergeable, review required [T]. I did not poll it. Nothing in CI has been seen yet. Remaining
|
Review — CS-Macmini PR#209 trackedBytes-leaks — head 4073d40Dom: APPROVE med nits Both claims hold. Leak 1 cannot be reached, and removing the dead branch changes no behaviour. Leak 2 is fixed correctly on all five entry paths, with no double removal and no references left behind after eviction. The nits are an unpinned cost constant and a separate pre-existing hash-migration issue, which I propose to file as its own issue. Evidence: [T] = ran it, [A] = read in source or diff, [K] = taken from the author's report or earlier work, not re-verified here. This review was read-only. Findings
No blocking defects found. 1. Leak 1: is the merge branch dead?
2. Leak 2: correctness on the five entry paths
3. Interaction with #182 and #190
4. Performance (rule 0)Interleaved run on this machine: master and merged-tree test binaries, the PR's
5. Tests, mutants
6. Rules
CIRun
CI ran the PR head, not the merged tree. The merged tree was covered locally by the Not verified
|
Relates to #202 (and #113, #164, #198).
Draft. Both leaks from #202 are covered, but the first one is not what the issue describes, so read that part before reviewing.
1. Hash migration: the double credit is not reachable
migrateContentHashesAsynchas an in-memory branch that would copy a merged duplicate's observations to the survivor while the duplicate stays ins.packets, which would credit them twice at eviction. That branch never runs: it is selected byu.newHash == "", but the marker is set onu, the copy of the element made byfor _, u := range updates, soupdateskeeps the real hash. On master a merged duplicate stays in memory with its own observations (probe on a two-duplicate store: both transmissions keep their observation,packets=2), and each observation is credited once. This is the legacy ghost-row behaviour the existing characterization test already pins.TestHashMigrationMerge_EvictionCreditsEveryObservationOnce_202merges two duplicates, evicts both and asserts thattrackedBytesequals a live ballast transmission exactly, so the clamp at 0 cannot hide a double credit. It also checkstrackedBytesagainst the live charges right after the merge. It passes on master, so there is no red-before for this leak.s.packetsand every index) is the separate change the existing characterization test calls for, and it changesQueryPacketscardinality, so it is out of scope here.2. The
path_jsonfallback: real, and a bit bigger than the issue saysWithout a persisted
resolved_path,indexObservationRelayHops(used byLoad,scanAndMergeChunk,IngestNewFromDBandIngestNewObservations) and the merge step ofloadChunk(the background fill) added relays tobyNodeandnodeHasheswithout chargingtrackedBytes. Eviction also never removed those entries, because it only knows decoded pubkeys and theresolved_pathpubkeys read from SQL. So an evicted transmission stayed pinned inbyNode/nodeHashesfor ever (an emptied store still held 256 nodes in the tests).addFallbackRelaycharges each new (relay, tx) pair and records it ins.fallbackByNode, a per-transmission map likepathHopResolved.txChargedBytesaddsfallbackRelayBytes(len(record)), a function of the count alone, so eviction credits exactly what was charged, and removes the entries frombyNode/nodeHashesusing the record.StoreTxis unchanged (320 bytes, layout tests unchanged). No newmap[string]interface{};cmd/serverstays read-only.Load,LoadChunked, background fill,IngestNewFromDB,IngestNewObservations): entries are charged (red on master), eviction removes and credits (red on master), the charge equals the records exactly, partial eviction stays exact, and one call charges once per relay.acct113Sum, the shared accounting invariant, gains the fallback term.addToByNode.Performance
Interleaved master/PR runs, 5 rounds of 7 runs each (median per round), same fixture,
CORESCOPE_PERF_202tests in this PR (they skip otherwise):trackedBytesafter LoadtrackedBytesrises on stores whose observations lackresolved_path(here +25% on a store where every hop resolves through the fallback). That is memory the store really held; memory-based eviction will see it.Tests run
-race -count=3: pass.cd cmd/server && go test ./...: pass.go vet: clean.gofmt -lon the changed files: clean.sh test-all.sh: 214/214 files.CI has not been checked here (see the report comment).
Remaining
s.packetsand its indexes, re-parent its observations) is still the separate change the characterization test names.nodeHashesis keyed bytx.Hash, which the hash migration rewrites after indexing, so entries made under the old hash are not reached at eviction for migrated transmissions. Not touched here.🤖 Generated with Claude Code