macOS: Remove panic wrapper (#4147)
This is unnecessary nowadays, unwinding in CF observer callbacks is safe (and is safe in Rust after the introduction of `extern "C-unwind"`). Panicking elsewhere (such as in NSNotificationCenter callbacks or delegate methods) _may_ still lead to an abort, if AppKit tries to catch it with libc++, since Rust panics are not compatible with those. That's "just" a quality-of-implementation detail of current Rust though, not an inherent limitation, and should really be solved in rustc.
This commit is contained in:
parent
f51a470872
commit
3e50911adb
3 changed files with 23 additions and 156 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use std::cell::{Cell, OnceCell, RefCell};
|
use std::cell::{Cell, OnceCell, RefCell};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ use winit_core::window::WindowId;
|
||||||
|
|
||||||
use super::super::event_handler::EventHandler;
|
use super::super::event_handler::EventHandler;
|
||||||
use super::super::event_loop_proxy::EventLoopProxy;
|
use super::super::event_loop_proxy::EventLoopProxy;
|
||||||
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
|
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop};
|
||||||
use super::menu;
|
use super::menu;
|
||||||
use super::observer::{EventLoopWaker, RunLoop};
|
use super::observer::{EventLoopWaker, RunLoop};
|
||||||
|
|
||||||
|
|
@ -309,13 +309,9 @@ impl AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by RunLoopObserver after finishing waiting for new events
|
// Called by RunLoopObserver after finishing waiting for new events
|
||||||
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
pub fn wakeup(self: &Rc<Self>) {
|
||||||
let panic_info = panic_info
|
|
||||||
.upgrade()
|
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
|
||||||
|
|
||||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||||
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
if !self.event_handler.ready() || !self.is_running() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,15 +337,11 @@ impl AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by RunLoopObserver before waiting for new events
|
// Called by RunLoopObserver before waiting for new events
|
||||||
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
pub fn cleared(self: &Rc<Self>) {
|
||||||
let panic_info = panic_info
|
|
||||||
.upgrade()
|
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
|
||||||
|
|
||||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||||
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
||||||
// we're about to return to the `CFRunLoop` to poll for new events?
|
// we're about to return to the `CFRunLoop` to poll for new events?
|
||||||
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
if !self.event_handler.ready() || !self.is_running() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
use std::any::Any;
|
use std::rc::Rc;
|
||||||
use std::cell::Cell;
|
|
||||||
use std::fmt;
|
|
||||||
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
|
|
||||||
use std::rc::{Rc, Weak};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -36,42 +32,6 @@ use super::observer::setup_control_flow_observers;
|
||||||
use crate::platform::macos::ActivationPolicy;
|
use crate::platform::macos::ActivationPolicy;
|
||||||
use crate::platform_impl::Window;
|
use crate::platform_impl::Window;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PanicInfo {
|
|
||||||
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for PanicInfo {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("PanicInfo").finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING:
|
|
||||||
// As long as this struct is used through its `impl`, it is UnwindSafe.
|
|
||||||
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
|
|
||||||
impl UnwindSafe for PanicInfo {}
|
|
||||||
impl RefUnwindSafe for PanicInfo {}
|
|
||||||
impl PanicInfo {
|
|
||||||
pub fn is_panicking(&self) -> bool {
|
|
||||||
let inner = self.inner.take();
|
|
||||||
let result = inner.is_some();
|
|
||||||
self.inner.set(inner);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Overwrites the current state if the current state is not panicking
|
|
||||||
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
|
|
||||||
if !self.is_panicking() {
|
|
||||||
self.inner.set(Some(p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
|
|
||||||
self.inner.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ActiveEventLoop {
|
pub struct ActiveEventLoop {
|
||||||
pub(super) app_state: Rc<AppState>,
|
pub(super) app_state: Rc<AppState>,
|
||||||
|
|
@ -183,7 +143,6 @@ pub struct EventLoop {
|
||||||
app_state: Rc<AppState>,
|
app_state: Rc<AppState>,
|
||||||
|
|
||||||
window_target: ActiveEventLoop,
|
window_target: ActiveEventLoop,
|
||||||
panic_info: Rc<PanicInfo>,
|
|
||||||
|
|
||||||
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
|
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
|
||||||
// the system instead cleans it up next time it would have posted a notification to it.
|
// the system instead cleans it up next time it would have posted a notification to it.
|
||||||
|
|
@ -259,14 +218,12 @@ impl EventLoop {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let panic_info: Rc<PanicInfo> = Default::default();
|
setup_control_flow_observers(mtm);
|
||||||
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
|
|
||||||
|
|
||||||
Ok(EventLoop {
|
Ok(EventLoop {
|
||||||
app,
|
app,
|
||||||
app_state: app_state.clone(),
|
app_state: app_state.clone(),
|
||||||
window_target: ActiveEventLoop { app_state, mtm },
|
window_target: ActiveEventLoop { app_state, mtm },
|
||||||
panic_info,
|
|
||||||
_did_finish_launching_observer,
|
_did_finish_launching_observer,
|
||||||
_will_terminate_observer,
|
_will_terminate_observer,
|
||||||
})
|
})
|
||||||
|
|
@ -306,15 +263,6 @@ impl EventLoop {
|
||||||
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
|
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
|
||||||
self.app.run();
|
self.app.run();
|
||||||
|
|
||||||
// While the app is running it's possible that we catch a panic
|
|
||||||
// to avoid unwinding across an objective-c ffi boundary, which
|
|
||||||
// will lead to us stopping the `NSApplication` and saving the
|
|
||||||
// `PanicInfo` so that we can resume the unwind at a controlled,
|
|
||||||
// safe point in time.
|
|
||||||
if let Some(panic) = self.panic_info.take() {
|
|
||||||
resume_unwind(panic);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.app_state.internal_exit()
|
self.app_state.internal_exit()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -370,15 +318,6 @@ impl EventLoop {
|
||||||
self.app.run();
|
self.app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// While the app is running it's possible that we catch a panic
|
|
||||||
// to avoid unwinding across an objective-c ffi boundary, which
|
|
||||||
// will lead to us stopping the application and saving the
|
|
||||||
// `PanicInfo` so that we can resume the unwind at a controlled,
|
|
||||||
// safe point in time.
|
|
||||||
if let Some(panic) = self.panic_info.take() {
|
|
||||||
resume_unwind(panic);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app_state.exiting() {
|
if self.app_state.exiting() {
|
||||||
self.app_state.internal_exit();
|
self.app_state.internal_exit();
|
||||||
PumpStatus::Exit(0)
|
PumpStatus::Exit(0)
|
||||||
|
|
@ -423,29 +362,3 @@ pub(super) fn notify_windows_of_exit(app: &NSApplication) {
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Catches panics that happen inside `f` and when a panic
|
|
||||||
/// happens, stops the `sharedApplication`
|
|
||||||
#[inline]
|
|
||||||
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
|
||||||
mtm: MainThreadMarker,
|
|
||||||
panic_info: Weak<PanicInfo>,
|
|
||||||
f: F,
|
|
||||||
) -> Option<R> {
|
|
||||||
match catch_unwind(f) {
|
|
||||||
Ok(r) => Some(r),
|
|
||||||
Err(e) => {
|
|
||||||
// It's important that we set the panic before requesting a `stop`
|
|
||||||
// because some callback are still called during the `stop` message
|
|
||||||
// and we need to know in those callbacks if the application is currently
|
|
||||||
// panicking
|
|
||||||
{
|
|
||||||
let panic_info = panic_info.upgrade().unwrap();
|
|
||||||
panic_info.set_panic(e);
|
|
||||||
}
|
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
|
||||||
stop_app_immediately(&app);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@
|
||||||
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::rc::Weak;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use objc2::MainThreadMarker;
|
use objc2::MainThreadMarker;
|
||||||
|
|
@ -18,47 +16,18 @@ use objc2_core_foundation::{
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use super::app_state::AppState;
|
use super::app_state::AppState;
|
||||||
use super::event_loop::{stop_app_on_panic, PanicInfo};
|
|
||||||
|
|
||||||
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
|
|
||||||
{
|
|
||||||
let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) };
|
|
||||||
// Asserting unwind safety on this type should be fine because `PanicInfo` is
|
|
||||||
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
|
|
||||||
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
|
|
||||||
// `from_raw` takes ownership of the data behind the pointer.
|
|
||||||
// But if this scope takes ownership of the weak pointer, then
|
|
||||||
// the weak pointer will get free'd at the end of the scope.
|
|
||||||
// However we want to keep that weak reference around after the function.
|
|
||||||
std::mem::forget(info_from_raw);
|
|
||||||
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
|
|
||||||
let _ = &panic_info;
|
|
||||||
f(panic_info.0)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
// begin is queued with the highest priority to ensure it is processed before other observers
|
||||||
extern "C-unwind" fn control_flow_begin_handler(
|
extern "C-unwind" fn control_flow_begin_handler(
|
||||||
_: *mut CFRunLoopObserver,
|
_: *mut CFRunLoopObserver,
|
||||||
activity: CFRunLoopActivity,
|
activity: CFRunLoopActivity,
|
||||||
panic_info: *mut c_void,
|
_info: *mut c_void,
|
||||||
) {
|
) {
|
||||||
unsafe {
|
match activity {
|
||||||
control_flow_handler(panic_info, |panic_info| {
|
CFRunLoopActivity::AfterWaiting => {
|
||||||
#[allow(non_upper_case_globals)]
|
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
|
||||||
match activity {
|
},
|
||||||
CFRunLoopActivity::AfterWaiting => {
|
_ => unreachable!(),
|
||||||
// trace!("Triggered `CFRunLoopAfterWaiting`");
|
|
||||||
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
|
|
||||||
// trace!("Completed `CFRunLoopAfterWaiting`");
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,21 +36,14 @@ extern "C-unwind" fn control_flow_begin_handler(
|
||||||
extern "C-unwind" fn control_flow_end_handler(
|
extern "C-unwind" fn control_flow_end_handler(
|
||||||
_: *mut CFRunLoopObserver,
|
_: *mut CFRunLoopObserver,
|
||||||
activity: CFRunLoopActivity,
|
activity: CFRunLoopActivity,
|
||||||
panic_info: *mut c_void,
|
_info: *mut c_void,
|
||||||
) {
|
) {
|
||||||
unsafe {
|
match activity {
|
||||||
control_flow_handler(panic_info, |panic_info| {
|
CFRunLoopActivity::BeforeWaiting => {
|
||||||
#[allow(non_upper_case_globals)]
|
AppState::get(MainThreadMarker::new().unwrap()).cleared();
|
||||||
match activity {
|
},
|
||||||
CFRunLoopActivity::BeforeWaiting => {
|
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
|
||||||
// trace!("Triggered `CFRunLoopBeforeWaiting`");
|
_ => unreachable!(),
|
||||||
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
|
|
||||||
// trace!("Completed `CFRunLoopBeforeWaiting`");
|
|
||||||
},
|
|
||||||
CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,11 +142,11 @@ impl RunLoop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
|
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
|
||||||
let run_loop = RunLoop::main(mtm);
|
let run_loop = RunLoop::main(mtm);
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut context = CFRunLoopObserverContext {
|
let mut context = CFRunLoopObserverContext {
|
||||||
info: Weak::into_raw(panic_info) as *mut _,
|
info: ptr::null_mut(),
|
||||||
version: 0,
|
version: 0,
|
||||||
retain: None,
|
retain: None,
|
||||||
release: None,
|
release: None,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue