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

  1. Stop the receive loop when this error occurs instead of retrying on the same socket
  2. Track connection state in the caller and never call recv_message after initiating close
  3. Reconnect if further communication is needed
  4. 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

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)