libp2p / libp2p/unified-testing

rust-v0.53 WebSocket quicksink panic (intermittent)

Open
#19 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

rust
Dominant language
Shell
Stars
7
Forks
19
PR merge metrics
No merged PRs in 30d

Description

**Repo:** [libp2p/rust-libp2p](https://github.com/libp2p/rust-libp2p)
**Commit:** `b7914e407da34c99fb76dcc300b3d44b9af97fac`
**Transport:** ws, **Secure:** tls, **Muxer:** yamux

## Summary

rust-v0.53 panics in WebSocket quicksink when go-v0.40 dials via ws/tls/yamux. The listener crashes with "SinkImpl::poll_ready called after error" during TLS/yamux session teardown. The dialer successfully completes its handshake and measurement (exit code 0), but the listener crashes, causing the test to fail.

**This bug is intermittent.** It failed in the first test run (transport-f89ec4b4-162830-14-02-2026, 2026-02-14) but passed in a subsequent run with the same configuration. Reproducibility depends on the timing of yamux session teardown.

## Failing tests (1)

- `go-v0.40 x rust-v0.53 (ws, tls, yamux)`

## Error output

```
thread 'tokio-runtime-worker' panicked at transports/websocket/src/quicksink.rs:159:30:
SinkImpl::poll_ready called after error.
```

## Stack trace

```
quicksink::SinkImpl::poll_ready
-> framed::Connection::poll_ready
-> AsyncWrite::poll_write_vectored
-> futures_rustls::Stream::write_io
-> yamux::frame::io::Io::poll_ready
```

## Root cause analysis

The panic occurs in `quicksink::SinkImpl::poll_ready` at `transports/websocket/src/quicksink.rs:159`. The `quicksink` state machine transitions to an error state when the TLS/yamux layer encounters a connection close, but the async runtime schedules another `poll_ready` call before the stream is dropped. This is a classic "use after error" bug in async Rust sink implementations.

## Why only go-v0.40

The timing window is narrow. go-v0.40 likely has slightly different connection teardown timing compared to other go versions (v0.38-v0.45), making it the only version that triggers the race. Other go versions either close the connection cleanly before the sink polls again, or close it fast enough that the sink task is cancelled first.

## Fix recommendations

**Immediate fix:** In `quicksink.rs:159`, instead of panicking when `poll_ready` is called after error, return `Poll::Ready(Err(..))` with the cached error. This converts the panic into a graceful error propagation.

**Proper fix:** The `quicksink` module should track error state and short-circuit all subsequent poll calls with the stored error, similar to how `futures::SinkExt` handles post-error states:

```rust
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> {
if let Some(err) = self.cached_error.take() {
return Poll::Ready(Err(err));
}
// ... normal poll logic
}
```

**Upstream status:** This was fixed in later rust-libp2p versions (v0.54+) which replaced the custom `quicksink` with `futures::SinkExt`. The fix is only relevant for v0.53 compatibility.

## Workaround

Since the dialer (go-v0.40) actually completes successfully (exit code 0) and only the listener panics during teardown, the test framework could potentially check dialer success independently. However, the listener panic causes `docker-compose --abort-on-container-exit` to report failure.

## Notes

This is a race condition in the WebSocket transport sink state machine. After a TLS or yamux error occurs, the `quicksink` module's `poll_ready` is called again when it should not be. Only the specific combination of go-v0.40 as dialer triggers this, suggesting timing-dependent behavior in session shutdown.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Begin at transports/websocket/src/quicksink.rs:159 and inspect the post-error poll_ready path. Reproduce the go-v0.40 x rust-v0.53 ws/tls/yamux interoperability test, then verify the listener no longer panics during teardown and the test reports success.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, rust
Domain
networking, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.