Repository navigation
fix(reactivity): clear removed subscriber links (fix #16176) - #16177
Narek-Khachikyan wants to merge 3 commits into
Conversation
Clear subscriber neighbors after unlinking so old removed links cannot retain younger effects during minor collections. Preserve dependency traversal pointers for effect and scope cleanup. Closes vuejs#16176
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
|
Thanks for taking this further :) The original invariant I was aiming for was simple:
But the current propagation algorithm cannot always satisfy that immediately, because a removed Link may still be part of the traversal state. In other words, That is why this PR now needs a second lifetime: and therefore a new mechanism: propagationDepth
unlinkedDuringPropagationConceptually, this is a small deferred-reclamation layer protecting the live traversal from Links that are already dead from the graph's point of view. I don't think that makes this PR wrong. The added tests around But it does reveal an important coupling in the representation: graph links are also being used as ephemeral execution state. Clearing stale topology therefore becomes observable to the traversal algorithm. A design where traversal continuation is captured separately before entering user/reactive code would not need dead Links to remain partially traversable. For example, the general shape would be: const next = link.nextSub
// code here may unlink `link`
link = nextor an explicit work stack containing the continuation itself, rather than later recovering it from a Link that may already have been removed. So I would view
That overlap is also why the PR has to refresh The current patch looks like a reasonable compatibility-preserving solution. I just think the need for deferred reclamation is itself useful information about the architecture, and worth keeping in mind if |
|
Graph membership and traversal continuation have different lifetimes in the current implementation. A walk can already hold the next link when user code removes it; saving the current link's successor alone does not cover removal of that saved successor. The first-onTrigger-stops-second regression protects exactly that case. This patch preserves those continuation edges only until the outermost synchronous propagation exits, with cleanup in |
You're right that saving only |
Summary
Retired links in old space can retain younger stopped effects through stale
prevSub/nextSubpointers across minor GCs, until a major GC reclaims them. Clear those subscriber pointers after removal, deferring cleanup until the outermost active propagation finishes so anonTriggercallback cannot truncate a saved traversal. Refresh subscriber heads after callbacks or computed evaluation can remove them. PreserveprevDep,nextDep, andunlink()'s returned dependency successor.Targets
minor: the affected 3.6 reactivity implementation differs from stable 3.5.Closes #16176
Evidence
onTrigger, computed evaluation, and scope resume.CI=true pnpm test-unit --maxWorkers=2passes all 288 files: 9334 passed, 2 expected fail, 3 skipped, 17 todo. Explicit GC run: 5 passed, 2 existing todo, no skips; the minor-GC regression ran.Merge Danger
Reversible internal reactivity change; no exported API change. Cleanup adds work to propagation. The final default 30 ms computed timing guard passed, but earlier local runs failed around 35–75 ms; CI uses the unchanged 100 ms bound. No zero-overhead claim.
Earlier Node 24 deterministic coverage passed, but minor-GC capability was skipped there. No final Node 24/22 full matrix, browser/e2e, ecosystem, bundle-size, or built-declaration validation was run locally.