Window panics are now handled

Prior to this commit, if a window panic happened, the window would be
left on the screen frozen. Now, the panic is caught and the window is
closed before resuming the panic.

The app event loop has a separate message it receives when a window
panics, and if it's the last window, the process exits with a non-zero
exit code.

This places the burden of handling a panicking window into the
developer's hands: ultimately if the window has a way to communicate
with it, the behavior is being dropped as part of the panic handling,
which ensures any channels the window had will be dropped too.
This commit is contained in:
Jonathan Johnson 2023-07-03 08:38:38 -07:00
parent 3f099070c2
commit 8f49442f28
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 34 additions and 19 deletions

View file

@ -6,6 +6,8 @@
mod private;
mod window;
pub use winit;
use raw_window_handle::HasRawWindowHandle;
pub use window::{RunningWindow, Window, WindowBehavior, WindowBuilder};
@ -70,6 +72,11 @@ impl PendingApp {
*control_flow = ControlFlow::ExitWithCode(0);
}
}
AppMessage::WindowPanic(window_id) => {
if self.running.windows.close(window_id) {
*control_flow = ControlFlow::ExitWithCode(1);
}
}
AppMessage::OpenWindow {
attrs,
sender,

View file

@ -28,6 +28,7 @@ pub enum AppMessage {
open_sender: mpsc::SyncSender<Result<Arc<winit::window::Window>, OsError>>,
},
CloseWindow(WindowId),
WindowPanic(WindowId),
}
#[derive(Debug)]

View file

@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::ops::{Deref, DerefMut};
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::path::PathBuf;
use std::sync::{mpsc, Arc};
use std::thread;
@ -151,7 +152,7 @@ where
// avoid a "frozen" window causing massive memory allocations, we'll use
// a fixed-size channel and be cautious to not block the main event loop
// by always using try_send.
let (sender, receiver) = mpsc::sync_channel(128);
let (sender, receiver) = mpsc::sync_channel(1024);
let Some(winit) = self.owner.open(self.attributes, sender)? else {
return Ok(None)
};
@ -159,7 +160,7 @@ where
id: winit.id(),
app: self.owner.app(),
};
let mut running_window = RunningWindow {
let running_window = RunningWindow {
messages: receiver,
app: self.owner.app(),
occluded: winit.is_visible().unwrap_or(false),
@ -297,25 +298,31 @@ impl RunningWindow {
self.modifiers
}
fn run_with<Behavior>(&mut self, context: Behavior::Context)
fn run_with<Behavior>(mut self, context: Behavior::Context)
where
Behavior: self::WindowBehavior,
{
let mut behavior = Behavior::initialize(self, context);
while !self.close && self.process_messages_until_redraw(&mut behavior) {
self.next_redraw_target = None;
behavior.redraw(self);
}
drop(behavior);
let proxy = self.app.proxy.clone();
let window_id = self.window.id();
let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || {
let mut behavior = Behavior::initialize(&mut self, context);
while !self.close && self.process_messages_until_redraw(&mut behavior) {
self.next_redraw_target = None;
behavior.redraw(&mut self);
}
// Do not notify the main thread to close the window until after the
// behavior is dropped. This upholds the requirement for RawWindowHandle
// by making sure that any resources required by the behavior have had a
// chance to be freed.
}));
// Do not notify the main thread to close the window until after the
// behavior is dropped. This upholds the requirement for RawWindowHandle
// by making sure that any resources required by the behavior have had a
// chance to be freed.
let _result = self
.app
.proxy
.send_event(AppMessage::CloseWindow(self.window.id()));
//
if let Err(panic) = possible_panic {
let _result = proxy.send_event(AppMessage::WindowPanic(window_id));
std::panic::resume_unwind(panic)
} else {
let _result = proxy.send_event(AppMessage::CloseWindow(window_id));
}
}
fn process_messages_until_redraw<Behavior>(&mut self, behavior: &mut Behavior) -> bool
@ -616,12 +623,12 @@ enum TimeUntilRedraw {
/// consumers of the libraries. This trait provides functions for each of the
/// events a window may receive, enabling the type to react and update its
/// state.
pub trait WindowBehavior: Sized + 'static {
pub trait WindowBehavior: UnwindSafe + Sized + 'static {
/// A type that is passed to [`initialize()`](Self::initialize).
///
/// This allows providing data to the window from the thread that is opening
/// the window without requiring that `WindowBehavior` also be `Send`.
type Context: Send;
type Context: Send + UnwindSafe;
/// Returns a new window builder for this behavior. When the window is
/// initialized, a default [`Context`](Self::Context) will be passed.