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

  1. As an end user: avoid duplicate shutdown requests — issue one `shutdown` then `exit`, and restart the LSP process if it is wedged.
  2. 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"); }`.
  3. 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

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


AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16). Data as JSON: /api/errors/e726a9206bdfb8ca. Report an issue: GitHub.