GraphiteEditor/Graphite · error

UUID mutex poisoned

Error message

UUID mutex poisoned

What it means

generate_uuid() in core-types guards a process-global static RNG (Mutex<Option<ChaCha20Rng>>) and panics with "UUID mutex poisoned" when lock() returns Err. A Rust Mutex is poisoned only after some thread panicked while holding it, so this message is always a secondary failure: the real bug is the earlier panic that left the lock poisoned. Once poisoned, every later NodeId::new() and generate_uuid() call in the entire process panics immediately.

Source

Thrown at node-graph/libraries/core-types/src/uuid.rs:66

	static RNG: Mutex<Option<ChaCha20Rng>> = Mutex::new(None);
	thread_local! {
		pub static UUID_SEED: Cell<Option<u64>> = const { Cell::new(None) };
	}

	pub fn set_uuid_seed(random_seed: u64) {
		UUID_SEED.with(|seed| seed.set(Some(random_seed)))
	}

	pub fn generate_uuid() -> u64 {
		let Ok(mut lock) = RNG.lock() else { panic!("UUID mutex poisoned") };
		if lock.is_none() {
			UUID_SEED.with(|seed| {
				let random_seed = seed.get().unwrap_or(42);
				*lock = Some(ChaCha20Rng::seed_from_u64(random_seed));
			})
		}
		lock.as_mut().map(ChaCha20Rng::next_u64).expect("UUID mutex poisoned")
	}
}

#[repr(transparent)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, PartialOrd, Ord, DynAny)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeId(pub u64);

impl NodeId {
	pub fn new() -> Self {
		Self(generate_uuid())
	}
}

impl std::fmt::Display for NodeId {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", self.0)

View on GitHub (pinned to c507b35645)

Solutions

  1. Treat this as a symptom: find the FIRST panic in the logs (it precedes the poisoning) and fix that root cause
  2. Restart the process or test worker to clear the poisoned mutex; the global RNG state cannot be repaired from user code
  3. Make generate_uuid() poison-tolerant: the RNG has no cross-call invariants, so recover the guard with RNG.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
  4. Longer term, replace the global Mutex<Option<Rng>> with a lock-free atomic counter or per-thread RNGs so poisoning is structurally impossible

Example fix

// before (uuid.rs)
let Ok(mut lock) = RNG.lock() else { panic!("UUID mutex poisoned") };

// after: RNG state carries no cross-call invariants, so recovering from poisoning is safe
let mut lock = RNG.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
Defensive patterns

Strategy: try-catch

Try / catch

let ids = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    (0..count).map(|_| NodeId::new()).collect::<Vec<_>>()
}));
if ids.is_err() {
    // mutex poisoned (or another ID panic): locate the ORIGINAL panic in logs, then restart this worker/process
}

Prevention

When it happens

Trigger: Any earlier panic on any thread while the RNG mutex guard is alive (the critical section in generate_uuid) poisons the lock; every subsequent call, e.g. NodeId::new() during node creation, document load, or paste, then panics with this message instead of producing an ID.

Common situations: A graph-evaluation worker thread hits a different .expect panic (GPU error, encoding error) and the process keeps running, after which every ID allocation dies; parallel test runs where one failing test poisons globals and cascading failures report "poisoned" instead of the root cause; hosts that use catch_unwind to survive panics but keep reusing the same process.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/4ec522d9eb3147a4. Report an issue: GitHub.