Skip to content

Commit e9845ee

Browse files
afrindmeta-codesync[bot]
authored andcommitted
Propagate WT stream priority to the QUIC socket
Summary: WebTransport `setPriority` never made it to quic: the stream-id version was a stub that did nothing, and the write handle version only reordered our own egress queue. So the QUIC stream sat at default priority and was never ranked against the datagram flows, which do set priority on the socket. Now the stream-id version delegates to the write handle, and the write handle tells the transport, which is where `QuicWtSessionBase` calls `QuicSocket::setStreamPriority`. The capsule transports need nothing -- their queue is the schedule. `setPriorityQueue` is still a stub, separate change. Reviewed By: sharmafb Differential Revision: D115277068 fbshipit-source-id: e01c28b58c6d15278ec5e35a8b3b4d37b364e5ae
1 parent 7eccc29 commit e9845ee

6 files changed

Lines changed: 122 additions & 1 deletion

File tree

proxygen/lib/http/webtransport/QuicWtSession.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,21 @@ void QuicWtSessionBase::StreamManagerCallback::eventsAvailable() noexcept {
314314
}
315315
}
316316

317+
// Mirror the priority into the QuicSocket write queue, so wt streams and wt
318+
// datagram flows are scheduled against each other in the connection rather than
319+
// the stream sitting at default priority.
320+
//
321+
// Best effort. The transport can legitimately no longer have the stream (eg. it
322+
// just reset it), and priority is only a scheduling hint -- anything that
323+
// actually depends on the stream, like the next write, will fail louder.
324+
void QuicWtSessionBase::StreamManagerCallback::onStreamPriority(
325+
uint64_t streamId, quic::PriorityQueue::Priority priority) noexcept {
326+
XCHECK(sess.quicSocket_);
327+
auto res = sess.quicSocket_->setStreamPriority(streamId, priority);
328+
XLOG_IF(DBG4, res.hasError())
329+
<< __func__ << "; id=" << streamId << "; err=" << toString(res.error());
330+
}
331+
317332
void QuicWtSessionBase::StreamManagerCallback::onNewPeerStream(
318333
uint64_t /*streamId*/) noexcept {
319334
}

proxygen/lib/http/webtransport/QuicWtSession.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ class QuicWtSessionBase
111111
}
112112
void readReady(detail::WtStreamManager::WtReadHandle& rh) noexcept override;
113113
void eventsAvailable() noexcept override;
114+
void onStreamPriority(
115+
uint64_t streamId,
116+
quic::PriorityQueue::Priority priority) noexcept override;
114117
void onNewPeerStream(uint64_t streamId) noexcept override;
115118
} smCb_{*this};
116119

proxygen/lib/http/webtransport/WtStreamManager.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ struct WtStreamManager::Accessor {
9393
uint32_t err,
9494
uint64_t reliableSize) noexcept;
9595
void onStreamWritable(WriteHandle& wh) noexcept;
96+
void onStreamPriority(WriteHandle& wh) noexcept;
9697
// invoked when write handle or read handle are done
9798
void done(WriteHandle& wh) noexcept;
9899
void done(ReadHandle& rh) noexcept;
@@ -292,6 +293,10 @@ void Accessor::onStreamWritable(WriteHandle& wh) noexcept {
292293
sm_.onStreamWritable(wh);
293294
}
294295

296+
void Accessor::onStreamPriority(WriteHandle& wh) noexcept {
297+
sm_.egressCb_.onStreamPriority(wh.getID(), wh.getPriority());
298+
}
299+
295300
void Accessor::done(WriteHandle& wh) noexcept {
296301
XCHECK_EQ(wh.state_, WriteHandleState::Closed);
297302
if (wh.bidi_->rh.state_ == ReadHandleState::Closed) { // bidi done
@@ -1085,6 +1090,7 @@ folly::Expected<folly::Unit, WriteHandle::ErrCode> WriteHandle::setPriority(
10851090
}
10861091
StreamWriteHandle::setPriority(priority);
10871092
smAccessor_.writableStreams().update(getID(), getPriority());
1093+
smAccessor_.onStreamPriority(*this);
10881094
return folly::unit;
10891095
}
10901096

proxygen/lib/http/webtransport/WtStreamManager.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ struct WtStreamManager {
9292
struct EgressCallback {
9393
virtual ~EgressCallback() = default;
9494
virtual void eventsAvailable() noexcept = 0;
95+
/**
96+
* Invoked when the application reprioritizes an egress stream. The
97+
* manager's own queue has already been updated; this is only for transports
98+
* that also schedule wt streams somewhere else -- quic ranks them against
99+
* wt datagram flows in the connection write queue. For the capsule
100+
* transports the manager's queue is the schedule, so the default no-op is
101+
* correct.
102+
*/
103+
virtual void onStreamPriority(
104+
uint64_t /*streamId*/,
105+
quic::PriorityQueue::Priority /*priority*/) noexcept {
106+
}
95107
};
96108
/**
97109
* IngressCallback::onNewPeerStream is invoked whenever a new peer stream has

proxygen/lib/http/webtransport/WtUtils.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,10 @@ WtExpected<folly::Unit>::Type WtSessionBase::resetStream(
475475

476476
WtExpected<folly::Unit>::Type WtSessionBase::setPriority(
477477
uint64_t streamId, quic::PriorityQueue::Priority priority) noexcept {
478-
return folly::unit;
478+
if (auto* wh = sm_.getBidiHandle(streamId).writeHandle) {
479+
return wh->setPriority(priority);
480+
}
481+
return folly::makeUnexpected(WtErrCode::INVALID_STREAM_ID);
479482
}
480483

481484
WtExpected<folly::Unit>::Type WtSessionBase::setPriorityQueue(

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,72 @@ TEST_F(QuicWtSessionTest, SetPriority) {
420420
EXPECT_EQ(httpPriority->incremental, true);
421421
}
422422

423+
// The priority must reach the QuicSocket, otherwise the stream sits at default
424+
// priority in the connection's write queue and is never compared against the
425+
// other streams (or the wt datagram flows) sharing it.
426+
TEST_F(QuicWtSessionTest, SetPriorityPropagatesToQuicSocket) {
427+
auto handle = session_->createUniStream();
428+
ASSERT_TRUE(handle.hasValue());
429+
auto id = handle.value()->getID();
430+
431+
// deliberately every field differs from HTTPPriorityQueue's default
432+
// (u=3, i=true, o=0), so this cannot pass on an unpropagated priority
433+
quic::HTTPPriorityQueue::Priority priority(/*u=*/5,
434+
/*i=*/false,
435+
/*o=*/42);
436+
socketDriver_.expectSetPriority(id, priority);
437+
438+
EXPECT_TRUE(session_->setPriority(id, priority).hasValue());
439+
// the local egress schedule is updated too
440+
EXPECT_EQ(quic::HTTPPriorityQueue::Priority(handle.value()->getPriority()),
441+
priority);
442+
}
443+
444+
// Applications reprioritize through the write handle they already hold rather
445+
// than by stream id, so this is the path that has to reach the socket.
446+
TEST_F(QuicWtSessionTest, WriteHandleSetPriorityPropagatesToQuicSocket) {
447+
auto handle = session_->createUniStream();
448+
ASSERT_TRUE(handle.hasValue());
449+
auto id = handle.value()->getID();
450+
451+
quic::HTTPPriorityQueue::Priority priority(/*u=*/5,
452+
/*i=*/false,
453+
/*o=*/42);
454+
socketDriver_.expectSetPriority(id, priority);
455+
456+
EXPECT_TRUE(handle.value()->setPriority(priority).hasValue());
457+
EXPECT_EQ(quic::HTTPPriorityQueue::Priority(handle.value()->getPriority()),
458+
priority);
459+
}
460+
461+
TEST_F(QuicWtSessionTest, SetPriorityUnknownStreamId) {
462+
// no stream has been created, so there is nothing to prioritize
463+
constexpr uint64_t kUnknownStreamId = 4;
464+
EXPECT_CALL(*socketDriver_.getSocket(), setStreamPriority(_, _)).Times(0);
465+
466+
auto res = session_->setPriority(kUnknownStreamId,
467+
quic::HTTPPriorityQueue::Priority(5, false));
468+
ASSERT_TRUE(res.hasError());
469+
EXPECT_EQ(res.error(), WebTransport::ErrorCode::INVALID_STREAM_ID);
470+
}
471+
472+
// The socket half is best effort -- a transport that no longer has the stream
473+
// does not make the call a failure, and the local schedule still applies.
474+
TEST_F(QuicWtSessionTest, SetPriorityQuicSocketErrorIgnored) {
475+
auto handle = session_->createUniStream();
476+
ASSERT_TRUE(handle.hasValue());
477+
auto id = handle.value()->getID();
478+
479+
quic::HTTPPriorityQueue::Priority priority(/*u=*/5, /*i=*/false, /*o=*/42);
480+
EXPECT_CALL(*socketDriver_.getSocket(), setStreamPriority(id, _))
481+
.WillOnce(Return(
482+
quic::make_unexpected(quic::LocalErrorCode::STREAM_NOT_EXISTS)));
483+
484+
EXPECT_TRUE(session_->setPriority(id, priority).hasValue());
485+
EXPECT_EQ(quic::HTTPPriorityQueue::Priority(handle.value()->getPriority()),
486+
priority);
487+
}
488+
423489
TEST_F(QuicWtSessionTest, AwaitWritable) {
424490
// Note: This test verifies basic functionality but doesn't test the blocking
425491
// scenario where awaitWritable returns a "not ready" future.
@@ -986,6 +1052,22 @@ TEST_F(H3WtSessionTest, CapsuleParseErrorClosesSession) {
9861052
connectStreamCb_.events.back()));
9871053
}
9881054

1055+
// H3WtSession shares the QuicSocket with the http/3 session, so its wt streams
1056+
// have to be ranked in that shared write queue like any other quic stream.
1057+
TEST_F(H3WtSessionTest, SetPriorityPropagatesToQuicSocket) {
1058+
socketDriver_.setMaxUniStreams(1);
1059+
auto handle = session_->createUniStream();
1060+
ASSERT_TRUE(handle.hasValue());
1061+
auto id = handle.value()->getID();
1062+
1063+
quic::HTTPPriorityQueue::Priority priority(/*u=*/5,
1064+
/*i=*/false,
1065+
/*o=*/42);
1066+
socketDriver_.expectSetPriority(id, priority);
1067+
1068+
EXPECT_TRUE(handle.value()->setPriority(priority).hasValue());
1069+
}
1070+
9891071
TEST_F(H3WtSessionTest, AcquireIngressStream) {
9901072
// client-initiated stream ids (server is the local endpoint)
9911073
constexpr uint64_t kClientBidiId = 0;

0 commit comments

Comments
 (0)