You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
quic: Accumulation buffer is not filled, if small packets arrive #64035
I have the following situation:
On http/3 (actually webtransport) the underlying data stream consists of 1024 byte packages (coming from a third party server), I see on the reader that the bytes arrive in packets of 100 bytes on js directly from the nghttp3 parser without being accumulated in the buffer.
The reason is that in ReceiveData in streams.cc:
} else if (reader_ && was_empty) {
printf("RD 10\n");
// Notify the reader once when the accumulator transitions from empty
// to non-empty. This wakes the reader exactly once per accumulation
// cycle. When the reader wakes and calls pull(), BeforePull() flushes
// the accumulated data, and the subsequent EntryRead() extends the
// flow control window. We do NOT notify on every callback — that
// would defeat coalescing by flushing tiny amounts each time.
reader_->NotifyPull();
printf("RD 11\n");
}
does actually not cause a wake, but an immediate execution at least for:
console.log('before wakeup')
const fin = await wakeup.promise;
console.log('after wakeup')
in createBlobReaderIterable .
Here is a log trace:
Receive data wt 100
RD 1
RD 2
RD 4
RD 5
RD 8
RD 10
after wakeup
Flush acc not pull
webtransport:http3wtstream(3804) commitReadBuffer 100 +1ms
read mark2 4061 1781992229358 406100 100
before wakeup
after wakeup
before wakeup
RD 11
Receive data wt 100
to prove it. And yes, it is woken up twice per cycle via Stream::FlushAccumulation() (that is the "Flush acc not pull" printout).
If I can trust on what is gemini saying, it is that the call from C++ will immediately schedule all microtasks before return, but I do not trust it....
It is unclear, if createBlobReaderStream(reader) will show the same issues.
My first reflex is to wrap the call to the wakeup call in a nextTick or setImmediate call , but maybe @jasnell or @pimterry know a better way?
My analysis is only half correct, after fixing the event loop issue, I still see 100 byte packets arriving on js side.
So may be only 100 bytes are arriving in one spin of the event loop? I have to debug, what is happening inside ngtcp2 or nghttp3.
This is quite interesting, already ReadPacket in session shows most times 158 bytes size quic packets.
Though the server side should send out larger packets, I wonder who breaks the packets in such tiny chunks.
(Is it a negotiation problem?)
It is just my stupidity I confused maximum number of stream limit in flow control with the maximum window size. And if you have a window size of 100.... then this is the result.
Anyway it uncovered that the wakeup mechanism does not work as intended. I will clean up my dev dir and make a PR.
Activity
martenrichter commentedon Jun 21, 2026
My analysis is only half correct, after fixing the event loop issue, I still see 100 byte packets arriving on js side.
So may be only 100 bytes are arriving in one spin of the event loop? I have to debug, what is happening inside ngtcp2 or nghttp3.
martenrichter commentedon Jun 21, 2026
This is quite interesting, already
ReadPacketin session shows most times 158 bytes size quic packets.Though the server side should send out larger packets, I wonder who breaks the packets in such tiny chunks.
(Is it a negotiation problem?)
martenrichter commentedon Jun 21, 2026
It is just my stupidity I confused maximum number of stream limit in flow control with the maximum window size. And if you have a window size of 100.... then this is the result.
Anyway it uncovered that the wakeup mechanism does not work as intended. I will clean up my dev dir and make a PR.
quic: fix wake up blob
quic: fix wake up blob
quic: fix wake up blob
quic: fix wake up blob
quic: fix wake up blob