Repository navigation
Defer idle requests made during a period unless the requesting callback used its slice - #130
Closed
mattcosta7 wants to merge 1 commit into
Closed
mattcosta7 wants to merge 1 commit into
mattcosta7 wants to merge 1 commit into
Conversation
…ck used its slice Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The test helper can accept an inline continuation before the outer callback’s microtasks, leaving microtask ordering unverified for successful same-period attempts.
1 open issue
What changed in this PR
Refines the idle scheduling introduced in #129 to prevent cheap self-rescheduling callbacks from monopolizing an idle period, while preserving throughput for callbacks that exhaust their slice.
Changes:
- Defer requests made during a running period, except synchronous continuations from callbacks that consume their full slice.
- Rename
unpaintedtodeferredand track requests created by the running callback. - Extend tests for deferral, whole-slice continuations, polling frequency, and throwing callbacks.
| File | Description |
|---|---|
test/requestidlecallback.js |
Updates scheduling expectations and adds continuation, polling, and error-path coverage. |
src/requestidlecallback.ts |
Implements conditional deferral based on whether the requesting callback exhausted its slice. |
Review effort: Max (auto)
Comment on lines
+409
to
+418
| let continued = false | ||
| requestIdleCallback(deadline => { | ||
| exhaust(deadline) | ||
| requestIdleCallback(() => { | ||
| continued = true | ||
| }) | ||
| }) | ||
| runFrame() | ||
| await sleep(50) | ||
| if (continued) return true |
Member
Author
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.

Follow-up to #129.
Problem
Since #129, a callback registered during an idle period runs later in the same 10 ms period. A cheap callback that requeues itself, such as a poller with nothing to do, therefore runs back to back for the whole period, every frame.
github-ui hits this case. Its pull request diff loader polls an empty queue through
requestIdleCallbackfor the life of the page in virtualized and single-file views (packages/pull-requests/utils/progressive-diffs-loader.ts). On Safari, which has no nativerequestIdleCallbackand so runs this polyfill, that loop now holds about half a core.Change
Requests made while a period is running now wait for the next period, as native does. One exception remains: a request made synchronously by a callback that used its whole slice can still run later in the same period. That keeps the throughput of
while (deadline.timeRemaining() > 0) { ... }work loops, which is what #129's deviation was for.unpaintedis renamed todeferred, because it now holds both kinds of waiting request.Evidence
Playwright WebKit 26.5 runs a lab harness that installs each polyfill build before page scripts. Each figure is the median over 5 interleaved runs, with the min-max range in brackets. CPU is the WebContent process tree, sampled with
ps.lib/)await, calls/stimeRemaining()loop, 200 ms of worknpm testpasses: lint,tsc, and wtr with 131 tests. The requestidlecallback file passed 5 repeated runs. Against the oldsrc, 5 of the new or changed tests fail, as intended.Tradeoffs
timeRemaining()now get one chunk per frame: 2.5 s instead of 0.24 s in the table. That is still 3x faster than native Chrome, where such loops get one chunk per idle period. An audit of github-ui found no such loop.performance.now()has 1 ms resolution (measured), so a 1 ms threshold cannot tell a poller from a short work chunk.MessageChanneltask runs. They retry up to 5 times. A starved CI worker could still fail them.