Program freezes when I try to resize the window on Windows builds. No issues happen on Mac or Linux. A snippet to reproduce, using the latest pull from the winit repo:
extern crate winit;
fn main() {
let mut events = winit::EventsLoop::new();
let window = winit::Window::new(&events).unwrap();
while true {
events.poll_events(|event| {
match event {
// Grab mouse cursor if window is focused; release otherwise
winit::Event::WindowEvent {
event: winit::WindowEvent::Focused(focused),
..
} => {
if let Err(e) = window.grab_cursor(focused) {
eprintln!("{}", e);
}
},
_ => {}
}
});
}
}
A sample stacktrace from debug calls:
[Inline Frame] demo.exe!std::sys::windows::c::SleepConditionVariableSRW() Line 78 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sys\windows\compat.rs:78)
[Inline Frame] demo.exe!std::sys::windows::condvar::Condvar::wait() Line 32 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sys\windows\condvar.rs:32)
[Inline Frame] demo.exe!std::sys_common::condvar::Condvar::wait() Line 51 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sys_common\condvar.rs:51)
[Inline Frame] demo.exe!std::sync::condvar::Condvar::wait() Line 214 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\condvar.rs:214)
demo.exe!std::thread::park() Line 885 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\thread\mod.rs:885)
demo.exe!std::sync::mpsc::blocking::WaitToken::wait() Line 81 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\mpsc\blocking.rs:81)
demo.exe!std::sync::mpsc::oneshot::Packet<core::result::Result<(), alloc::string::String>>::recv<core::result::Result<(), alloc::string::String>>(core::option::Option<std::time::Instant> self) Line 163 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\mpsc\oneshot.rs:163)
demo.exe!std::sync::mpsc::Receiver<core::result::Result<(), alloc::string::String>>::recv<core::result::Result<(), alloc::string::String>>() Line 1203 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\mpsc\mod.rs:1203)
demo.exe!winit::platform::platform::window::Window::grab_cursor(bool self) Line 397 \.cargo\git\checkouts\winit-5993d63686bf7f83\214e157\src\platform\windows\window.rs:397)
Tagging @Osspial for reference
I've looked into why this is happening, and it seems the reason is that Windows modal loops are absolutely awful in every way.
See, our Windows backend spawns a background thread to handle event processing, but grab_cursor needs to run code on that background thread to actually grab the cursor. The way that's implemented is we create a custom Winapi message to send a closure to the background thread, we have the background thread execute that closure, then that closure notifies the original thread whether the grab operation succeeded or failed. However, we handle those events by special-casing them on the event loop level instead of having windows dispatch them to some window to handle the events.
The issue is that when a user resizes a window, Winapi blocks its "get event" function and enters an internal event loop that doesn't have our special closure-executing code. The internal loop then gets our custom event, sees that there's no special Windows code to do something with it, then throws it away without executing the closure. The cursor doesn't get grabbed, the original thread doesn't get notified of a success or failure, and it blocks waiting for a response that will never come.
The solution to this is to create an invisible dummy window that receives these custom events and knows how to handle them. That way, Windows will dispatch events to it even in the modal loops and they'll always get called.
Incidentally, the EventsLoopProxy::wakeup function is implemented in a similar way and also gets its events eaten when a window gets resized! If you run the following code and resize the window, the two counters will fall out of step with eachother when you resize or move the window:
extern crate winit;
use std::thread;
fn main() {
let mut events = winit::EventsLoop::new();
let window = winit::Window::new(&events).unwrap();
let proxy = events.create_proxy();
thread::spawn(move || {
let mut y = 0;
loop {
// Modal resize loop eats these events!
println!("send {}", y);
proxy.wakeup().unwrap();
thread::sleep_ms(500);
y += 1;
}
});
let mut x = 0;
while true {
events.poll_events(|event| {
match event {
// Resizing the window causes this event to get eaten and
// the `x` counter falls out of sync with the `y` counter.
winit::Event::Awakened => {
println!("Awaken {}", x);
x += 1;
},
_ => {}
}
});
}
}
Fixing this bug will also fix that.
Is this a regression? Should this be addressed for 0.18?
@atouchet I don't think this a regression. As far as I can tell it's probably been a problem for a while, but nobody's noticed it! I'm pretty sure it's fixed by #638, but I/somebody should still write a PR to fix this while everything gets resolved around that.
Most helpful comment
I've looked into why this is happening, and it seems the reason is that Windows modal loops are absolutely awful in every way.
See, our Windows backend spawns a background thread to handle event processing, but
grab_cursorneeds to run code on that background thread to actually grab the cursor. The way that's implemented is we create a custom Winapi message to send a closure to the background thread, we have the background thread execute that closure, then that closure notifies the original thread whether the grab operation succeeded or failed. However, we handle those events by special-casing them on the event loop level instead of having windows dispatch them to some window to handle the events.The issue is that when a user resizes a window, Winapi blocks its "get event" function and enters an internal event loop that doesn't have our special closure-executing code. The internal loop then gets our custom event, sees that there's no special Windows code to do something with it, then throws it away without executing the closure. The cursor doesn't get grabbed, the original thread doesn't get notified of a success or failure, and it blocks waiting for a response that will never come.
The solution to this is to create an invisible dummy window that receives these custom events and knows how to handle them. That way, Windows will dispatch events to it even in the modal loops and they'll always get called.
Incidentally, the
EventsLoopProxy::wakeupfunction is implemented in a similar way and also gets its events eaten when a window gets resized! If you run the following code and resize the window, the two counters will fall out of step with eachother when you resize or move the window:Fixing this bug will also fix that.