Repository navigation
Memory leak in Popover, possibly other components #805
Description
Activity
NullVoxPopuli-ai-agent commented
on Sep 4, 2026 ContributorMore actionsRoot cause is in Glimmer VM, not in this addon. Any dynamic modifier whose value changes after the first render leaks its destructor. This is present in every Ember release since early 2021.
How
<Popover>hits it:<FloatingUI>yieldsfloatingas(if this.reference (modifier anchorTo this.reference)). On the first renderthis.referenceis not set, sofloatingisundefined.- The
referencemodifier setsthis.referenceafter the first render. Glimmer then re-evaluates{{@floating}}on the content element and creates theanchorToinstance. UpdateDynamicModifierOpcode.evaluateattaches that instance's destroyable to the updating opcode itself, withassociateDestroyableChild(this, destroyable).- Nothing registers that opcode as a destroyable child of its block.
vm.updateWithonly pushes it to the updating list. When the block is torn down, the opcode is not destroyed, and the destructor never runs.
The line comes from glimmer-vm commit
cb11136(February 2021).Reproduction with no addon code involved. A
<div {{state.mod}}>wherestate.modstarts asundefinedand later becomes a modifier never runs its destructor onclearRender()or when an{{#if}}around it goes false. The same modifier defined on the first render is torn down correctly.Verified fix in the
DynamicModifierappend opcode: register the updating opcode with the block before pushing it.let updateOpcode = new UpdateDynamicModifierOpcode(tag, instance, instanceRef); vm.associateDestroyable(updateOpcode); return vm.updateWith(updateOpcode);
With that patch applied to a local
ember-sourcebuild, the reproduction passes.Next steps:
- I will open a pull request on
emberjs/ember.jswith a failing test and the fix. I will link it here. - This addon can also work around the bug on unpatched Ember versions. Yield a stable modifier and pass the reference as an argument, instead of switching the yielded value from
undefinedto a modifier. A stable curried modifier whose argument goes fromundefinedto an element is torn down correctly.
NullVoxPopuli-ai-agent commented
on Sep 4, 2026 ContributorMore actionsPull request with a failing test and the fix: emberjs/ember.js#21591
- added a commit that references this issue
on Sep 4, 2026 - added a commit that references this issue
on Sep 4, 2026 - added 4 commits that reference this issue
on Sep 14, 2026
<Popover>components leak some observers after being un-rendered. The mechanism for the leak is that the anchorTo modifier cleanup fun ction does not get run when un-rendering a<Popover>, although I have no idea why.I created a branch with one commit that demonstrates the behavior. It hacks in some counting and console logs, and then implements a single test that renders and un-renders a
<Popover>and asserts that the number of times the anchorTo modifiers install code ran equals the number of times its cleanup code ran. If you run only that test, you will see it fail, saying that the count is 1, i.e. the cleanup code never ran, which you can also see in the console logs.I have no clue why the modifier's destructor is not running. Instead of
clearRender()I also tried wrapping the<Popover>in anifblock and un-rendering it that way, and it produced the same result.The effect of this is that
@floating-ui/dom's cleanup code never runs, and it leaks some observers (resize and maybe others?), causing a whole bunch of modifier/component infrastructure to leak as well (and in the case of rendering tests, the entire application instance!).