Skip to content

Fix _scheduleSystemFontsUpdate assertion during non-idle scheduler phase#184332

Merged
auto-submit[bot] merged 1 commit into
flutter:masterfrom
solvejet-team:fix-system-fonts-scheduler-assertion
Apr 17, 2026
Merged

Fix _scheduleSystemFontsUpdate assertion during non-idle scheduler phase#184332
auto-submit[bot] merged 1 commit into
flutter:masterfrom
solvejet-team:fix-system-fonts-scheduler-assertion

Conversation

@solvejet-team

@solvejet-team solvejet-team commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes the assertion failure in RelayoutWhenSystemFontsChangeMixin._scheduleSystemFontsUpdate() when the system fonts notification arrives during a non-idle scheduler phase.

Problem

On web, when fonts load asynchronously (e.g. via google_fonts), the platform sends a fontsChange system message that can arrive during SchedulerPhase.midFrameMicrotasks. The _scheduleSystemFontsUpdate method asserted schedulerPhase == idle, causing:

Failed assertion: SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle

Fix

Remove the idle-phase assertion

scheduleFrameCallback is safe to call during any phase — if called mid-frame, the callback naturally runs in the next frame's transient callback phase (since handleBeginFrame snapshots and replaces _transientCallbacks before iterating)

The _hasPendingSystemFontsDidChangeCallBack flag prevents duplicate scheduling

Test

Added regression test that triggers a fontsChange platform message during transientCallbacks phase.

Fixes #151873

@github-actions github-actions Bot added the framework flutter/packages/flutter repository. See also f: labels. label Mar 30, 2026

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request modifies the RelayoutWhenSystemFontsChangeMixin to allow system font updates to be scheduled during any scheduler phase. When the scheduler is not idle, the update is now deferred using a post-frame callback to avoid assertion failures. A regression test was added to verify this behavior, and the AUTHORS file was updated. Feedback was provided to improve code conciseness by refactoring the local scheduleUpdate function into a tear-off.

Comment on lines +4701 to +4722
void scheduleUpdate() {
SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) {
assert(_hasPendingSystemFontsDidChangeCallBack);
_hasPendingSystemFontsDidChangeCallBack = false;
assert(
attached || (debugDisposed ?? true),
'$this is detached during ${SchedulerBinding.instance.schedulerPhase} but is not disposed.',
);
if (attached) {
systemFontsDidChange();
}
});
}

if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
scheduleUpdate();
} else {
// Not idle — defer to after the current frame completes.
SchedulerBinding.instance.addPostFrameCallback((_) {
scheduleUpdate();
});
}

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.

medium

For slightly better conciseness, you can adjust the signature of scheduleUpdate to accept an optional Duration argument. This allows you to pass scheduleUpdate directly to addPostFrameCallback as a tear-off, removing the need for a wrapping closure.

    void scheduleUpdate([Duration? _]) {
      SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) {
        assert(_hasPendingSystemFontsDidChangeCallBack);
        _hasPendingSystemFontsDidChangeCallBack = false;
        assert(
          attached || (debugDisposed ?? true),
          '$this is detached during ${SchedulerBinding.instance.schedulerPhase} but is not disposed.',
        );
        if (attached) {
          systemFontsDidChange();
        }
      });
    }

    if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
      scheduleUpdate();
    } else {
      // Not idle — defer to after the current frame completes.
      SchedulerBinding.instance.addPostFrameCallback(scheduleUpdate);
    }

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.

Good suggestion — applied. Using a tear-off with void scheduleUpdate([Duration? _]) is cleaner. Updated.

@solvejet-team solvejet-team force-pushed the fix-system-fonts-scheduler-assertion branch from 10e8050 to 5bd1a91 Compare March 30, 2026 09:33
@justinmc justinmc requested a review from Piinks March 31, 2026 22:19
@Piinks

Piinks commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request adds a new entry to the AUTHORS file and modifies the RelayoutWhenSystemFontsChangeMixin to handle system font updates that occur during non-idle scheduler phases, preventing assertion failures. A regression test has been included to verify the fix. Feedback suggests simplifying the logic in _scheduleSystemFontsUpdate by removing the conditional check for SchedulerPhase.idle and the use of addPostFrameCallback, as scheduleFrameCallback is safe to call during any phase and will naturally defer execution to the next frame.

Comment on lines +4692 to +4720
if (_hasPendingSystemFontsDidChangeCallBack) {
return;
}
_hasPendingSystemFontsDidChangeCallBack = true;
SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) {
assert(_hasPendingSystemFontsDidChangeCallBack);
_hasPendingSystemFontsDidChangeCallBack = false;
assert(
attached || (debugDisposed ?? true),
'$this is detached during ${SchedulerBinding.instance.schedulerPhase} but is not disposed.',
);
if (attached) {
systemFontsDidChange();
}
});

// The system fonts notification can arrive during any scheduler phase
// (e.g. midFrameMicrotasks on web when fonts load asynchronously).
// If we're not idle, defer scheduling to the next frame via a
// post-frame callback. If idle, schedule directly.
void scheduleUpdate([Duration? _]) {
SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) {
assert(_hasPendingSystemFontsDidChangeCallBack);
_hasPendingSystemFontsDidChangeCallBack = false;
assert(
attached || (debugDisposed ?? true),
'$this is detached during ${SchedulerBinding.instance.schedulerPhase} but is not disposed.',
);
if (attached) {
systemFontsDidChange();
}
});
}

if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
scheduleUpdate();
} else {
// Not idle — defer to after the current frame completes.
SchedulerBinding.instance.addPostFrameCallback(scheduleUpdate);
}

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.

medium

The logic to check for SchedulerPhase.idle and use addPostFrameCallback as an intermediary step is likely unnecessary. SchedulerBinding.instance.scheduleFrameCallback is safe to call during any scheduler phase; if called during a frame, it simply schedules the callback for the next frame's transient callback phase.

By removing the assertion and calling scheduleFrameCallback directly, you achieve the same result (executing in the next frame) with simpler code and without the potential two-frame delay that can occur if _scheduleSystemFontsUpdate is called during the postFrameCallbacks phase.

Also, the local function scheduleUpdate can be removed to keep the implementation concise.

    if (_hasPendingSystemFontsDidChangeCallBack) {
      return;
    }
    _hasPendingSystemFontsDidChangeCallBack = true;

    // The system fonts notification can arrive during any scheduler phase
    // (e.g. midFrameMicrotasks on web when fonts load asynchronously).
    // We schedule a frame callback to ensure the update happens in the
    // transient callback phase of the next frame.
    SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) {
      assert(_hasPendingSystemFontsDidChangeCallBack);
      _hasPendingSystemFontsDidChangeCallBack = false;
      assert(
        attached || (debugDisposed ?? true),
        '$this is detached during ${SchedulerBinding.instance.schedulerPhase} but is not disposed.',
      );
      if (attached) {
        systemFontsDidChange();
      }
    });
References
  1. Optimize for readability: Code is read more often than it is written. (link)

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.

Hmm, I loathe when gemini code assist contradicts itself. 😆 What do you think @solvejet-team?

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.

Haha yeah, gotta love it, This is the "future" we are worried about 😄 But the second suggestion is actually right — I dug into scheduleFrameCallback and it has no phase checks at all, it just appends to _transientCallbacks. And handleBeginFrame snapshots that map before iterating, so anything added mid-frame just runs next frame. The addPostFrameCallback detour was unnecessary overhead. Simplified!

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.

Sweet! I was like oh gosh, I think Gemini is contradicting itself. 🤣

@solvejet-team solvejet-team force-pushed the fix-system-fonts-scheduler-assertion branch from 5bd1a91 to 5af0e99 Compare April 3, 2026 04:32
Piinks
Piinks previously approved these changes Apr 6, 2026

@Piinks Piinks 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.

LGTM! I would suggest updating the PR description to reflect the updates here. This will need a second review to land, so that will be helpful to the next reviewer. :)

@Piinks Piinks added the CICD Run CI/CD label Apr 6, 2026
@Piinks Piinks force-pushed the fix-system-fonts-scheduler-assertion branch from 5af0e99 to 0350281 Compare April 7, 2026 21:58
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 7, 2026
@Piinks Piinks added the CICD Run CI/CD label Apr 7, 2026
@justinmc justinmc requested a review from Renzo-Olivares April 7, 2026 22:13

@Renzo-Olivares Renzo-Olivares 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.

LGTM, the analyzer issues need to be addressed. It looks like the added test is not formatted correctly. Also confirmed the scheduleFrameCallback behavior discussed.

@solvejet-team solvejet-team force-pushed the fix-system-fonts-scheduler-assertion branch from 0350281 to 6f0350c Compare April 8, 2026 12:59
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 8, 2026
@solvejet-team

Copy link
Copy Markdown
Contributor Author

Fixed the formatting issues and updated the PR description. Ready for another look! 🙂

@Piinks Piinks 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.

LGTM again!

@Piinks Piinks added the CICD Run CI/CD label Apr 15, 2026
@Piinks Piinks requested a review from Renzo-Olivares April 15, 2026 18:19
@Piinks Piinks added the autosubmit Merge PR when tree becomes green via auto submit App label Apr 15, 2026
@auto-submit

auto-submit Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/184332, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR.

@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Apr 15, 2026
The system fonts notification can arrive during any scheduler phase
(e.g. midFrameMicrotasks on web when fonts load asynchronously via
google_fonts). The previous implementation asserted that the scheduler
was idle, which is not guaranteed for async platform messages.

This change removes the idle-phase assertion and defers scheduling to
a post-frame callback when the scheduler is not idle, ensuring the
frame callback is only scheduled when it is safe to do so.

Fixes flutter#151873
@Piinks Piinks force-pushed the fix-system-fonts-scheduler-assertion branch from 6f0350c to d096096 Compare April 15, 2026 18:55
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 15, 2026
@Piinks Piinks added autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD labels Apr 15, 2026
@auto-submit auto-submit Bot added this pull request to the merge queue Apr 17, 2026
Merged via the queue into flutter:master with commit 7f07a47 Apr 17, 2026
86 of 87 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Apr 17, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Apr 17, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Apr 17, 2026
flutter/flutter@31f1802...8e8a194

2026-04-17 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Fix ImageInfo.isCloneOf to correctly compare scale values (fixes #184626) (#184643)" (flutter/flutter#185203)
2026-04-17 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Roll Fuchsia Test Scripts from R2EllDf4DgBXVNuiN... to dQ4PjIJB5kZFU8Y32... (#185192)" (flutter/flutter#185199)
2026-04-17 chris@bracken.jp [iOS] Migrate FlutterLaunchEngine to Swift (flutter/flutter#185151)
2026-04-17 116356835+AbdeMohlbi@users.noreply.github.com Remove unused `FlutterRunArguments.java` file  (flutter/flutter#184160)
2026-04-17 engine-flutter-autoroll@skia.org Roll Fuchsia Test Scripts from R2EllDf4DgBXVNuiN... to dQ4PjIJB5kZFU8Y32... (flutter/flutter#185192)
2026-04-17 pewpewstol@gmail.com [Impeller] Fix morphology filter asymmetric dilation/erosion (flutter/flutter#184913)
2026-04-17 nate.w5687@gmail.com `AnimationStyle` methods (flutter/flutter#182333)
2026-04-17 karan@solvejet.net Fix _scheduleSystemFontsUpdate assertion during non-idle scheduler phase (flutter/flutter#184332)
2026-04-17 154381524+flutteractionsbot@users.noreply.github.com Revert "Unpin sdk package dependencies" (flutter/flutter#185186)
2026-04-16 116356835+AbdeMohlbi@users.noreply.github.com Clarify why the `child` is nullable in `AnimatedTransitionBuilder` (flutter/flutter#182995)
2026-04-16 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from IdBT8fSMYrYSip65j... to di3JdYrdE9OFu8Iyl... (flutter/flutter#185173)
2026-04-16 1063596+reidbaker@users.noreply.github.com Add dart_skills_lint to dev/tool with test and update readme instructions for new skills authors (flutter/flutter#185033)
2026-04-16 flar@google.com [Impeller] Provide std::optional getter for stroke property (flutter/flutter#185112)
2026-04-16 katelovett@google.com Validate itemExtent with geometry in RenderSliverFixedExtentBoxAdaptor (flutter/flutter#185159)
2026-04-16 152433210+mozammal-hossain@users.noreply.github.com [iOS] Clarify provisioning profile error instructions (flutter/flutter#184051)
2026-04-16 bkonyi@google.com [Widget Preview] Fix flaky integration test timeout during flutter clean (flutter/flutter#184991)
2026-04-16 engine-flutter-autoroll@skia.org Roll Dart SDK from fbddcbe0cd96 to 7c2564c18770 (2 revisions) (flutter/flutter#185143)
2026-04-16 sigurdm@google.com Unpin sdk package dependencies (flutter/flutter#184821)
2026-04-16 68919043+Istiak-Ahmed78@users.noreply.github.com Fix ImageInfo.isCloneOf to correctly compare scale values (fixes #184626) (flutter/flutter#184643)
2026-04-16 engine-flutter-autoroll@skia.org Roll Skia from 391cdbe3ffe9 to d8415c5d7b91 (2 revisions) (flutter/flutter#185141)
2026-04-16 154381524+flutteractionsbot@users.noreply.github.com Sync CHANGELOG.md from stable (flutter/flutter#185166)
2026-04-16 154381524+flutteractionsbot@users.noreply.github.com Revert "Move widget_preview_scaffold into pub workspace" (flutter/flutter#185164)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC boetger@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
creatorpiyush pushed a commit to creatorpiyush/packages that referenced this pull request Jun 10, 2026
…r#11523)

flutter/flutter@31f1802...8e8a194

2026-04-17 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Fix ImageInfo.isCloneOf to correctly compare scale values (fixes #184626) (#184643)" (flutter/flutter#185203)
2026-04-17 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Roll Fuchsia Test Scripts from R2EllDf4DgBXVNuiN... to dQ4PjIJB5kZFU8Y32... (#185192)" (flutter/flutter#185199)
2026-04-17 chris@bracken.jp [iOS] Migrate FlutterLaunchEngine to Swift (flutter/flutter#185151)
2026-04-17 116356835+AbdeMohlbi@users.noreply.github.com Remove unused `FlutterRunArguments.java` file  (flutter/flutter#184160)
2026-04-17 engine-flutter-autoroll@skia.org Roll Fuchsia Test Scripts from R2EllDf4DgBXVNuiN... to dQ4PjIJB5kZFU8Y32... (flutter/flutter#185192)
2026-04-17 pewpewstol@gmail.com [Impeller] Fix morphology filter asymmetric dilation/erosion (flutter/flutter#184913)
2026-04-17 nate.w5687@gmail.com `AnimationStyle` methods (flutter/flutter#182333)
2026-04-17 karan@solvejet.net Fix _scheduleSystemFontsUpdate assertion during non-idle scheduler phase (flutter/flutter#184332)
2026-04-17 154381524+flutteractionsbot@users.noreply.github.com Revert "Unpin sdk package dependencies" (flutter/flutter#185186)
2026-04-16 116356835+AbdeMohlbi@users.noreply.github.com Clarify why the `child` is nullable in `AnimatedTransitionBuilder` (flutter/flutter#182995)
2026-04-16 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from IdBT8fSMYrYSip65j... to di3JdYrdE9OFu8Iyl... (flutter/flutter#185173)
2026-04-16 1063596+reidbaker@users.noreply.github.com Add dart_skills_lint to dev/tool with test and update readme instructions for new skills authors (flutter/flutter#185033)
2026-04-16 flar@google.com [Impeller] Provide std::optional getter for stroke property (flutter/flutter#185112)
2026-04-16 katelovett@google.com Validate itemExtent with geometry in RenderSliverFixedExtentBoxAdaptor (flutter/flutter#185159)
2026-04-16 152433210+mozammal-hossain@users.noreply.github.com [iOS] Clarify provisioning profile error instructions (flutter/flutter#184051)
2026-04-16 bkonyi@google.com [Widget Preview] Fix flaky integration test timeout during flutter clean (flutter/flutter#184991)
2026-04-16 engine-flutter-autoroll@skia.org Roll Dart SDK from fbddcbe0cd96 to 7c2564c18770 (2 revisions) (flutter/flutter#185143)
2026-04-16 sigurdm@google.com Unpin sdk package dependencies (flutter/flutter#184821)
2026-04-16 68919043+Istiak-Ahmed78@users.noreply.github.com Fix ImageInfo.isCloneOf to correctly compare scale values (fixes #184626) (flutter/flutter#184643)
2026-04-16 engine-flutter-autoroll@skia.org Roll Skia from 391cdbe3ffe9 to d8415c5d7b91 (2 revisions) (flutter/flutter#185141)
2026-04-16 154381524+flutteractionsbot@users.noreply.github.com Sync CHANGELOG.md from stable (flutter/flutter#185166)
2026-04-16 154381524+flutteractionsbot@users.noreply.github.com Revert "Move widget_preview_scaffold into pub workspace" (flutter/flutter#185164)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC boetger@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed assertion: line 4449 pos 7: 'SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle'

3 participants