GraphiteEditor/Graphite · critical

Failed to send response

Error message

Failed to send response

What it means

InternalNodeGraphUpdateSender::send_compilation_response pushes a CompilationResponse from the NodeRuntime worker thread back to the main thread over an mpsc channel whose receiver lives in NodeRuntimeIO. The expect fires when that receiver has been dropped — the main-thread side was destroyed (application teardown, executor dropped, or runtime replaced) while the worker was still finishing a compilation. Because this runs on the worker thread, the panic kills the runtime thread; the main thread then typically crashes later on its next request send with the 'Failed to send generation request' error, making these two panics a coupled symptom pair.

Source

Thrown at editor/src/node_graph_executor/runtime.rs:101

}

#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ExportConfig {
	pub name: String,
	pub file_type: FileType,
	pub scale_factor: f64,
	pub bounds: ExportBounds,
	pub size: UVec2,
	pub artboard_name: Option<String>,
	pub artboard_count: usize,
}

#[derive(Clone)]
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);

impl InternalNodeGraphUpdateSender {
	fn send_compilation_response(&self, response: CompilationResponse) {
		self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send response")
	}

	fn send_execution_response(&self, response: ExecutionResponse) {
		self.0.send(NodeGraphUpdate::ExecutionResponse(Box::new(response))).expect("Failed to send response")
	}

	fn send_eyedropper_preview(&self, raster: Raster<CPU>) {
		self.0.send(NodeGraphUpdate::EyedropperPreview(raster)).expect("Failed to send response")
	}
}

impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {
	fn send(&self, message: NodeGraphUpdateMessage) {
		self.0.send(NodeGraphUpdate::NodeGraphUpdateMessage(message)).expect("Failed to send response")
	}
}

// TODO: Replace with `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) or similar

View on GitHub (pinned to c507b35645)

Solutions

  1. Replace the expect with a checked send: on Err, log 'node graph update receiver dropped' and stop processing (the channel is permanently closed).
  2. Fix the lifecycle race: keep the NodeRuntimeIO receiver alive as long as the runtime thread can send (join the thread before dropping IO, or use an Arc-kept channel).
  3. On shutdown, signal the runtime thread to drain and exit before the receiver is dropped (send a Shutdown request and join).
  4. When replacing the runtime (replace_node_runtime), stop the old thread first so in-flight responses have a live receiver.

Example fix

// before
fn send_compilation_response(&self, response: CompilationResponse) {
	self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send response")
}

// after
fn send_compilation_response(&self, response: CompilationResponse) {
	if self.0.send(NodeGraphUpdate::CompilationResponse(response)).is_err() {
		log::warn!("node graph receiver dropped; discarding compilation response");
	}
}
Defensive patterns

Strategy: fallback

Validate before calling

// On the worker: probe cheaply is not possible with std mpsc, so make send tolerant
fn send_compilation_response(&self, response: CompilationResponse) {
	if self.0.send(NodeGraphUpdate::CompilationResponse(response)).is_err() {
		// consumer dropped (shutdown): stop emitting further updates
	}
}

Try / catch

// Wrap worker sends so a closed channel unwinds the task instead of killing the thread:
let Ok(()) = self.0.send(NodeGraphUpdate::CompilationResponse(response)) else {
	log::warn!("receiver dropped; ending runtime send loop");
	return;
};

Prevention

When it happens

Trigger: The NodeRuntime finishes compiling a document graph and calls send_compilation_response after the main-thread Receiver<NodeGraphUpdate> inside NodeRuntimeIO was dropped (app exiting, executor replaced via replace_node_runtime, or document close tearing down the IO).

Common situations: Shutdown races: quit during an active compilation; re-initializing the node runtime dropping the old channel pair while a compilation is in flight; tests spawning short-lived runtimes that compile past the receiver's drop; a panic on the main thread unwinding and dropping receivers mid-compilation.

Related errors


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