gpui-pre-windows: guard the VSync `RedrawWindow` loop with `IsWindow` (spurious ERROR_INVALID_WINDOW_HANDLE on window close)

Open Beginner friendly
#3,795 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
77/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
desktop

Research direction

Read src/platform.rs and inspect begin_vsync_thread, especially the loop over all_windows and its RedrawWindow call. Check how WindowsWindow::drop and WM_GPUI_CLOSE_ONE_WINDOW affect the lifetime of those handles. Add the described guard for invalid HWNDs, then confirm closing a secondary window no longer logs the error.

Written by the indexing model from the issue text.

Description

What happens

Closing a secondary GPUI window on Windows logs a spurious COM error once per frame, for a tick or two after the window is gone:

ERROR 无效的窗口句柄。 (0x80070578)      // ERROR_INVALID_WINDOW_HANDLE

It comes from the VSync thread calling RedrawWindow on an already destroyed HWND. Every close of a settings/dialog window in our app prints it, so it looks like the app is broken even though nothing is.

Why

gpui-pre-windows' begin_vsync_thread (src/platform.rs, zed's crates/gpui_windows/src/platform.rs) redraws every handle tracked in all_windows:

for hwnd in all_windows.read().iter() {
    unsafe {
        if let Some(signal_at) = signal_at {
            if IsWindowVisible(hwnd.as_raw()).as_bool() && !IsIconic(hwnd.as_raw()).as_bool() {
                ...
            }
        }
        RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE)
            .ok()
            .log_err();
    }
}

The handle is destroyed by WindowsWindow::drop → DestroyWindow, but its entry in the raw-handle list is pruned only when the WM_GPUI_CLOSE_ONE_WINDOW message that DestroyWindow posts gets processed. Between those two points the VSync thread still sees the destroyed handle, and RedrawWindow fails with ERROR_INVALID_WINDOW_HANDLE (which is then log_err'd at ERROR level).

IsWindowVisible does not help: it returns false for an invalid handle, but the RedrawWindow call is unconditional, so it is reached either way.

Fix

Skip stale handles in the VSync loop:

for hwnd in all_windows.read().iter() {
    unsafe {
        // The HWND outlives its GPUI entry for a tick or two: the raw handle is
        // pruned only after WM_DESTROY is processed. RedrawWindow on a destroyed
        // handle logs a spurious ERROR_INVALID_WINDOW_HANDLE; skip it.
        if !IsWindow(Some(hwnd.as_raw())).as_bool() {
            continue;
        }
        ...

A more thorough fix would be to remove the handle from all_windows in WindowsWindow::drop (or in the DestroyWindow path) so the VSync thread never sees a stale handle at all; the IsWindow check above is the minimal, allocation-free version we have been carrying.

Notes
  • Verified against gpui-pre-windows 0.3.10 and 0.3.11 (zed f816e1f). The same loop in zed main (crates/gpui_windows/src/platform.rs) is still unguarded, so this may be worth a PR upstream as well.
  • IsWindow is already in scope there (UI::WindowsAndMessaging::*), windows 0.62 signature: IsWindow(Some(hwnd.as_raw())).as_bool().
  • We vendor gpui-pre-windows with this one-line guard (diff and repro notes in https://github.com/qiuzhanghua/qcode/blob/main/vendor/gpui-pre-windows/README.qcode.md); once this is fixed upstream we can drop the fork.
Dominant language
Rust
Stars
18.9k
Forks
1.2k
Avg merge
23h 2m
Merged PRs (30d)
259

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from longbridge/gpui-kit

All issues in longbridge/gpui-kit

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.