{"record":{"id":"ec56b1f791de6696","repo":"GraphiteEditor/Graphite","slug":"failed-to-send-generation-request","errorCode":null,"errorMessage":"Failed to send generation request","messagePattern":"Failed to send generation request","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"editor/src/node_graph_executor.rs","lineNumber":125,"sourceCode":"\n\t\tlet node_executor = Self {\n\t\t\tfutures: Default::default(),\n\t\t\truntime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),\n\t\t\tnode_graph_hash: 0,\n\t\t\tcurrent_execution_id: 0,\n\t\t\tprevious_node_to_inspect: Vec::new(),\n\t\t\tgradient_migration: None,\n\t\t\tgradient_migration_attempted: HashSet::new(),\n\t\t};\n\t\t(node_runtime, node_executor)\n\t}\n\n\t/// Execute the network by flattening it and creating a borrow stack.\n\tfn queue_execution(&mut self, render_config: RenderConfig) -> u64 {\n\t\tlet execution_id = self.current_execution_id;\n\t\tself.current_execution_id += 1;\n\t\tlet request = ExecutionRequest { execution_id, render_config };\n\t\tself.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).expect(\"Failed to send generation request\");\n\n\t\texecution_id\n\t}\n\n\tpub fn update_editor_preferences(&self, editor_preferences: EditorPreferences) {\n\t\tself.runtime_io\n\t\t\t.send(GraphRuntimeRequest::EditorPreferencesUpdate(editor_preferences))\n\t\t\t.expect(\"Failed to send editor preferences\");\n\t}\n\n\t/// Updates the network to monitor all inputs. Useful for the testing.\n\t#[cfg(test)]\n\tpub(crate) fn update_node_graph_instrumented(&mut self, document: &mut DocumentMessageHandler) -> Result<Instrumented, String> {\n\t\t// We should always invalidate the cache.\n\t\tself.node_graph_hash = crate::application::generate_uuid();\n\t\tlet mut network = document.network_interface.document_network().clone();\n\t\tlet instrumented = Instrumented::new(&mut network);\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/node_graph_executor.rs#L107-L143","documentation":"NodeGraphExecutor::queue_execution pushes an ExecutionRequest over a std::sync::mpsc channel to the NodeRuntime worker thread that owns the graph evaluator. NodeRuntimeIO::send returns Result<(), String>, and the expect panics when the send fails — which happens exactly when the receiving NodeRuntime thread has terminated (it panicked inside node execution or was dropped during teardown). The editor main thread crashes at the next execution request (any edit or render), so this error is a secondary symptom: the real failure is whatever killed the runtime thread earlier.","triggerScenarios":"Any operation that triggers graph re-execution (document edit, transform, frame render) after the NodeRuntime thread has died: runtime_io.send(GraphRuntimeRequest::ExecutionRequest(...)).expect(\"Failed to send generation request\") returns Err (channel disconnected) and panics on the main thread.","commonSituations":"A node implementation panicking inside the runtime thread (its panic kills the thread, then the next edit crashes here); application shutdown racing a final execution request; tests spawning a NodeGraphExecutor whose runtime was replaced via replace_node_runtime; double initialization dropping the receiver bound to the stored sender.","solutions":["Find the root cause first: reproduce and read the earlier panic from the NodeRuntime thread in logs — fixing that panic removes this error.","Handle the send error instead of expecting: on Err, mark the executor degraded, surface an error message, and optionally respawn the NodeRuntime (replace_node_runtime with fresh channels).","Guard teardown: set a shutting_down flag checked in queue_execution so late requests are dropped instead of sent into a dead channel.","In tests, keep the runtime thread alive for the executor's lifetime or use with_channels with a receiver that is not dropped prematurely."],"exampleFix":"// before\nself.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).expect(\"Failed to send generation request\");\n\n// after\nif self.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).is_err() {\n\tlog::error!(\"node runtime thread is gone; skipping execution {execution_id}\");\n}","handlingStrategy":"fallback","validationCode":"// NodeRuntimeIO::send already returns Result — check it instead of expecting\nif self.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).is_err() {\n\t// runtime thread is dead: degrade gracefully, log, optionally respawn via replace_node_runtime\n\treturn execution_id; // skip further execution bookkeeping\n}","typeGuard":null,"tryCatchPattern":"// Rust: not a caught exception — the expect panics. Prevent by handling the Result:\nmatch self.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)) {\n\tOk(()) => {}\n\tErr(err) => log::error!(\"node runtime unavailable: {err}\"),\n}","preventionTips":["Never expect on channel sends between threads; handle SendError as an expected lifecycle event.","Join or signal the runtime thread during shutdown before the main side goes away.","Log worker-thread panics loudly so the original crash is found, not this secondary send failure.","In tests, pair every executor with a runtime whose receiver outlives the test body."],"tags":["rust","graphite-editor","mpsc","channel-closed","expect-panic","node-runtime","thread-death"],"backgroundTag":"mpsc-channel-closed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}