Skip to content

gpui_windows: Prevent paint and input starvation under sustained messages - #63489

Closed
railapex wants to merge 1 commit into
zed-industries:mainfrom
railapex:review/61469-general
Closed

railapex wants to merge 1 commit into
zed-industries:mainfrom
railapex:review/61469-general

Conversation

@railapex

@railapex railapex commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Objective

Fixes #61469.

On Windows, GPUI can keep processing queued work while a visible window stops presenting. The vsync thread invalidates the window, but Windows synthesizes WM_PAINT only after higher-priority sent, posted, and input work has drained. A continuously busy posted queue can therefore starve both pending paint and hardware input.

The original report uses held-key input. #61632 demonstrates the same synthesized-paint mechanism with high-polling mouse input. In the controlled matrix below, isolated 60 Hz key and 1 kHz mouse input are healthy on the frozen base. Saturated posted traffic is the deterministic trigger: it starves paint, and when combined with 1 kHz mouse motion it also starves the input queue. A keydown-only draw request fixes one symptom but leaves the queue-level failure.

Solution

After dispatching a normally retrieved message, the outer Windows message loop runs a bounded fairness probe at most once per millisecond:

  1. Dispatch at most one pending paint-class message.
  2. Dispatch at most four pending input-class messages.
  3. Return to the normal blocking GetMessageW path.

The one-millisecond interval is shorter than a 240 Hz frame period and caps an idle queue at 1,000 supplemental probe batches per second. Four input messages per batch gives a 1 kHz device headroom when the outer loop does not regain control exactly once per millisecond. The queued messages selected by each probe stay finite, so the fairness policy does not add an unbounded queue drain. As documented by Win32, PeekMessageW can dispatch pending nonqueued sent messages before returning; the quota is not a hard bound on callbacks or CPU work. This was measured at 1 kHz; it is not a claim about higher input rates, and the policy has an idealized selection ceiling of roughly 4,000 input messages per second under simultaneous posted saturation.

Both filtered paths retain the normal accelerator, translation, dispatch, and WM_QUIT handling. The change does not call UpdateWindow, draw synchronously from an input/vsync thread, post another per-vsync signal into the busy FIFO, or change GPUI's invalidation and request-frame path.

This follows established message-pump practice rather than introducing a new Win32 primitive. Microsoft documents both the normal queue ordering and class-filtered retrieval with PM_QS_PAINT and PM_QS_INPUT. GPUI's existing run_foreground_task pump already selects those two classes. SDL's current Windows pump independently uses a one-millisecond deadline plus a finite new-message allowance to prevent an endless drain, while Chromium interleaves bounded native-message and application-work turns to prevent posted work from starving paint and timers. The exact one-paint/four-input weighting here is the GPUI-specific policy established by the matrix below.

Sources: Win32 PeekMessageW, SDL Windows message pump, and Chromium's Windows message pump.

Testing

The source-only deterministic Windows matrix harness contains the exact six-phase fixture, isolated build stager, randomized runner, integrity gates, methodology, and reference aggregate. It excludes binaries, symbols, ETLs, raw logs, machine inventory, and capture-machine privacy policy.

The external Windows fixture keeps animation demand constant and records GPUI platform-draw attempts after message delivery. Each row ran three times in a rotated order against the same frozen base and byte-identical profiler fixture. All 12 runs passed their provenance, foreground, input-restoration, exit, and process-cleanup integrity checks.

Median rates for the deterministic saturated-posted phases:

Implementation Key + posted: present / key Mouse + posted: present / mouse Posted only: present
Frozen main 0.502 / 59.975 Hz 1.000 / 269.426 Hz 0.750 Hz
Rebased #61632 11.795 / 59.980 Hz 8.748 / 0 Hz 12.247 Hz
Paint-only fairness 144.065 / 59.985 Hz 143.941 / 0 Hz 143.963 Hz
This change 143.807 / 59.982 Hz 143.956 / 998.343 Hz 143.961 Hz

The paint-only row is intentional: it shows that selecting paint alone can restore frame cadence while the same posted queue still blocks hardware input. This change was the only row to pass all 22 predeclared presentation and input gates in all three runs.

For this change, the worst mouse-plus-posted maximum gap was 9.196 ms and the worst posted-only maximum gap was 11.354 ms. One key-plus-posted run recorded a 76.538 ms maximum gap; the corresponding control phases also had maxima up to 77.115 ms, so the data does not support claiming that every isolated gap stayed below one frame period. The rate, mean, p99, input, and integrity gates passed.

