@@ -110,6 +110,9 @@ struct WtStreamManager::Accessor {
110110 auto & writableStreams () {
111111 return sm_.writableStreams_ ;
112112 }
113+ auto & connFcBlockedStreams () {
114+ return sm_.connFcBlockedStreams_ ;
115+ }
113116 WtStreamManager& sm_;
114117};
115118
@@ -191,6 +194,7 @@ struct WriteHandle : public WebTransport::StreamWriteHandle {
191194
192195 Accessor smAccessor_;
193196 WtBufferedStreamData bufferedSendData_;
197+ quic::PriorityQueue::Priority priority_; // Stream priority
194198 uint64_t err{kInvalidVarint };
195199 WritePromise promise_{emptyWritePromise ()};
196200 HandleState state_;
@@ -329,10 +333,12 @@ uint64_t& WtStreamManager::MaxStreamsContainer::getMaxStreams(
329333WtStreamManager::WtStreamManager (WtDir dir,
330334 const WtConfig& config,
331335 EgressCallback& egressCb,
332- IngressCallback& ingressCb) noexcept
336+ IngressCallback& ingressCb,
337+ quic::PriorityQueue& priorityQueue) noexcept
333338 : dir_(dir),
334339 nextStreamIds_ (nextStreamIds(dir)),
335340 maxStreams_(maxStreams(dir, config)),
341+ writableStreams_(priorityQueue),
336342 wtConfig_(config),
337343 connRecvFc_(config.selfMaxConnData),
338344 connSendFc_(config.peerMaxConnData),
@@ -507,8 +513,21 @@ WtStreamManager::Result WtStreamManager::onMaxData(MaxConnData data) noexcept {
507513 if (!connSendFc_.grant (data.maxData )) {
508514 return Fail;
509515 }
516+
510517 bool wasEmpty = !hasEvent ();
511- if (!wasEmpty && hasEvent ()) {
518+ // Re-add all connection-FC-blocked streams to the priority queue
519+ auto blockedStreams = std::move (connFcBlockedStreams_);
520+ for (auto * wh : blockedStreams) {
521+ auto & writeHandle = writehandle_ref_cast (*wh);
522+ if (writeHandle.bufferedSendData_ .canSendData ()) {
523+ writableStreams_.insert (writeHandle.getID (), writeHandle.priority_ );
524+ } else {
525+ XLOG (ERR) << " Stream " << writeHandle.getID ()
526+ << " in connFcBlockedStreams_ but !canSendData" ;
527+ }
528+ }
529+
530+ if (wasEmpty && hasEvent ()) {
512531 egressCb_.eventsAvailable ();
513532 }
514533 return Ok;
@@ -566,31 +585,35 @@ StreamData WtStreamManager::dequeue(WtWriteHandle& wh,
566585 uint64_t atMost) noexcept {
567586 // we're limited by conn egress fc
568587 atMost = std::min (atMost, connSendFc_.getAvailable ());
569- auto res = writehandle_ref_cast (wh).dequeue (atMost);
588+ auto & writeHandle = writehandle_ref_cast (wh);
589+ auto res = writeHandle.dequeue (atMost);
570590 // TODO(@damlaj): return len to elide unnecessarily computing chain len
571591 auto len = computeChainLength (res.data );
572592 // commit len bytes to conn window
573593 connSendFc_.commit (len);
594+
595+ // Connection FC blocked if we have data but conn window is exhausted
596+ if (connSendFc_.getAvailable () == 0 &&
597+ writeHandle.bufferedSendData_ .hasData ()) {
598+ connFcBlockedStreams_.insert (&wh);
599+ }
600+
574601 XLOG (DBG8) << __func__ << " ; atMost=" << atMost << " ; len=" << len
575602 << " ; fin=" << res.fin ;
576603 return res;
577604}
578605
579- WtStreamManager::WtWriteHandle* WtStreamManager::nextWritable () const noexcept {
580- WriteHandle* wh = !writableStreams_.empty ()
581- ? writehandle_ptr_cast (*writableStreams_.begin ())
582- : nullptr ;
583- // streams with only a pending fin should be yielded even if connection-level
584- // flow control window is blocked
585- return (wh && (connSendFc_.getAvailable () > 0 ||
586- wh->bufferedSendData_ .onlyFinPending ()))
587- ? wh
588- : nullptr ;
589- }
590-
591606void WtStreamManager::onStreamWritable (WtWriteHandle& wh) noexcept {
607+ // Don't re-insert if already tracked as conn FC blocked
608+ if (connFcBlockedStreams_.count (&wh) > 0 ) {
609+ return ;
610+ }
611+
592612 bool wasEmpty = !hasEvent ();
593- writableStreams_.insert (&wh);
613+
614+ auto & writeHandle = writehandle_ref_cast (wh);
615+ writableStreams_.insert (writeHandle.getID (), writeHandle.priority_ );
616+
594617 if (wasEmpty && hasEvent ()) {
595618 egressCb_.eventsAvailable ();
596619 }
@@ -623,29 +646,8 @@ bool WtStreamManager::canCreateBidi() const noexcept {
623646 return !streamLimitExceeded (nextStreamIds_.bidi );
624647}
625648
626- /* *
627- * Even if wt connection is flow-control blocked, a stream with only a fin
628- * pending should be yielded from ::nextWritable. We insert streams with only a
629- * pending fin first in the set for ::nextWritable to check such cases in O(1)
630- * time.
631- */
632- bool WtStreamManager::Compare::operator ()(const WtWriteHandle* l,
633- const WtWriteHandle* r) const {
634- const auto * lWh = static_cast <const WriteHandle*>(l);
635- const auto * rWh = static_cast <const WriteHandle*>(r);
636- // safe to cast to int64_t as getID() is limited by kMaxVarint
637- int64_t lId = lWh->getID ();
638- int64_t rId = rWh->getID ();
639- // set highest order bit to ensure id is negative for onlyFinPending streams
640- // (i.e. always less than non-onlyFinPending streams) while maintaining stream
641- // id priority
642- lId |= int64_t (lWh->bufferedSendData_ .onlyFinPending ()) << 63 ;
643- rId |= int64_t (rWh->bufferedSendData_ .onlyFinPending ()) << 63 ;
644- return lId < rId;
645- }
646-
647649bool WtStreamManager::hasEvent () const noexcept {
648- return nextWritable () != nullptr || !ctrlEvents_.empty ();
650+ return writableStreams_. hasStreams () || !ctrlEvents_.empty ();
649651}
650652
651653uint64_t WtStreamManager::initStreamRecvFc (uint64_t streamId) const noexcept {
@@ -697,6 +699,11 @@ WtStreamManager::WtReadHandle* WtStreamManager::getIngressHandle(
697699 return getBidiHandle (streamId).readHandle ;
698700}
699701
702+ WtStreamManager::WtWriteHandle* WtStreamManager::nextWritable () const noexcept {
703+ auto streamId = writableStreams_.peek ();
704+ return streamId ? getEgressHandle (*streamId) : nullptr ;
705+ }
706+
700707} // namespace proxygen::coro::detail
701708
702709/* *
@@ -839,7 +846,12 @@ folly::Expected<folly::Unit, WriteHandle::ErrCode> WriteHandle::resetStream(
839846
840847folly::Expected<folly::Unit, WriteHandle::ErrCode> WriteHandle::setPriority (
841848 quic::PriorityQueue::Priority priority) {
842- XLOG (FATAL) << " not implemented" ;
849+ XCHECK_NE (state_, Closed) << " setPriority after close" ;
850+
851+ priority_ = priority;
852+ smAccessor_.writableStreams ().update (getID (), priority_);
853+
854+ return folly::unit;
843855}
844856
845857Result WriteHandle::onMaxData (uint64_t offset) {
@@ -871,16 +883,26 @@ WritePromise WriteHandle::resetPromise() noexcept {
871883// TODO(@damlaj): StreamData and DequeueResult should be the same struct
872884StreamData WriteHandle::dequeue (uint64_t atMost) noexcept {
873885 XCHECK_NE (state_, Closed) << " dequeue after close" ;
886+
874887 auto res = bufferedSendData_.dequeue (atMost);
875888 const auto bufferAvailable = bufferedSendData_.window ().getBufferAvailable ();
889+
876890 if (bufferAvailable > 0 ) {
877891 if (auto p = resetPromise (); p.valid ()) {
878892 p.setValue (bufferAvailable);
879893 }
880894 }
881- if (!bufferedSendData_.canSendData ()) {
882- smAccessor_.writableStreams ().erase (this );
895+
896+ auto bytesDequeued = computeChainLength (res.data );
897+
898+ // Erase if blocked (wrote nothing) or done (!canSendData)
899+ // Consume if wrote data and still have more
900+ if (bytesDequeued > 0 && bufferedSendData_.canSendData ()) {
901+ smAccessor_.writableStreams ().consume (bytesDequeued);
902+ } else {
903+ smAccessor_.writableStreams ().erase (getID ());
883904 }
905+
884906 finish (res.fin );
885907 return StreamData{std::move (res.data ), res.fin };
886908}
@@ -892,7 +914,9 @@ void WriteHandle::cancel(folly::exception_wrapper ex) noexcept {
892914 if (auto p = resetPromise (); p.valid ()) {
893915 p.setException (ex_);
894916 }
895- smAccessor_.writableStreams ().erase (this );
917+ smAccessor_.writableStreams ().erase (getID ());
918+ // Also remove from conn FC blocked set if present
919+ smAccessor_.connFcBlockedStreams ().erase (this );
896920 cs_.requestCancellation ();
897921 // **beware finish must be last** (`this` can be deleted immediately after)
898922 finish (/* done=*/ true );
0 commit comments