From 9cbb5e4a6b352809912a5d41d28bf82747f00249 Mon Sep 17 00:00:00 2001 From: Naman Trivedi Date: Sat, 15 Aug 2026 23:35:53 +0000 Subject: [PATCH] quic: remove unused fin flag from blob reader wakeup The fin argument threaded from Blob::Reader::NotifyPull to the JS blob reader iterator was dead: the only consumer, `if (fin) continue;`, was the last statement in the loop and behaved identically to falling through. End-of-stream is always discovered by the subsequent pull returning EOS, never via the wakeup label. Remove the flag from the JS iterator, NotifyPull's signature/argv, and the EndReadable call site. The `!fin` coalescing bypass collapses safely because a parked reader always has pull_pending_ == false, so the first wakeup after parking always fires. Refs: https://github.com/nodejs/node/issues/64767 Signed-off-by: Naman Trivedi --- lib/internal/blob.js | 10 +--------- src/node_blob.cc | 16 ++++++---------- src/node_blob.h | 2 +- src/quic/streams.cc | 7 +++---- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index 2ea5505c183c..ee42ebbab9eb 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -618,9 +618,7 @@ async function* createBlobReaderIterable(reader, options = kEmptyObject) { const { getReadError } = options; let wakeup = PromiseWithResolvers(); let immediate; - let fin = false; - reader.setWakeup((setfin) => { - fin ||= setfin; + reader.setWakeup(() => { immediate ??= setImmediate(() => { immediate = undefined; wakeup.resolve?.(); @@ -672,12 +670,6 @@ async function* createBlobReaderIterable(reader, options = kEmptyObject) { if (blocked) { await wakeup.promise; wakeup = PromiseWithResolvers(); - // If the wakeup was triggered by FIN (EndReadable), the DataQueue - // is capped. Continue the loop to pull again -- the next pull will - // return EOS. Without this, a race between the data notification - // and the FIN notification can leave the iterator waiting for a - // wakeup that will never come. - if (fin) continue; } } } finally { diff --git a/src/node_blob.cc b/src/node_blob.cc index 57d35358fbfd..c217fae74ed5 100644 --- a/src/node_blob.cc +++ b/src/node_blob.cc @@ -420,20 +420,16 @@ void Blob::Reader::SetWakeup(const FunctionCallbackInfo& args) { reader->wakeup_.Reset(args.GetIsolate(), args[0].As()); } -void Blob::Reader::NotifyPull(bool fin) { +void Blob::Reader::NotifyPull() { if (wakeup_.IsEmpty() || !env()->can_call_into_js()) return; - // FIN notifications always fire — they must not be suppressed by - // pull_pending_ because there will be no further notifications to - // wake the iterator. Regular data notifications respect pull_pending_ - // to coalesce multiple deliveries within a single packet. - if (!fin && pull_pending_) return; + // Coalesce notifications: if a wakeup is already pending and the reader + // has not yet pulled, skip re-notifying to avoid redundant wakeups + // within a single packet. + if (pull_pending_) return; pull_pending_ = true; HandleScope handle_scope(env()->isolate()); Local fn = wakeup_.Get(env()->isolate()); - // Pass fin as the first argument so the JS iterator knows EOS is - // imminent and should pull again without waiting for another wakeup. - Local argv[] = {v8::Boolean::New(env()->isolate(), fin)}; - MakeCallback(fn, 1, argv); + MakeCallback(fn, 0, nullptr); } BaseObjectPtr Blob::BlobTransferData::Deserialize( diff --git a/src/node_blob.h b/src/node_blob.h index e782b96594b5..06d3a151cfa5 100644 --- a/src/node_blob.h +++ b/src/node_blob.h @@ -82,7 +82,7 @@ class Blob : public BaseObject { BaseObjectPtr blob); static void Pull(const v8::FunctionCallbackInfo& args); static void SetWakeup(const v8::FunctionCallbackInfo& args); - void NotifyPull(bool fin = false); + void NotifyPull(); explicit Reader(Environment* env, v8::Local obj, diff --git a/src/quic/streams.cc b/src/quic/streams.cc index c2691362447a..913009e63901 100644 --- a/src/quic/streams.cc +++ b/src/quic/streams.cc @@ -1614,10 +1614,9 @@ void Stream::EndReadable(std::optional maybe_final_size) { FlushAccumulation(); set_final_size(maybe_final_size.value_or(STAT_GET(Stats, bytes_received))); inbound_->cap(STAT_GET(Stats, final_size)); - // Notify the JS reader so it can see EOS. Pass fin=true so the - // wakeup promise resolves with a value the iterator can check to - // avoid waiting for another wakeup that will never come. - if (reader_) reader_->NotifyPull(true); + // Notify the JS reader so it can see EOS. The subsequent pull observes + // the now-capped DataQueue and returns EOS. + if (reader_) reader_->NotifyPull(); } void Stream::Destroy(QuicError error) {