test(producer): extract frameDirMaxIndexCache to its own module and pin cross-job isolation#381
Conversation
91f129b to
fe8fd9b
Compare
ae2d4a7 to
5f4e626
Compare
fe8fd9b to
253cf29
Compare
253cf29 to
faa7890
Compare
5f4e626 to
cbdf78d
Compare
faa7890 to
e4eb138
Compare
cbdf78d to
ca75d84
Compare
e4eb138 to
d781d68
Compare
jrusso1020
left a comment
There was a problem hiding this comment.
Extracting frameDirMaxIndexCache into its own module is the right move after #371 added the eviction logic — a module-scoped Map with cross-cutting add/evict/sweep semantics deserves its own seam rather than living as a top-level constant in renderOrchestrator.ts. The cross-job isolation tests are the bit I most care about: the previous lifetime-leak bug was about the cache persisting entries across renders, and the only durable way to prevent regression is to explicitly assert that a new render starts with a clean cache state relative to the previous one. A unit test with two mock render jobs where job 2 must not see job 1's entries is the right shape.
Three things worth considering for followups (non-blocking):
- Bounded-size enforcement. #371 added eviction but didn't add a size cap. If some future render path forgets to call the eviction block, the cache still self-limits at something reasonable (say, max 1000 entries with LRU eviction). Defense in depth.
- Metric emission on eviction. A Datadog counter on "cache entries evicted during render" would make it observable when something's leaking; currently the only signal of a regression is process memory climbing under sustained load.
- The cache key shape should probably include the render-job ID explicitly (not implicit via the frameDir path) — makes cross-job contamination mechanically impossible rather than just tested-against.
Approved.
— Rames Jusso
9b2eefa to
e270588
Compare
d781d68 to
fb6866c
Compare
e270588 to
445409b
Compare
fb6866c to
6cc7215
Compare
445409b to
d83c70c
Compare
6cc7215 to
c9aefa7
Compare
|
Thanks for the approval @jrusso1020. Addressed (1) directly in this PR; (2) and (3) are tracked as follow-ups. 1. Bounded-size enforcement — done in this PR. The module now enforces a hard This is documented at the top of `packages/producer/src/services/frameDirCache.ts` and pinned by three tests in `frameDirCache.test.ts`:
The cross-job isolation contract you most cared about is also pinned at the unit-test layer with three explicit tests:
2. Metric emission on eviction — follow-up. Not in this PR. The mechanical hooks are in place (the LRU eviction site in `getMaxFrameIndex` and `getMaxFrameIndexCacheSize()` for sampling), so wiring a Datadog counter is a small follow-up. Tracked in `plans/hdr-followups.md`. 3. Job-ID-scoped cache key — follow-up. Not in this PR. The current key is the `frameDir` path string, which is implicitly job-scoped because `workDir` is per-job, but you're right that an explicit `{ jobId, frameDir }` composite key would make cross-job contamination mechanically impossible rather than tested-against. Will need a small `renderOrchestrator.ts` plumbing change to thread the job ID. Tracked in `plans/hdr-followups.md`. No outstanding action items in this PR. |
|
Hi, Several LRU tests use hardcoded Severity: remediation recommended | Category: maintainability How to fix: Use tmpdir() for paths Agent prompt to fix - you can give this to your LLM of choice:
Found by Qodo code review |
d83c70c to
c4cf447
Compare
c9aefa7 to
8bda232
Compare
c4cf447 to
88f43c4
Compare
01404fa to
7f94754
Compare
736c5bc to
4502711
Compare
7f94754 to
3aaaa48
Compare
4502711 to
de981e0
Compare
3aaaa48 to
a26c7c9
Compare
de981e0 to
acc15ce
Compare
a26c7c9 to
3998701
Compare
acc15ce to
ddcef60
Compare
04f1932 to
d62dbc2
Compare
76b62e3 to
f47ae00
Compare
…in cross-job isolation
Chunk 9E. The frame-directory max-index cache lived as a private
module-scoped Map inside renderOrchestrator.ts, which made the cross-job
isolation contract added in Chunk 5B impossible to unit-test directly.
Extract the cache into packages/producer/src/services/frameDirCache.ts
behind getMaxFrameIndex / clearMaxFrameIndex / getMaxFrameIndexCacheSize
(plus a test-only __resetMaxFrameIndexCacheForTests helper). Behavior is
unchanged: callers still get the same module-scoped sharing inside a
job, and renderOrchestrator's outer finally still clears every entry it
registered so the cache cannot grow monotonically across renders.
renderOrchestrator now imports the new helpers, drops the unused
readdirSync import, and updates the inline comments to point at the new
module. Two cleanup sites that previously called
frameDirMaxIndexCache.delete now call clearMaxFrameIndex.
Add frameDirCache.test.ts (bun:test) with 11 tests covering:
- reading the max index from a populated directory
- ignoring filenames that do not match frame_NNNN.png (wrong ext,
wrong prefix, wrong case, double extension, empty index group, and
a same-named subdirectory)
- empty- and missing-directory paths returning 0 and being cached
- the intra-job invariant that subsequent readdir mutations are not
observed once a directory has been cached
- clearMaxFrameIndex forcing a re-read and returning false for paths
that were never cached
- per-directory isolation when multiple directories are registered
- the cross-job contract from Chunk 5B: the cache is empty between
well-behaved jobs, does not grow monotonically across 20 simulated
renders with 3 HDR videos each (steady-state cache size stays at 3),
and a buggy job that forgets to clear leaks exactly its own entries
rather than affecting unrelated jobs.
Made-with: Cursor
d62dbc2 to
b171d31
Compare

Summary
Extract the
frameDirMaxIndexCachefrom a private module-scoped Map insiderenderOrchestrator.tsinto its ownframeDirCache.tsmodule, then add a 11-test bun:test suite that pins the cross-job isolation contract added in Chunk 5B.Why
Chunk 9Eofplans/hdr-followups.md. The cache lived as a private Map insiderenderOrchestrator.ts, which made the cross-job isolation contract from Chunk 5B impossible to unit-test directly. Extracting it both makes the contract testable and reduces orchestrator complexity slightly.What changed
packages/producer/src/services/frameDirCache.tsexposesgetMaxFrameIndex/clearMaxFrameIndex/getMaxFrameIndexCacheSize(plus a test-only__resetMaxFrameIndexCacheForTestshelper). Behavior is unchanged: callers still get the same module-scoped sharing inside a job, andrenderOrchestrator's outerfinallystill clears every entry it registered so the cache cannot grow monotonically across renders.renderOrchestrator.ts: imports the new helpers, drops the unusedreaddirSyncimport, updates inline comments, and replaces twoframeDirMaxIndexCache.deletesites withclearMaxFrameIndex.frameDirCache.test.ts(bun:test, 11 tests) covering:frame_NNNN.png(wrong ext, wrong prefix, wrong case, double extension, empty index group, same-named subdirectory).0and being cached.clearMaxFrameIndexforcing a re-read; returnsfalsefor paths that were never cached.Test plan
frameDirCache.test.ts11/11 pass.finallyeviction.Stack
Chunk 9E of
plans/hdr-followups.md. Test-driven extraction; complements Chunk 5B.