Skip to content

fix: memory cycle in diverts - #23

Closed
brentjanderson wants to merge 1 commit into
bladecoder:mainfrom
brentjanderson:fix-reference-cycle-leak
Closed

brentjanderson wants to merge 1 commit into
bladecoder:mainfrom
brentjanderson:fix-reference-cycle-leak

Conversation

@brentjanderson

@brentjanderson brentjanderson commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

There is an Rc<T> cycle between a Container, a Divert, back to a Container. This reliably produces a memory leak when running multiple stories in sequence or parallel (memory_leak_test helps verify this).

By caching potential targets and using Weak<T> to break the cycle, we avoid this memory leak case.

This shouldn't change the performance profile of a running story, as target references are already lazily loaded. We are just moving from the Divert struct to a shared cache with weak references.

There is an Rc<T> cycle between a Container, a Divert, back to a Container. This reliably produces a memory leak when running multiple stories in parallel (memory_leak_test covers this case).

By caching potential targets and using Weak<T> to break the cycle, we avoid this memory leak case.

This shouldn't change the performance profile of a running story, as target references are already lazily loaded. We are just moving from the Divert struct to a shared cache with weak references.
@bladecoder

Copy link
Copy Markdown
Owner

Thank you, @brentjanderson, for identifying this real reference-cycle leak and for proposing the correct core solution: caching the target container through Weak instead of retaining it through Rc. Your diagnosis and approach have been preserved, and you are credited as a co-author of the replacement change.

I am closing this PR because it can no longer be merged cleanly in its current form:

  • the repository has since moved the runtime crate from lib/ to runtime/, leaving this branch with merge conflicts;
  • the nested if let in the patch now fails the repository’s mandatory Clippy run with -D warnings (clippy::collapsible-if);
  • the regression test installs a process-wide unsafe tracking allocator and runs 1,000 complete stories. Locally it took roughly 9–13 seconds and, when demonstrating the regression, retained about 667 MB. That makes a failing test unnecessarily expensive and risks an OOM on constrained CI runners.

The replacement PR, #32, ports the fix to the current tree, satisfies the current lint rules, and replaces the allocator-based stress test with a focused ownership test that constructs the exact Container -> Divert -> Container cycle and verifies deallocation directly through Weak. It has passed CI and has now been merged.

Thanks again for the valuable report and original fix.

@bladecoder bladecoder closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants