linera-io/linera-protocol · error · std::io::Error

Server task did not finish successfully

Error message

Server task did not finish successfully

What it means

`ServerHandle::join` awaits the tokio task running the simple-network TCP/UDP server. If awaiting the `TaskHandle` yields a `JoinError` — the task panicked or was cancelled — it is mapped to `std::io::ErrorKind::Interrupted` with this message. The original panic details are discarded, so the root cause must be found in tracing logs; note the trailing `?`: a server task that finishes normally but with `Err` propagates that inner I/O error instead of this message.

Source

Thrown at linera-rpc/src/simple/transport.rs:126

        &mut self,
        _blob_ids: Vec<BlobId>,
    ) -> Option<Pin<Box<dyn Stream<Item = RpcMessage> + Send>>> {
        None
    }
}

/// The result of spawning a server is oneshot channel to track completion, and the set of
/// executing tasks.
pub struct ServerHandle {
    /// The handle tracking completion of the server task.
    pub handle: TaskHandle<Result<(), std::io::Error>>,
}

impl ServerHandle {
    /// Waits for the server task to finish.
    pub async fn join(self) -> Result<(), std::io::Error> {
        self.handle.await.map_err(|_| {
            std::io::Error::new(
                std::io::ErrorKind::Interrupted,
                "Server task did not finish successfully",
            )
        })?
    }
}

/// A trait alias for a protocol transport.
///
/// A transport is an active connection that can be used to send and receive
/// [`RpcMessage`]s.
pub trait Transport:
    Stream<Item = Result<RpcMessage, codec::Error>> + Sink<RpcMessage, Error = codec::Error>
{
}

impl<T> Transport for T where
    T: Stream<Item = Result<RpcMessage, codec::Error>> + Sink<RpcMessage, Error = codec::Error>

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Inspect tracing output for the panic message that preceded this error — the returned io::Error carries no detail.
  2. Shut the server down through its `CancellationToken` before dropping the runtime so the task exits cleanly with `Ok(())`.
  3. For genuine panics, run with `RUST_BACKTRACE=1` or install `std::panic::set_hook` to capture the backtrace.
  4. Check whether shutdown was in progress when `join` failed, to distinguish cancellation from a crash.

Example fix

// before
let handle = TransportProtocol::Tcp.spawn_server(addr, state, token, &mut join_set);
runtime.shutdown_background(); // may cancel the server task
handle.join().await?; // 'Server task did not finish successfully'

// after
let handle = TransportProtocol::Tcp.spawn_server(addr, state, token.clone(), &mut join_set);
token.cancel(); // graceful shutdown first
handle.join().await?;
Defensive patterns

Strategy: try-catch

Try / catch

if let Err(e) = handle.join().await { if e.kind() == std::io::ErrorKind::Interrupted { tracing::error!("server task panicked or was cancelled; inspect logs"); /* decide: restart server or propagate */ } return Err(e); }

Prevention

When it happens

Trigger: The spawned `UdpServer::run`/`TcpServer::run` loop panicking (e.g. hitting an `unreachable!` arm on a misbehaving stream); the tokio runtime shutting down and cancelling the server task while `join` is still pending; aborting the JoinSet that owns the server task.

Common situations: Dropping or shutting down the runtime before cancelling the server via its `CancellationToken`; a message-handler panic escalating during fuzzing or malformed traffic; embedding linera-rpc in a host that tears down tasks abruptly.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/426794ce78b31b58. Report an issue: GitHub.