asciinema/asciinema · error

SplitStream closed

Error message

SplitStream closed

What it means

handle_socket treats the WebSocket stream ending without a Close frame as fatal: when stream.next() returns None it bails with "SplitStream closed". A properly closed connection should deliver Message::Close first; hitting EOF means the peer (or transport) terminated abruptly.

Source

Thrown at src/forwarder.rs:216

            _ = &mut ping_timeout => bail!("ping timeout"),

            message = stream.next() => {
                match message {
                    Some(Ok(Message::Close(close_frame))) => {
                        handle_close_frame(close_frame)?;
                        return Ok(true);
                    },

                    Some(Ok(Message::Ping(_))) => (),

                    Some(Ok(Message::Pong(_))) => {
                        ping_timeout = Box::pin(future::pending());
                    },

                    Some(Ok(msg)) => debug!("unexpected message from the server: {msg:?}"),
                    Some(Err(e)) => bail!(e),
                    None => bail!("SplitStream closed")
                }
            }
        }
    }
}

async fn send_with_timeout(
    sink: &mut SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
    message: Message,
) -> anyhow::Result<Result<(), tungstenite::Error>> {
    time::timeout(SEND_TIMEOUT, sink.send(message))
        .await
        .map_err(|_| anyhow!("send timeout"))
}

fn handle_close_frame(frame: Option<CloseFrame>) -> anyhow::Result<()> {
    match frame {
        Some(CloseFrame { code, reason }) => {

View on GitHub (pinned to 7749806198)

Solutions

  1. Retry the stream/upload once connectivity is restored (handle_socket's caller handles reconnection)
  2. Check server/proxy logs for abrupt disconnects and raise idle timeouts
  3. If reproducible locally, test the server with a WebSocket client (wscat) to confirm it closes gracefully

Example fix

// before
None => bail!("SplitStream closed"),
// after (caller side)
match forward().await { Err(e) if e.to_string().contains("SplitStream closed") => reconnect_and_resume(), other => other }
Defensive patterns

Strategy: retry

Validate before calling

// preflight TCP+TLS reachability to reduce abrupt EOF risk
TcpStream::connect_timeout(&addr, Duration::from_secs(5))
    .context("server not reachable, aborting before stream")?;

Try / catch

match forward().await {
    Err(e) if e.to_string().contains("SplitStream closed") => {
        warn!("server dropped connection; reconnecting");
        reconnect().await
    }
    other => other,
}

Prevention

When it happens

Trigger: The remote end drops the TCP connection without a WebSocket Close handshake while connect_and_forward is streaming — server kill -9, network outage, LB idle eviction — so the split stream yields None.

Common situations: Long uploads interrupted by network drops; container/server restarts; load balancers that cut connections at idle timeout without a graceful close; MTU issues truncating frames.

Related errors


AI-assisted analysis of asciinema/asciinema@7749806198 (2026-09-03). Data as JSON: /api/errors/6ef0d714b2153d01. Report an issue: GitHub.