commit 26a55e242dd744539791dc24d9b396f22dfed603
parent bc6c7da176c6f6383491b72494307a3593257323
Author: Kershaw Chang <kershaw@mozilla.com>
Date: Wed, 26 Nov 2025 09:27:03 +0000
Bug 2001721 - Prevent busy loop when tunneled connection returns WOULD_BLOCK, r=necko-reviewers,valentin
When a tunneled connection returns WOULD_BLOCK, the consumer calls AyncWait() and we enqueue the stream on the ready queue. We then call ForceSend() -> ProcessOutput() and immediately attempt to send again, which still would block, causing the stream to be re-queued and retried in a busy loop.
This patch introduces a mBlockedByFlowControl flag that is only cleared when neqo notifies us the stream is writable. While the flag is set, we skip send attempts. This avoids the busy waiting.
Differential Revision: https://phabricator.services.mozilla.com/D274000
Diffstat:
3 files changed, 20 insertions(+), 0 deletions(-)
diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp
@@ -589,6 +589,7 @@ nsresult Http3Session::ProcessEvents() {
if (stream) {
StreamReadyToWrite(stream);
+ stream->SetBlockedByFlowControl(false);
}
} break;
case Http3Event::Tag::Reset:
@@ -1823,11 +1824,18 @@ nsresult Http3Session::SendData(nsIUDPSocket* socket) {
nsresult rv = NS_OK;
RefPtr<Http3StreamBase> stream;
+ nsTArray<RefPtr<Http3StreamBase>> blockedStreams;
+
// Step 1)
while (CanSendData() && (stream = mReadyForWrite.PopFront())) {
LOG(("Http3Session::SendData call ReadSegments from stream=%p [this=%p]",
stream.get(), this));
stream->SetInTxQueue(false);
+ if (stream->BlockedByFlowControl()) {
+ LOG(("stream %p blocked by flow control", stream.get()));
+ blockedStreams.AppendElement(stream);
+ continue;
+ }
rv = stream->ReadSegments();
// on stream error we return earlier to let the error be handled.
@@ -1863,6 +1871,13 @@ nsresult Http3Session::SendData(nsIUDPSocket* socket) {
if (NS_FAILED(rv)) {
return rv;
}
+
+ // Put the blocked streams back to the queue, since they are ready to write.
+ for (const auto& stream : blockedStreams) {
+ mReadyForWrite.Push(stream);
+ stream->SetInTxQueue(true);
+ }
+
rv = ProcessEvents();
// Let the connection know we sent some app data successfully.
diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp
@@ -184,6 +184,7 @@ nsresult Http3Stream::OnReadSegment(const char* buf, uint32_t count,
rv = mSession->SendRequestBody(mStreamId, buf, count, countRead);
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
mSendingBlockedByFlowControlCount++;
+ mBlockedByFlowControl = true;
}
if (NS_FAILED(rv)) {
diff --git a/netwerk/protocol/http/Http3StreamBase.h b/netwerk/protocol/http/Http3StreamBase.h
@@ -59,6 +59,9 @@ class Http3StreamBase : public SupportsWeakPtr, public ARefBase {
void SetInTxQueue(bool aValue) { mInTxQueue = aValue; }
bool IsInTxQueue() const { return mInTxQueue; }
+ void SetBlockedByFlowControl(bool aValue) { mBlockedByFlowControl = aValue; }
+ bool BlockedByFlowControl() const { return mBlockedByFlowControl; }
+
protected:
~Http3StreamBase();
@@ -71,6 +74,7 @@ class Http3StreamBase : public SupportsWeakPtr, public ARefBase {
bool mFin{false};
bool mResetRecv{false};
bool mInTxQueue{false};
+ bool mBlockedByFlowControl{false};
};
} // namespace mozilla::net