Sandbox node graph execution on native targets and attempt recovery from panics on Wasm (#1846)

* Test out wasm unwinding

* Implement panic catching for native targets

* Hack in support for recovering panics in wasm

* Keep debug info in release builds

* Check for DynAnyNode in Backtrace because that can't be inlined as well

* Improve error dialog

* Use a mutex for storing the frontend state instead of a RefCell

* Code review

* Update crash text

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert 2024-07-29 01:46:44 +02:00 committed by GitHub
parent 06177597ae
commit 5b1d3a0ae4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 110 additions and 46 deletions

View file

@ -9,17 +9,19 @@ pub mod helpers;
use editor::messages::prelude::*;
use std::cell::{OnceCell, RefCell};
use std::panic;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use wasm_bindgen::prelude::*;
// Set up the persistent editor backend state
pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false);
pub static NODE_GRAPH_ERROR_DISPLAYED: AtomicBool = AtomicBool::new(false);
pub static LOGGER: WasmLog = WasmLog;
thread_local! {
pub static EDITOR: OnceCell<RefCell<editor::application::Editor>> = const { OnceCell::new() };
pub static EDITOR_HANDLE: OnceCell<RefCell<editor_api::EditorHandle>> = const { OnceCell::new() };
pub static EDITOR: Mutex<Option<editor::application::Editor>> = const { Mutex::new(None) };
pub static EDITOR_HANDLE: Mutex<Option<editor_api::EditorHandle>> = const { Mutex::new(None) };
}
/// Initialize the backend
@ -35,16 +37,44 @@ pub fn init_graphite() {
/// When a panic occurs, notify the user and log the error to the JS console before the backend dies
pub fn panic_hook(info: &panic::PanicInfo) {
EDITOR_HAS_CRASHED.store(true, Ordering::SeqCst);
let info = info.to_string();
let backtrace = Error::new("stack").stack().to_string();
if backtrace.contains("DynAnyNode") {
log::error!("Node graph evaluation panicked {info}");
error!("{info}");
// When the graph panics, the node runtime lock may not be released properly
if editor::node_graph_executor::NODE_RUNTIME.try_lock().is_none() {
unsafe { editor::node_graph_executor::NODE_RUNTIME.force_unlock() };
}
if !NODE_GRAPH_ERROR_DISPLAYED.load(Ordering::SeqCst) {
NODE_GRAPH_ERROR_DISPLAYED.store(true, Ordering::SeqCst);
editor_api::editor_and_handle(|_, handle| {
let error = r#"
<rect x="50%" y="50%" width="600" height="100" transform="translate(-300 -50)" rx="4" fill="var(--color-error-red)" />
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
<tspan x="50%" dy="-24" font-weight="bold">The document crashed while being rendered in its current state.</tspan>
<tspan x="50%" dy="24">The editor is now unstable! Undo your last action to restore the artwork,</tspan>
<tspan x="50%" dy="24">then save your document and restart the editor before continuing work.</tspan>
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
handle.send_frontend_message_to_js_rust_proxy(FrontendMessage::UpdateDocumentArtwork { svg: error });
});
}
return;
} else {
EDITOR_HAS_CRASHED.store(true, Ordering::SeqCst);
}
log::error!("{info}");
EDITOR_HANDLE.with(|editor_handle| {
editor_handle.get().map(|handle| {
handle
.borrow_mut()
.send_frontend_message_to_js_rust_proxy(FrontendMessage::DisplayDialogPanic { panic_info: info.to_string() })
})
let mut guard = editor_handle.lock();
if let Ok(Some(ref mut handle)) = guard.as_deref_mut() {
handle.send_frontend_message_to_js_rust_proxy(FrontendMessage::DisplayDialogPanic { panic_info: info.to_string() });
}
});
}
@ -56,6 +86,9 @@ extern "C" {
#[wasm_bindgen(constructor)]
pub fn new(msg: &str) -> Error;
#[wasm_bindgen(structural, method, getter)]
fn stack(error: &Error) -> String;
}
/// Logging to the JS console
@ -69,6 +102,8 @@ extern "C" {
fn warn(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn trace(msg: &str, format: &str);
}
#[derive(Default)]