FuelLabs/sway · error
failed to send terminate message
Error message
failed to send terminate message
What it means
During LSP shutdown, shutdown_server drains pending compilations, sets the retrigger flag, then sends TaskMessage::Terminate to the compilation thread over cb_tx. mpsc::send fails only when the receiver has been dropped — the compilation thread already exited (crashed earlier or the channel closed). The expect converts that into a panic, crashing the server in the middle of its own shutdown sequence.
Source
Thrown at sway-lsp/src/server_state.rs:336
// We are still compiling, lets wait to be notified.
self.finished_compilation.notified().await;
}
}
pub fn shutdown_server(&self) -> jsonrpc::Result<()> {
let _p = tracing::trace_span!("shutdown_server").entered();
tracing::info!("Shutting Down the Sway Language Server");
// Drain pending compilation requests
while self.cb_rx.try_recv().is_ok() {}
// Set the retrigger_compilation flag to true so that the compilation exits early
self.retrigger_compilation.store(true, Ordering::SeqCst);
// Send a terminate message to the compilation thread
self.cb_tx
.send(TaskMessage::Terminate)
.expect("failed to send terminate message");
// Delete all temporary directories.
for entry in self.sync_workspaces.iter() {
entry.value().remove_temp_dir();
}
Ok(())
}
pub(crate) async fn publish_diagnostics(
&self,
uri: Url,
workspace_uri: Url,
session: Arc<Session>,
) {
let diagnostics = self.diagnostics(&uri, session.clone());
// Note: Even if the computed diagnostics vec is empty, we still have to push the empty Vec
// in order to clear former diagnostics. Newly pushed diagnostics always replace previously pushed diagnostics.View on GitHub (pinned to 47e5e902fa)
Solutions
- As an end user: avoid duplicate shutdown requests — issue one `shutdown` then `exit`, and restart the LSP process if it is wedged.
- Upgrade sway-lsp; the send should tolerate a dead receiver: `if self.cb_tx.send(TaskMessage::Terminate).is_err() { tracing::warn!("compile thread already terminated"); }`.
- If you embed sway-lsp, wrap the shutdown path in std::panic::catch_unwind so a dead compile thread cannot abort your host.
Example fix
// before (sway-lsp/src/server_state.rs:336)
self.cb_tx.send(TaskMessage::Terminate).expect("failed to send terminate message");
// after
if self.cb_tx.send(TaskMessage::Terminate).is_err() {
tracing::warn!("compilation thread already terminated; skipping Terminate");
} Defensive patterns
Strategy: fallback
Try / catch
// embedders: tolerate a wedged server during teardown
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
server.shutdown_server()
}));
if result.is_err() {
tracing::warn!("server panicked during shutdown; treating as terminated");
} Prevention
- Send exactly one LSP `shutdown` then `exit`; never repeat shutdown on the same session.
- Restart (kill) a wedged sway-lsp process instead of issuing redundant requests.
- Monitor for compile-thread panics in logs — they are what leaves cb_tx dangling for the next shutdown.
When it happens
Trigger: The compilation thread panicked during an earlier build leaving cb_tx dangling; a second shutdown/exit request after the first already terminated the thread; a race where the compile thread exits between the drain loop and the send.
Common situations: Editor restart flows issuing repeated shutdowns; test harnesses that call shutdown twice; the server left in a degraded state after an internal compile-thread panic, then asked to shut down.
Related errors
- Failed to execute ps command
- Failed to execute ps command
- Failed to execute tasklist command
- unable to find the user home directory
- Could not get plugin description.
AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16).
Data as JSON: /api/errors/e726a9206bdfb8ca.
Report an issue: GitHub.