diff --git a/quic/api/QuicTransportBase.cpp b/quic/api/QuicTransportBase.cpp index 213b8f961..9a34e53f8 100644 --- a/quic/api/QuicTransportBase.cpp +++ b/quic/api/QuicTransportBase.cpp @@ -806,6 +806,13 @@ folly::Expected QuicTransportBase::stopSending( if (!conn_->streamManager->streamExists(id)) { return folly::makeUnexpected(LocalErrorCode::STREAM_NOT_EXISTS); } + auto* stream = CHECK_NOTNULL(conn_->streamManager->getStream(id)); + if (stream->recvState == StreamRecvState::Closed) { + // skip STOP_SENDING if ingress is already closed + return folly::unit; + } + + // send STOP_SENDING frame to peer sendSimpleFrame(*conn_, StopSendingFrame(id, error)); updateWriteLooper(true); return folly::unit; diff --git a/quic/api/test/QuicTransportBaseTest.cpp b/quic/api/test/QuicTransportBaseTest.cpp index 16bb508c6..e4306181d 100644 --- a/quic/api/test/QuicTransportBaseTest.cpp +++ b/quic/api/test/QuicTransportBaseTest.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -666,6 +667,49 @@ TEST_P(QuicTransportImplTestBase, IdleTimeoutStreamMaessage) { transport->invokeIdleTimeout(); } +TEST_P(QuicTransportImplTestBase, StopSendingClosesIngress) { + // create bidi stream + auto streamID = transport->createBidirectionalStream().value(); + auto* stream = CHECK_NOTNULL(transport->getStream(streamID)); + + EXPECT_EQ(stream->sendState, StreamSendState::Open); + EXPECT_EQ(stream->recvState, StreamRecvState::Open); + + // suppose we rx a reset from peer which closes our ingress SM + receiveRstStreamSMHandler( + *stream, + RstStreamFrame(stream->id, GenericApplicationErrorCode::NO_ERROR, 0)); + EXPECT_EQ(stream->sendState, StreamSendState::Open); + EXPECT_EQ(stream->recvState, StreamRecvState::Closed); + + // send stop sending to peer should no-op + transport->stopSending(streamID, GenericApplicationErrorCode::NO_ERROR); + EXPECT_EQ(transport->transportConn->pendingEvents.frames.size(), 0); + + // now test ingress uni-directional stream + auto& streamManager = *transport->transportConn->streamManager; + auto nextPeerUniStream = + streamManager.nextAcceptablePeerUnidirectionalStreamId(); + EXPECT_TRUE(nextPeerUniStream.has_value()); + stream = streamManager.getStream(*nextPeerUniStream); + EXPECT_EQ(stream->sendState, StreamSendState::Invalid); + EXPECT_EQ(stream->recvState, StreamRecvState::Open); + + // suppose we rx a reset from peer which closes our ingress SM + receiveRstStreamSMHandler( + *stream, + RstStreamFrame(stream->id, GenericApplicationErrorCode::NO_ERROR, 0)); + EXPECT_EQ(stream->sendState, StreamSendState::Invalid); + EXPECT_EQ(stream->recvState, StreamRecvState::Closed); + EXPECT_TRUE(stream->inTerminalStates()); + + // send stop sending to peer should no-op + transport->stopSending(stream->id, GenericApplicationErrorCode::NO_ERROR); + EXPECT_EQ(transport->transportConn->pendingEvents.frames.size(), 0); + + transport.reset(); +} + TEST_P(QuicTransportImplTestBase, WriteAckPacketUnsetsLooper) { // start looper in running state first transport->writeLooper()->run(true);