The profiler event is an internal GPUI platform-draw-attempt oracle. It does not prove that IDXGISwapChain::Present ran or succeeded. Matched ETW/PresentMon captures are the external DXGI check.

A single clean-boot A/C/D/I run produced four complete, zero-loss scheduler ETLs plus matching PresentMon DXGI data. Rates are presents/input dispatch per second; parentheses show the maximum present gap:

Implementation Control present Key + posted: present / key Mouse + posted: present / mouse Posted only: present
Frozen main 143.951 0.502 / 59.980 (3969.732 ms) 7.497 / 985.343 (521.739 ms) 28.743 (711.396 ms)
Key-handler draw request 142.972 60.720 / 59.967 (17.506 ms) 0.750 / 154.457 (3983.250 ms) 0.500 (3917.660 ms)
Rebased #61632 143.975 8.282 / 59.978 (167.848 ms) 10.997 / 3.749 (149.073 ms) 9.248 (159.190 ms)
This change 144.449 144.054 / 59.981 (8.035 ms) 143.957 / 999.948 (8.131 ms) 144.208 (7.822 ms)

Frozen main, the narrow key fix, and rebased #61632 each failed the predeclared behavior gates. This exact PR source passed every integrity, presentation, and input gate. The ETLs and matching symbols remain private and can be supplied to maintainers after manual privacy review.

Supplemental checks:

  • a saturated-queue quit probe exited normally in 113.759 ms with 9,996 queued messages; no forced kill was needed;
  • an exact-policy two-window probe kept both windows at their requested cadence, closed one window in 3.182 ms under active posted flood, kept presenting the survivor, and exited normally;
  • an integration worktree combining this policy with Windows device-loss recovery changes passed all 12 injected recovery scenarios, including two windows, final-attempt recovery, exhausted retries without abort, a new device generation, and the non-DirectComposition path; the evidence records source, platform blob, executable hashes, and scenario results.

Validation at the PR head:

  • cargo fmt --all -- --check
  • cargo check --locked -p gpui --example present_starvation
  • cargo test --locked -p gpui_windows --features test-support — 9 passed
  • script/clippy -p gpui_windows -p gpui using the repository's canonical release/all-target/all-feature configuration

Bare cargo test -p gpui_windows has an existing feature mismatch on unchanged main: crate-local cfg(test) exposes render_to_image, while the dependency build of gpui hides the corresponding trait method unless test-support is enabled. This PR does not change that test configuration.

Limits

  • Nested or modal loops owned by Windows, COM, or third-party code do not pass through this outer loop and remain outside this change.
  • run_foreground_task has its own existing local pump. It already selects one paint, drains input, and posts a task-dispatched message to return control; changing that separate reentrant path is not needed for Window stops presenting for seconds under sustained keyboard input (WM_PAINT starvation; dispatch_key_event draws without presenting) #61469 and would enlarge the regression surface.
  • Translating a selected WM_KEYDOWN can post WM_CHAR behind an existing posted backlog. This change does not promise keydown/character adjacency during an artificial saturated posted flood.
  • The included example is a focused manual paint-starvation reproduction. It posts WM_KEYDOWN plus WM_NULL; hardware-input fairness is verified by the external SendInput matrix rather than asserted by the example.

Related work

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • Fixed Windows frames and input freezing while the message queue remains busy.

@cla-bot

cla-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @railapex on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@railapex

Copy link
Copy Markdown
Contributor Author

@cla-bot check

@cla-bot

cla-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @railapex on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@cla-bot

cla-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@railapex

Copy link
Copy Markdown
Contributor Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 31, 2026
@cla-bot

cla-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@MrSubidubi MrSubidubi changed the title gpui_windows: Prevent paint and input starvation under sustained messages (#61469) gpui_windows: Prevent paint and input starvation under sustained messages Aug 31, 2026
@MrSubidubi MrSubidubi added the platform:windows happens only on Windows, not other OS label Aug 31, 2026
@reflectronic reflectronic self-assigned this Aug 31, 2026
@ChristopherBiscardi

Copy link
Copy Markdown
Contributor

Thanks! The issue here is valid, but this fix doesn't seem to be the right one. I've labelled the relevant issue as zed-team-only as I believe we need to take this on

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

Labels

cla-signed The user has signed the Contributor License Agreement platform:windows happens only on Windows, not other OS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Window stops presenting for seconds under sustained keyboard input (WM_PAINT starvation; dispatch_key_event draws without presenting)

4 participants