diff --git a/node-graph/libraries/core-types/src/arena.rs b/node-graph/libraries/core-types/src/arena.rs index 2018691018..1c97e23034 100644 --- a/node-graph/libraries/core-types/src/arena.rs +++ b/node-graph/libraries/core-types/src/arena.rs @@ -22,6 +22,9 @@ struct DropEntry { unsafe impl Sync for Arena {} unsafe impl Send for Arena {} +impl std::panic::UnwindSafe for Arena {} +impl std::panic::RefUnwindSafe for Arena {} + impl Arena { pub fn new(capacity: usize) -> Self { let buf = (0..capacity).map(|_| UnsafeCell::new(MaybeUninit::uninit())).collect(); @@ -211,6 +214,33 @@ mod tests { } } + #[test] + fn panics_leave_the_arena_coherent() { + fn assert_ref_unwind_safe() {} + assert_ref_unwind_safe::(); + + static DROPS: AtomicU32 = AtomicU32::new(0); + struct Probe(#[allow(dead_code)] String); + impl Drop for Probe { + fn drop(&mut self) { + DROPS.fetch_add(1, Ordering::Relaxed); + } + } + let mut arena = Arena::new(1024); + let cell = ArenaCell::new(); + let result = std::panic::catch_unwind(|| { + let (_, weak) = arena.alloc(Probe("pre-panic".into())).unwrap(); + cell.store(weak); + panic!("mid-eval"); + }); + assert!(result.is_err()); + assert!(cell.load(&arena).is_some(), "the generation is still live after the caught panic"); + arena.reset(); + assert_eq!(DROPS.load(Ordering::Relaxed), 1, "reset reclaims pre-panic allocations"); + assert!(cell.load(&arena).is_none(), "the bump kills stale handles"); + assert!(arena.alloc(0u32).is_some(), "the arena stays usable"); + } + #[test] fn drop_glue_runs_on_reset() { static DROPS: AtomicU32 = AtomicU32::new(0); diff --git a/node-graph/node-macro/src/gcodegen.rs b/node-graph/node-macro/src/gcodegen.rs index 693ad783fc..d2c681512a 100644 --- a/node-graph/node-macro/src/gcodegen.rs +++ b/node-graph/node-macro/src/gcodegen.rs @@ -316,7 +316,7 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF let slot_check = quote! { let __key = #core_types::wire::cache_key(__input); { - let __entries = self.slot.lock().unwrap(); + let __entries = self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner); if let Some(__state) = __entries.get(&__key) { return match __state { Some(value) => __cell.merge(value.clone()), @@ -347,13 +347,13 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF let completion = future_completion(&parsed.output_type); quote! { #slot_check - self.slot.lock().unwrap().insert(__key, None); + self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, None); let __slot = std::sync::Arc::clone(&self.slot); #(#snapshot_binding)* let __future = self::#fn_name(#(#future_args),*); _runtime.0.spawn(_source, Box::pin(async move { let __value = #completion; - __slot.lock().unwrap().insert(__key, Some(__value)); + __slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, Some(__value)); })); #inflight } @@ -384,11 +384,11 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF #slot_check #placeholder_binding #acquire - self.slot.lock().unwrap().insert(__key, None); + self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, None); let __slot = std::sync::Arc::clone(&self.slot); _runtime.0.spawn(_source, Box::pin(async move { let __value = #completion; - __slot.lock().unwrap().insert(__key, Some(__value)); + __slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, Some(__value)); })); #spawn_return }