You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(codegen): async-to-generator boxes are never cleared — completed activations retain 96% of asyncpipe's young generation #7933
96% of asyncpipe's young generation is retained by activations that have already completed.
The async-to-generator transform boxes every body local of every async function, and nothing ever frees or clears a box. Every local of every completed activation therefore remains a permanent GC root for the life of the process. On asyncpipe that is ~48,000 completed activations pinning their entire local sets.
Measurement
A temporary one-env-var probe made box roots rewrite but not mark, i.e. kept them valid without treating them as live:
134 (SIGABRT — expected; the probe is unsound by construction, it is the control)
The real live young set is ~6,600 objects. At that size a minor costs ~0.8 ms instead of 62 ms.
Supporting attribution: crate::box::scan_box_roots_mut is 92% of all pointer roots and 89.5% of root-scan time on this program. Essentially every box slot is a from-space pointer, so visiting it during CopyingMark evacuates the object it names — 93,380 of minor #1's 156,236 evacuations come straight out of box roots.
Suggested direction
Clear an activation's boxes at its terminal state — do not free them.
Clearing keeps BOX_REGISTRY monotonic and every box address permanently valid, so both perry#4898's rejection semantics and #7906's positive pointer cache are untouched. Freeing would break both.
Why this is filed rather than fixed
It is a codegen change in the async-to-generator transform, which CLAUDE.md lists as a known-weak area ("it boxes every body local into a shared mutable cell typed Any"), and the terminal-state hook has to be right for every exit path — normal return, throw, and early termination — or a live local gets cleared while still reachable. That is a silent-use-after-clear, not a crash.
Activity
perf(transform): release a completed async activation's boxed locals (#…
fix(async): release a completed plain-async activation's box cells fo…
fix(async): release a completed plain-async activation's box cells fo…
fix(async): release a completed plain-async activation's box cells fo…
fix(async): release a completed plain-async activation's box cells fo…
fix(async): release and reuse a completed plain-async activation's bo…
fix(codegen,runtime): release a frame's variable-box cells at scope exit