Skip to content

[BUGFIX lts] Reset tracking when the cleanup of a render error throws - #21685

Merged
NullVoxPopuli merged 1 commit into
emberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/reset-tracking-when-cleanup-throws
Oct 11, 2026
Merged

NullVoxPopuli merged 1 commit into
emberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/reset-tracking-when-cleanup-throws

Conversation

@NullVoxPopuli-ai-agent

@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #21681

A render error in a development build can leave the tracking frame open. Each later tracked write then fails with a backtracking assertion, in every later test.

Cause

VM.execute cleans up after a render error in this order:

  1. It closes the open blocks with popBlock().
  2. It calls resetTracking().

popBlock() throws when the DOM of a block is gone. That error leaves the finally block, and step 2 does not run.

Fix

The popBlock() loop is now in its own try, and resetTracking() runs in the finally of that try.

Test

render-error-cleanup-test.gjs renders into a stale cursor: the nextSibling of the cursor is no longer in the element.

The render fails at its first insert with a NotFoundError. The VM then fails again when it closes the open block. The test has no custom throw and no DOM change during the render.

Build Result
main fails, isTracking() is true
this branch passes

A real modifier error does not reach this path on main. A modifier throws during the commit, after VM.execute returns. I ran three sequences with {{on "click" undefined}} followed by more renders into the same element, and none left a tracking frame open. On 7.3.0, the reporter got to the same VM state through clearRender(), which cleared the element under a failed root.

Not in this PR

  • The cleanup error still replaces the original render error. That behavior is the same as before.
  • The renderer that renderComponent creates is not a destroyable child of the owner when the first render throws. The issue reports this as a separate leak.
  • [BUGFIX release] Reset tracking state after a render error in prod #21634 changes the same lines for production builds. One of the two PRs will need a rebase.

Relation to #21634

The two PRs fix different holes. #21634 makes the reset run in production builds. This PR makes the reset run when popBlock() throws.

I ran the test file from #21634 on this branch and on main. GlimmerishComponent is gone from main, so I used @glimmer/component in its place.

Build main this branch
development 4 pass 4 pass
production 4 fail 4 fail

This PR does not change those results. The test in this PR needs a development build, and #21634 keeps the popBlock() loop before the reset with no try.

🤖 Generated with Claude Code

@NullVoxPopuli-ai-agent
NullVoxPopuli-ai-agent force-pushed the nvp/reset-tracking-when-cleanup-throws branch from 564f6e2 to 9203d38 Compare October 10, 2026 14:37
let element = document.createElement('div');
let marker = document.createComment('marker');

element.append(marker);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE that I don't think this is supported behavior -- but if something does this, I suspect that unless we fully understand why, we just move when the error occurs

(such as those automated translation tools browsers sometimes use)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per the reported issue, it is simulating a bad behavior from a modifier in a test

@NullVoxPopuli
NullVoxPopuli marked this pull request as draft October 10, 2026 15:44
@NullVoxPopuli-ai-agent
NullVoxPopuli-ai-agent force-pushed the nvp/reset-tracking-when-cleanup-throws branch from 9203d38 to 58a36db Compare October 10, 2026 16:23
@NullVoxPopuli
NullVoxPopuli marked this pull request as ready for review October 10, 2026 16:29
@NullVoxPopuli NullVoxPopuli changed the title [BUGFIX release] Reset tracking when the cleanup of a render error throws [BUGFIX lts] Reset tracking when the cleanup of a render error throws Oct 10, 2026
@NullVoxPopuli-ai-agent

Copy link
Copy Markdown
Contributor Author

pnpm bench for 58a36dbb52 against main at b80ff765b2: no change. Tracerbench reports "no difference" for each phase. Report: tracerbench-report.pdf

The fix is inside if (DEBUG), so it is not in production builds:

  • dist/prod of ember-source is byte-identical with and without the fix.
  • The two benchmark apps have the same bundle, main-kNJHri2y.js, with the same SHA-1.

The run therefore compares identical code, and it shows the noise of the benchmark.

View Phases with a change
Tracerbench report 0 of 24
Full phase, paired, without GC 0 of 24
Script time, paired, without GC 3 of 23

The three script time hits are removeFirstRow1 (+5.1%), removeSecondRow1 (+9.5%) and swapRows1 (+6.3%). Each phase is about 100 ms at 8x. These are false hits, because the code is the same on the two sides.

How this was measured
  • ember.js pnpm bench settings: tracerbench compare on smoke-tests/benchmark-app, 8x CPU throttle, headless, fidelity 50.
  • Benchmark app change, on control and experiment alike: gc() before the first mark, and a measured finalGc phase at the end. Chrome runs with --js-flags=--expose-gc.
  • Chrome and tracerbench ran pinned to one CPU core.
  • The paired views use the median of 50 per-round ratios. A value counts as a change when its bootstrap 95% CI excludes 0 and a Wilcoxon signed-rank test gives p < 0.05.
  • "Script time" runs from the Start mark of the phase to the end of the task that holds it.
  • Raw results: bench-reports branch.

ef4
ef4 previously approved these changes Oct 11, 2026
…rows

In a development build, `VM.execute` closes the open blocks after a
render error, and then calls `resetTracking()`.

`popBlock()` throws when the DOM of a block is gone. That error left
the `finally` block before `resetTracking()` ran. The tracking frame
then stayed open, and each later tracked write failed with a
backtracking assertion.

The reset now runs in a `finally` block around the `popBlock()` loop.

Fixes emberjs#21681

Co-Authored-By: Alberto Cantú Gómez <9092644+betocantu93@users.noreply.github.com>
Co-Authored-By: Geoff Hodgson <208148+geoffroh@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NullVoxPopuli-ai-agent
NullVoxPopuli-ai-agent force-pushed the nvp/reset-tracking-when-cleanup-throws branch from 12f57d8 to 482321a Compare October 11, 2026 21:48
@NullVoxPopuli
NullVoxPopuli merged commit 713db98 into emberjs:main Oct 11, 2026
68 checks passed
@NullVoxPopuli
NullVoxPopuli deleted the nvp/reset-tracking-when-cleanup-throws branch October 11, 2026 23:51
@github-actions github-actions Bot mentioned this pull request Oct 11, 2026
ef4 added a commit that referenced this pull request Oct 13, 2026
… new §05-14 item 15)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

a render that throws can leave a global tracking frame open for the rest of the test run.

3 participants