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

@ -10,6 +10,7 @@ use graph_craft::Type;
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::panic::UnwindSafe;
use std::sync::Arc;
/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
@ -102,9 +103,22 @@ impl DynamicExecutor {
}
}
impl<'a, I: StaticType + 'static + Send + Sync> Executor<I, TaggedValue> for &'a DynamicExecutor {
impl<'a, I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe> Executor<I, TaggedValue> for &'a DynamicExecutor {
fn execute(&self, input: I) -> LocalFuture<Result<TaggedValue, Box<dyn Error>>> {
Box::pin(async move { self.tree.eval_tagged_value(self.output, input).await.map_err(|e| e.into()) })
Box::pin(async move {
use futures::FutureExt;
let result = self.tree.eval_tagged_value(self.output, input);
let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;
match wrapped_result {
Ok(result) => result.map_err(|e| e.into()),
Err(e) => {
Box::leak(e);
Err("Node graph execution paniced".into())
}
}
})
}
}
@ -176,7 +190,7 @@ impl BorrowTree {
}
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value.
/// This ensures that no borrowed data can escape the node graph.
pub async fn eval_tagged_value<I: StaticType + 'static + Send + Sync>(&self, id: NodeId, input: I) -> Result<TaggedValue, String> {
pub async fn eval_tagged_value<I: StaticType + 'static + Send + Sync + UnwindSafe>(&self, id: NodeId, input: I) -> Result<TaggedValue, String> {
let node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let output = node.eval(Box::new(input));
TaggedValue::try_from_any(output.await)