From 255627efc53e7970e1b1e757e8cafc1cff7578b2 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 21 Jun 2026 14:04:09 +0200 Subject: [PATCH] quic: fix wake up blob The quic implementation calls setWakeUp with the assumption, that it is only executed once per event loop cycle. This assumption is wrong. Only setImmediate will guarantee, that the execution is delayed to later in the event loop and happening once in the event loop. Fixes: https://github.com/nodejs/node/issues/64035 Signed-off-by: Marten Richter --- lib/internal/blob.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index 20f7078d1d0915..8ad349d9407ff8 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -455,9 +455,15 @@ function createBlobReaderStream(reader) { this.pendingPulls = []; // Register a wakeup callback that the C++ side can invoke // when new data is available after a STATUS_BLOCK. + let immediate; reader.setWakeup(() => { - if (this.pendingPulls.length > 0) { - this.readNext(c); + if (this.pendingPulls.length > 0 && + typeof immediate === 'undefined') { + // Postpone the execution to the next steps of the event loop + immediate = setImmediate(() => { + immediate = undefined; + this.readNext(c); + }); } }); }, @@ -544,7 +550,16 @@ const kMaxBatchChunks = 16; async function* createBlobReaderIterable(reader, options = kEmptyObject) { const { getReadError } = options; let wakeup = PromiseWithResolvers(); - reader.setWakeup(wakeup.resolve); + let immediate; + reader.setWakeup(() => { + if (typeof immediate === 'undefined') { + // Postpone the execution to the next steps of the event loop + immediate = setImmediate(() => { + immediate = undefined; + wakeup.resolve?.(); + }); + } + }); try { while (true) { @@ -591,7 +606,6 @@ async function* createBlobReaderIterable(reader, options = kEmptyObject) { if (blocked) { const fin = await wakeup.promise; wakeup = PromiseWithResolvers(); - reader.setWakeup(wakeup.resolve); // 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