Skip to content

[lexical] Feature: Detect infinite recursion in update listeners#8542

Merged
etrepum merged 1 commit into
facebook:mainfrom
mayrang:fix/3694-cascade-detection
May 22, 2026
Merged

[lexical] Feature: Detect infinite recursion in update listeners#8542
etrepum merged 1 commit into
facebook:mainfrom
mayrang:fix/3694-cascade-detection

Conversation

@mayrang

@mayrang mayrang commented May 22, 2026

Copy link
Copy Markdown
Contributor

Description

editor.registerUpdateListener is documented as something to "watch out for infinite loops" with, but there is no runtime detection. When a listener calls editor.update() and the resulting state change is dirty enough to fire the listener again, the chain runs synchronously through $triggerEnqueuedUpdates$beginUpdate → commit → listener → _updates.push$triggerEnqueuedUpdates ... until the browser hangs. The existing infiniteTransformCount counter resets per $beginUpdate, so it can't catch a cascade that crosses update lifecycles.

Fix

Add a per-editor _cascadeCount counter that increments in $triggerEnqueuedUpdates when the queue is non-empty and resets when the chain naturally drains or the editor is reset. Once the counter exceeds the threshold, surface a cascade-specific invariant through editor._onError and clear the pending update queue so the chain is cut even if the user's onError swallows the throw. The counter is editor-instance state rather than module-level because a listener on editor A can synchronously call editor.update() on editor B; sharing the counter would let A's cascade exhaust B's budget.

Closes #3694

Design notes

  • The try/catch wraps only the cascade-detection invariant, not the surrounding $beginUpdate call, so it doesn't intercept the deliberate re-throw from $commitPendingUpdates's reconciler-recovery path.
  • Threshold matches the existing infiniteTransformCount (99).

Test plan

  • pnpm vitest run --project unit packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx — 79 tests pass (new "Detects infinite recursivity on update listeners" alongside the existing transform-recursivity test)
  • pnpm vitest run --project unit — 2585 tests pass, no NestedEditor regression
  • pnpm tsc --noEmit -p tsconfig.json clean
  • pnpm flow clean
  • npx prettier --check clean on changed files

@vercel

vercel Bot commented May 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment May 22, 2026 3:53pm
lexical-playground Ready Ready Preview, Comment May 22, 2026 3:53pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 22, 2026
@mayrang mayrang changed the title [lexical] Feature: Detect infinite recursion in update listeners (#3694) [lexical] Feature: Detect infinite recursion in update listeners May 22, 2026
@mayrang mayrang marked this pull request as ready for review May 22, 2026 10:38
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label May 22, 2026
},
{discrete: true},
);
expect(errorListener).toHaveBeenCalledTimes(1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The update listener is not doing a discrete update so there should probably be a similar drain here to ensure it's not still going?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pushed the drain — for (let i = 0; i < 10; i++) await Promise.resolve(); after the recovery update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: infinite loop detection for updates

2 participants