rust-windowing / rust-windowing/winit

Creating new window shortly after dropping previous window causes crash on Windows

Open
#2,583 14 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A - needs repro DS - win32
Dominant language
Rust
Stars
6.2k
Forks
1.3k
Avg merge
2d 19h
Merged PRs (30d)
9

Description

I just updated my code regarding winit from 0.26.1 to 0.27.5 and glutin from 0.28 to 0.30.
I noticed a weird regression (at least on Windows 10):
Context:
My GUI is rendered by a plugin in a DLL, loaded by another DLL, loaded by a host, and I'm using the dynamic_reload crate to auto-reload my plugin DLL from my plugin-wrapper DLL whenever I rebuild the plugin DLL.
Before the DLL unloads, I drop the GuiState (containing winit window etc.)
Before the upgrade of winit/glutin, DLL reloading was working fine and didn't crash, with this code:

#[no_mangle]
extern "system" fn dll_deinit() {
	let state = &mut *STATE.lock().unwrap();
	state.gui = None; // Drop winit window to prevent crash when window is recreated by reloaded DLL
}

Now, whenever I drop the gui state, the window still hangs around much longer than before, so that when the reloaded DLL tries to create the new window, the old window is still there (I see it still being open), and it seems this causes the host to crash (but I don't get any panic or error (but I used logging statements to find out that it crashes during window creation: It only logs everything up to that point and then crashes). I don't have the source for the host and it's not built with debug info). It only crashes sometimes, not always, but in the majority of cases, so it ruins the dev experience.

BUT: I found a weird workaround, if I add thread::sleep(Duration::from_millis(0)); after dropping the gui state, it never crashes when the DLL reloads!
Like this:

#[no_mangle]
extern "system" fn dll_deinit() {
	let state = &mut *STATE.lock().unwrap();
	state.gui = None; // Drop winit window to prevent crash when window is recreated by reloaded DLL
	thread::sleep(Duration::from_millis(0));
}

Any number of milliseconds works as a workaround, but thread::yield_now() doesn't work, weirdly enough!

I guess this same behavior would happen in an executable (without DLLs) if you drop and recreate a winit window (because I ran into this crash before, in an executable (with no DLLs involved), when I overwrote an Option<GuiState> instead of setting it to None first).
But it didn't use to be this way, with the previous winit version the window didn't hang around after drop and so it didn't cause a crash when the reloaded DLL created its new window!
Is there any chance we can get the old behavior back? A "synchronous drop", so that drop only returns when the window was actually destroyed?

Contributor guide

Open the contributing guide

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.

Research direction

Start by reproducing the Windows 10 crash with the provided DLL reload sequence, dropping GuiState before creating the replacement window. Trace the window destruction and creation lifecycle around dll_deinit, comparing the behavior with and without thread::sleep(Duration::from_millis(0)); done means the replacement window can be created reliably without a host crash.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.