Skip to content

Commit cfa449c

Browse files
afrindmeta-codesync[bot]
authored andcommitted
Keep the read callback registered on a local STOP_SENDING
Summary: When the application calls `stopSending` on an h3 WebTransport stream we sent STOP_SENDING with `setReadCallback(id, nullptr, err)`, which also unregisters the read callback. The peer's RESET_STREAM then never reached us, so `WtStreamManager` never saw the ingress side close and never returned the stream credit, and we never sent MAX_STREAMS. This uses `QuicSocket::stopSending` instead, which sends STOP_SENDING and leaves the read callback in place. That matches the http/2 path, which also waits for the peer's WT_RESET_STREAM before reaping the read side. Reviewed By: hanidamlaj Differential Revision: D116105653 fbshipit-source-id: 90df54796a4c076fb0850ad11263de20699692a0
1 parent dfa593a commit cfa449c

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

proxygen/lib/http/webtransport/QuicWtSession.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ struct QuicWtEventVisitor {
4444
}
4545

4646
void operator()(WtStreamManager::StopSending ev) const {
47-
quicSocket.setReadCallback(ev.streamId, nullptr, ev.err);
47+
// not setReadCallback(id, nullptr, err): that also unregisters the read
48+
// callback, so the peer's RESET_STREAM would never close the ingress side
49+
quicSocket.stopSending(ev.streamId, ev.err);
4850
}
4951

5052
// operations need to be serialized on the backing http/3 connect stream (if

proxygen/lib/http/webtransport/test/QuicWtSessionTest.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,43 @@ TEST_F(QuicWtSessionTest, StopSending) {
319319
EXPECT_EQ(writeHandle->exception()->error, WT_ERROR_1);
320320
}
321321

322+
/**
323+
* A local STOP_SENDING must not unregister the quic read callback. The peer
324+
* answers STOP_SENDING with a RESET_STREAM, and that has to reach
325+
* QuicWtSessionBase::readCb_ for WtStreamManager to observe the ingress side
326+
* closing -- otherwise the stream is never reaped and its credit is never
327+
* returned to the peer.
328+
*/
329+
TEST_F(QuicWtSessionTest, StopSendingKeepsReadCallback) {
330+
// id=2 is client-initiated uni, i.e. ingress for us
331+
constexpr uint64_t kPeerUniId = 2;
332+
WebTransport::StreamReadHandle* readHandle = nullptr;
333+
EXPECT_CALL(*handler_, onNewUniStream(_))
334+
.WillOnce(
335+
[&](WebTransport::StreamReadHandle* handle) { readHandle = handle; });
336+
socketDriver_.addReadEvent(kPeerUniId, nullptr, false);
337+
eventBase_.loopOnce();
338+
ASSERT_NE(readHandle, nullptr);
339+
340+
// park a read before stopping, so we can observe the peer's reset landing
341+
auto readFut = readHandle->readStreamData();
342+
343+
auto res = session_->stopSending(kPeerUniId, WT_ERROR_1);
344+
EXPECT_TRUE(res.hasValue());
345+
eventBase_.loopOnce();
346+
EXPECT_EQ(socketDriver_.streams_[kPeerUniId].error, WT_ERROR_1);
347+
EXPECT_NE(socketDriver_.streams_[kPeerUniId].readCB, nullptr);
348+
349+
// the peer resets in response; this must still be delivered to us
350+
socketDriver_.addReadError(
351+
kPeerUniId, quic::QuicErrorCode(quic::ApplicationErrorCode(WT_ERROR_2)));
352+
eventBase_.loopOnce();
353+
354+
EXPECT_TRUE(readFut.isReady());
355+
auto result = std::move(readFut).getTry();
356+
EXPECT_TRUE(result.hasException());
357+
}
358+
322359
TEST_F(QuicWtSessionTest, ResetStream) {
323360
// id=2 is client-initiated uni, so it's not egress for the server
324361
constexpr uint64_t kClientInitiatedUniId = 2;

0 commit comments

Comments
 (0)