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
- Retry the stream/upload once connectivity is restored (handle_socket's caller handles reconnection)
- Check server/proxy logs for abrupt disconnects and raise idle timeouts
- 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
- Configure LBs/proxies to close WebSockets gracefully with a Close frame
- Raise idle eviction timeouts beyond the session length
- Retry/resume uploads rather than treating EOF as fatal
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.