moghtech/komodo · warning
Connection already closed
Error message
Connection already closed
What it means
In the same websocket receive loop, a Closed indicator means this side already knows the socket is closed (e.g. a prior close or a failed send marked it). recv_message surfaces 'Connection already closed' instead of attempting to read from a dead socket, distinguishing local closure from a peer Close frame.
Solutions
- Stop the receive loop when this error occurs instead of retrying on the same socket
- Track connection state in the caller and never call recv_message after initiating close
- Reconnect if further communication is needed
- Serialize close/recv access (e.g. via a select loop in one task) to avoid racing the socket
Example fix
// before
loop { let msg = ws.recv_message().await?; }
// after
loop {
match ws.recv_message().await {
Ok(m) => handle(m),
Err(e) if e.to_string().contains("closed") => break, // stop, don't retry
Err(e) => return Err(e),
}
} Defensive patterns
Strategy: try-catch
Type guard
fn is_closed_error(e: &anyhow::Error) -> bool {
let s = e.to_string();
s == "Connection closed" || s == "Connection already closed"
} Try / catch
match ws.recv_message().await {
Ok(m) => handle(m),
Err(e) if e.to_string() == "Connection already closed" => break, // stop looping
Err(e) => return Err(e),
} Prevention
- Don't call recv_message after closing or observing a failed send
- Concentrate socket close/recv handling in a single task to avoid races
- Represent connection state (Open/Closed) in the caller and gate calls on it
- Break out of receive loops on any 'closed' error rather than retrying
When it happens
Trigger: Calling recv_message after the websocket was already closed locally or marked closed by a previous operation; calling recv concurrently after a close was processed.
Common situations: Continuing a receive loop after a send failed and closed the socket; double-closing a connection in cleanup code; races where one task closes while another still polls recv_message.
Related errors
AI-assisted analysis of moghtech/komodo@780ac68b99 (2026-09-08).
Data as JSON: /api/errors/9ec243a610cd0a0a.
Report an issue: GitHub.
Appendix: source
Thrown at lib/transport/src/websocket/mod.rs:95
> {
MaybeWithTimeout::new(async {
loop {
match tokio::time::timeout(
Duration::from_secs(10),
self.recv_inner(),
)
.await
.context("Timed out waiting for Ping")??
{
WebsocketMessage::Message(message) => {
return message.decode();
}
WebsocketMessage::Ping => continue,
WebsocketMessage::Close => {
return Err(anyhow!("Connection closed"));
}
WebsocketMessage::Closed => {
return Err(anyhow!("Connection already closed"));
}
}
}
})
}
}
impl<W: Websocket> WebsocketExt for W {}
/// Traits for split websocket receiver
pub trait WebsocketSender {
/// Streamlined pinging
fn ping(
&mut self,
) -> impl Future<Output = anyhow::Result<()>> + Send;
/// Streamlined sending on bytes
fn send(View on GitHub (pinned to 780ac68b99)