fix(python): bound FREED_POOL_IDS to stop unbounded memory growth in … - #3039
Conversation
…GPU memory-pool fast path
|
😎 Merged successfully - details. |
|
Automated review — no issues found. I reviewed the bounded-tracking change. The Minor observation only (not blocking): 🤖 Fully automated review by Claude (Claude Code) — no human has reviewed these findings. Please verify before relying on them. Generated by Claude Code Generated by Claude Code |
|
The eviction policy is sound and neither obvious hazard materialises:
No collision with #3056 beyond a trivial textual one (adjacent lines); semantically #3056 strengthens this. No interaction with #3014. |
…t, not per-stream
|
@CapThunder19 the Trunk merge queue failed for this PR. See the Trunk merge-status comment for details. Posted as a new comment so GitHub sends an email — Trunk's sticky comment is edited in place and won't trigger a notification. |
Re-checked at The one point still open is structural and pre-existing: Generated by Claude Code |
Summary
FREED_POOL_IDS(a process-global set inapis/python/node/src/lib.rs) tracks freed GPU memory-pool buffer IDs so the DORADMA fast path can detect read-after-free. Everyfree_memory_poolcall inserted into this set, but nothing ever removed entries in normal operation the set grew for the entire lifetime of the process.Because
buffer_idis derived from a monotonic per-process counter that never repeats, a node doing register write free once per frame (the exact zero-copy GPU streaming use case this feature targets, e.g. a camera node at 30-60Hz) added one permanent entry per frame, with no upper bound. At 30fps that's ~2.6M entries/day an unbounded memory leak in exactly the long-running production pipelines dora is meant to support.This PR bounds
FREED_POOL_IDSwith a small FIFO-eviction wrapper (FreedPoolIds), capped atFREED_POOL_IDS_CAP(4096) entries, since only recently-freed buffers are relevant for read-after-free detection.Root cause
apis/python/node/src/lib.rs, previouslyFREED_POOL_IDS: LazyLock<Mutex<HashSet<String>>>insert-only, no eviction.buffer_id, which requires the samenode_id + counterpair effectively impossible during normal streaming since the counter never repeats.free_memory_pool(PINNED_POOL,TRANSIT_META,RECV_GPU_VA,RECV_CPU_SHMEM,GPU_BUF_SIZES) is explicitly cleaned up with comments calling out leak-avoidance this one was missed.Fix
Replaced the plain
HashSet<String>withFreedPoolIds, a small struct pairing aHashSet<String>(O(1) membership) with aVecDeque<String>(insertion order), evicting the oldest entry once the set exceedsFREED_POOL_IDS_CAP. No new dependencies, no protocol/API changes scoped entirely to this one file.Testing
Added
freed_pool_ids_tests(unit tests, co-located with the struct, matching this file's existingseqlock_testsconvention):insert_past_cap_does_not_grow_unboundedregression test for the leak: inserting 4x the cap keepslen()bounded at exactly the cap.insert_past_cap_evicts_oldest_firstconfirms FIFO eviction (oldest gone, newest retained).duplicate_insert_is_idempotentre-inserting a tracked ID doesn't inflate the count or eviction queue.remove_drops_membership_and_order_entrycovers the existing tombstone-clear path and re-insertion after removal.#3032