Skip to content

fix(joint-react): keep the scheduler flushing after a callback throws - #3588

Open
samuelgja wants to merge 4 commits into
clientIO:devfrom
samuelgja:fix/react-scheduler-throw
Open

samuelgja wants to merge 4 commits into
clientIO:devfrom
samuelgja:fix/react-scheduler-throw

Conversation

@samuelgja

@samuelgja samuelgja commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • simpleScheduler is shared by every graph store on the page. If a queued callback threw, the error escaped flush() with flushing still true. Every later schedule() call then returned early, so all diagrams on the page stopped updating and the rest of that pass was lost.
  • The fix uses no try/catch and no timer. flush() queues a finishFlush microtask before running any callback, so that microtask still runs if one of them throws. It flushes whatever is still queued, or marks the scheduler idle.
  • Callbacks stay in one Set, and each is deleted just before it runs. A thrown error leaves the rest queued, and because iterating a Set also visits entries added while it runs, cascading callbacks stay in the same microtask, so they still land in one React batch.
  • The error is not caught, so it surfaces as a normal uncaught error (window.onerror and the console).

Behavior notes

  • A callback that is scheduled again while it is still waiting to run now runs once, not twice.
  • A callback that keeps re-scheduling itself still hangs the page, as it does on dev without a throw. This PR doesn't change that.

Test plan

  • yarn jest --testPathPatterns="utils/__tests__/scheduler": 10/10. "reports the error and keeps flushing, without a timer" and "runs a callback queued by the failing callback" fail on the dev scheduler.
  • yarn test in packages/joint-react (typecheck, knip, jscpd, Jest on React 19 and React 18): passes, 1173 and 1132 tests.
  • yarn lint on the changed files: 0 errors.

A callback that threw inside the flush left the shared scheduler marked as
flushing, so nothing was flushed again: every GraphProvider on the page
stopped receiving graph changes. Each callback's error is now rethrown on a
later task, and the rest of the pass still runs.
A callback that kept re-scheduling itself and throwing would otherwise hold
the flush microtask forever.
@samuelgja samuelgja added the React label Nov 3, 2026
@samuelgja samuelgja self-assigned this Nov 3, 2026
@samuelgja
samuelgja requested a balanced review from Copilot November 3, 2026 08:27

Copilot AI left a comment

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.

🟢 Approval recommended

No active review issues block the scheduler callback-error handling fix.

No open issues 🎉

What changed in this PR

Prevents a throwing callback from permanently blocking the shared @joint/react scheduler and freezing diagrams across the page.

Changes:

  • Continues processing the current batch after a callback throws, while reporting the error asynchronously.
  • Defers callbacks queued during a failing pass to a later task to prevent microtask starvation.
  • Adds regression tests and a patch changeset.
File Description
packages/​joint-react/​src/​utils/​scheduler.ts Isolates callback failures and defers queued work after a failing pass.
packages/​joint-react/​src/​utils/​__tests__/​scheduler.test.ts Tests continued scheduling, deferred errors, and yielding after self-rescheduling failures.
.changeset/​react-scheduler-throw.md Documents the scheduler fix for an @joint/react patch release.

Review effort: Balanced

A recovery microtask queued before the callbacks run finishes the flush whether or not one of them throws.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants