feat(fonts): bounded late-load reflow scheduler#3613
Merged
Conversation
a27d9bb to
6347e2d
Compare
When a required face loads after the gate's first-paint timeout the document must re-measure, but on a slow network a font-heavy doc's faces arrive in many waves (a probe saw ~38 over ~103s for 40 fonts) - reflowing per wave is a re-measure storm. FontReadinessGate now batches late-loaded faces through a small scheduler using a leading flush + throttled trailing (cooldown): the first late face flushes after a short quiet window, then a cooldown bounds the flush RATE to ~once per cooldown regardless of arrival spacing. Unlike a plain debounce (one flush per wave when waves are spaced apart) or a per-batch max-wait (the quiet flush resets it first), this actually bounds the slow, spaced-out case; arrivals wider than the cooldown reflow per arrival (the inherent floor / max correction lag). First paint is untouched (3s gate timeout). notifyFontConfigChanged reflows immediately AND cancels any pending batch (no double reflow); dispose cancels pending so a torn-down editor never reflows.
6347e2d to
6fe87a9
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… document swap Review follow-ups on the bounded late-load reflow scheduler: - Move the epoch bump + cache invalidation from the batched flush to the loadingdone handler so they fire immediately. Measure caches are keyed without the font epoch (fontMetricsCache: family|size|bold|italic), so deferring the explicit clear left a window where a concurrent re-measure could cache fallback metrics. Only the expensive reflow stays batched. - Reset the gate's late-load state on documentReplaced (new resetForDocumentChange) so a flush armed under the old document cannot fire a spurious reflow against the new one. - notifyFontConfigChanged now also clears the required face/family sets (shared #resetRequiredAndSeen), closing a latent redundant-reflow race. - Wrap the scheduler flush in try/finally so the cooldown always arms and a timer callback never leaks an uncaught exception. - Trim the unused flushNow / 'manual' surface; correct the epoch doc (measure caches are not epoch-keyed); make the "no loop" test drain the cooldown so it is not vacuous.
…ocument reset Two follow-ups on the scheduler review: - The scheduler flush ran in a try/finally, which armed the cooldown but still let a throwing flush escape the timer callback as an uncaught exception. Wrap it in try/catch/finally so a throw is swallowed (font readiness must not break layout) and the cooldown still arms. Add a test driving a throwing flush. - resetForDocumentChange left #lastSummary set, so an empty/no-text new document would short-circuit to the prior document's load summary. Clear it in the reset helper; add a test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on
caio/font-load-planner(PR #3612) — review that first; this diff is the scheduler only.The face-aware planner (#3612) narrows loading to the faces a document renders. This adds the other half of making a large font pack safe: it stops a font-heavy document on a slow network from triggering a full re-measure on every late font wave.
When a required face loads after the gate's first-paint timeout, the document must re-measure so it stops rendering against a fallback. On a slow link a font-heavy doc's faces arrive in many waves (a scaling probe measured ~38 over ~103s for 40 fonts) — reflowing per wave is a re-measure storm.
FontReadinessGatenow batches late-loaded faces through a small scheduler.Policy: leading flush + throttled trailing (cooldown). The first late face flushes after a short quiet window (coalescing the initial parallel batch). After any flush a
cooldownMswindow opens during which further arrivals are deferred; at its end one trailing flush drains them, then the cooldown reopens if more arrive. This bounds the flush rate to ~once per cooldown regardless of arrival spacing — unlike a plain debounce (one flush per wave when waves are spaced wider than the window) or a per-batch max-wait (the quiet flush resets it before it bites). Honest floor: arrivals spaced wider thancooldownMsreflow per arrival — you can't coalesce a wave that lands after the doc already corrected without delaying every correction by at least the gap, socooldownMsis the max correction lag.What changed
FontLateLoadReflowScheduler.ts: the quiet + cooldown policy; injectable timers;schedule()/flushNow()/cancel().FontReadinessGate:#onLoadingDonerecords the changed required-face keys and callsscheduler.schedule(...); one flush does bump-epoch + invalidate + reflow.notifyFontConfigChangedreflows immediately and cancels any pending batch (so a stale batch can't fire a second reflow right after).dispose()cancels pending work so a torn-down editor never reflows.Out of scope (unchanged)
Verified
notifyFontConfigChangedimmediate. Run in CI.