Skip to content

transports/quic: STOP_SENDING fails poll_close #3144

Closed
@mxinden

Description

Summary

Receiving a STOP_SENDING makes poll_close fail. This is problematic in libp2p-identify where a node expects to be able to close its write side before reading.

Expected behaviour

libp2p-identify should successfully exchange identify information.

Actual behaviour

Say that node A and B connected. A opens a libp2p-identify stream to B. A expects B to send its identify information.

After having negotiated the libp2p-identify protocol via multistream-select:

  • B sends its identify information and closes its write side.

    framed_io.send(message).await?;
    framed_io.close().await?;

    B then drops the stream. Dropping the stream results in libp2p-quic sending a STOP_SENDING.
    impl Drop for Substream {
    fn drop(&mut self) {
    let mut state = self.state.lock();
    state.substreams.remove(&self.id);
    let _ = state.connection.recv_stream(self.id).stop(0u32.into());

  • In parallel A closes its write side.

    socket.close().await?;

    This results in Substream::poll_close being called in libp2p-quic.
    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
    let mut inner = self.state.lock();
    if inner.unchecked_substream_state(self.id).is_write_closed {
    return Poll::Ready(Ok(()));
    }
    match inner.connection.send_stream(self.id).finish() {
    Ok(()) => {
    let substream_state = inner.unchecked_substream_state(self.id);
    substream_state.finished_waker = Some(cx.waker().clone());
    Poll::Pending
    }
    Err(err @ quinn_proto::FinishError::Stopped(_)) => {
    Poll::Ready(Err(io::Error::new(io::ErrorKind::ConnectionReset, err)))
    }

    In case the STOP_SENDING from B already arived, poll_close fails and thus the identify handshake fails.

Possible Solution

  1. Don't send a STOP_SENDING on Drop.
    • That would be unfortunate, the remote might send data which will never be received.
  2. Don't send a STOP_SENDING on DROP IFF the remote already closed the write side.
  3. Ignore the STOP_SENDING error in poll_close.
    • That would give a false sense to upstream users. While they think closing was successful and thus assume (one can never be sure) that ones data made it to the remote, there is actually a high likelihood that remote never received the data.
  4. Don't close the write side in libp2p-identify before reading the remote's identify information.
    • I would expect libp2p-identify to be an edge-case here, i.e. I would expect other protocols not to run into this issue. E.g. in a standard request response style protocol A would send a request, then close, then B would read, then B writes, then B closes and only then B drops.

I am tending towards (2) AND (4).

Version

  • libp2p version (version number, commit, or branch):

I am using 055636e on #2712 but any commit on master should work. E.g. 0c85839.

Would you like to work on fixing this bug?

Maybe. Let's find consensus on a good solution first.

//CC @elenaf9

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

bugpriority:importantThe changes needed are critical for libp2p, or are blocking another project

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions