{"record":{"id":"426794ce78b31b58","repo":"linera-io/linera-protocol","slug":"server-task-did-not-finish-successfully","errorCode":null,"errorMessage":"Server task did not finish successfully","messagePattern":"Server task did not finish successfully","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"linera-rpc/src/simple/transport.rs","lineNumber":126,"sourceCode":"        &mut self,\n        _blob_ids: Vec<BlobId>,\n    ) -> Option<Pin<Box<dyn Stream<Item = RpcMessage> + Send>>> {\n        None\n    }\n}\n\n/// The result of spawning a server is oneshot channel to track completion, and the set of\n/// executing tasks.\npub struct ServerHandle {\n    /// The handle tracking completion of the server task.\n    pub handle: TaskHandle<Result<(), std::io::Error>>,\n}\n\nimpl ServerHandle {\n    /// Waits for the server task to finish.\n    pub async fn join(self) -> Result<(), std::io::Error> {\n        self.handle.await.map_err(|_| {\n            std::io::Error::new(\n                std::io::ErrorKind::Interrupted,\n                \"Server task did not finish successfully\",\n            )\n        })?\n    }\n}\n\n/// A trait alias for a protocol transport.\n///\n/// A transport is an active connection that can be used to send and receive\n/// [`RpcMessage`]s.\npub trait Transport:\n    Stream<Item = Result<RpcMessage, codec::Error>> + Sink<RpcMessage, Error = codec::Error>\n{\n}\n\nimpl<T> Transport for T where\n    T: Stream<Item = Result<RpcMessage, codec::Error>> + Sink<RpcMessage, Error = codec::Error>","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-rpc/src/simple/transport.rs#L108-L144","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect tracing output for the panic message that preceded this error — the returned io::Error carries no detail.","Shut the server down through its `CancellationToken` before dropping the runtime so the task exits cleanly with `Ok(())`.","For genuine panics, run with `RUST_BACKTRACE=1` or install `std::panic::set_hook` to capture the backtrace.","Check whether shutdown was in progress when `join` failed, to distinguish cancellation from a crash."],"exampleFix":"// before\nlet handle = TransportProtocol::Tcp.spawn_server(addr, state, token, &mut join_set);\nruntime.shutdown_background(); // may cancel the server task\nhandle.join().await?; // 'Server task did not finish successfully'\n\n// after\nlet handle = TransportProtocol::Tcp.spawn_server(addr, state, token.clone(), &mut join_set);\ntoken.cancel(); // graceful shutdown first\nhandle.join().await?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"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); }","preventionTips":["Always cancel the server's CancellationToken before tearing down the runtime.","Keep tracing enabled at warn/error so handler and task panics are visible.","Don't abort the JoinSet that owns the server task while join() is pending.","Test shutdown ordering in integration tests, not just startup."],"tags":["rpc","server","tokio","task-panic","shutdown"],"backgroundTag":"async-task-join-error","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}