From 8f49442f280aae5b7772b76199ec7247e3be5611 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Mon, 3 Jul 2023 08:38:38 -0700 Subject: [PATCH] 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. --- src/lib.rs | 7 +++++++ src/private.rs | 1 + src/window.rs | 45 ++++++++++++++++++++++++++------------------- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f67dee5..017f52c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, diff --git a/src/private.rs b/src/private.rs index 96f0e8b..c55989f 100644 --- a/src/private.rs +++ b/src/private.rs @@ -28,6 +28,7 @@ pub enum AppMessage { open_sender: mpsc::SyncSender, OsError>>, }, CloseWindow(WindowId), + WindowPanic(WindowId), } #[derive(Debug)] diff --git a/src/window.rs b/src/window.rs index e3f9313..a756df8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -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(&mut self, context: Behavior::Context) + fn run_with(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(&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.