From 255627efc53e7970e1b1e757e8cafc1cff7578b2 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 21 Jun 2026 14:04:09 +0200 Subject: [PATCH 1/4] 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 From c3680807d78f763814808261cd54fee5a1305c32 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 5 Jul 2026 18:59:22 +0200 Subject: [PATCH 2/4] quic: Update lib/internal/blob.js more elegantly Co-authored-by: James M Snell --- lib/internal/blob.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index 8ad349d9407ff8..bb296dde7367e8 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -457,13 +457,12 @@ function createBlobReaderStream(reader) { // when new data is available after a STATUS_BLOCK. let immediate; reader.setWakeup(() => { - if (this.pendingPulls.length > 0 && - typeof immediate === 'undefined') { - // Postpone the execution to the next steps of the event loop - immediate = setImmediate(() => { + if (this.pendingPulls.length > 0) { + immediate ??= setImmediate(() => { immediate = undefined; this.readNext(c); - }); + }) + } } }); }, From 836d4b1c654a0fb06942ce985fe45dfb48c62c3b Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 5 Jul 2026 19:36:59 +0200 Subject: [PATCH 3/4] quic: fix lib/internal/blob.js --- lib/internal/blob.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index bb296dde7367e8..3d2bb5cfdefd25 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -461,8 +461,7 @@ function createBlobReaderStream(reader) { immediate ??= setImmediate(() => { immediate = undefined; this.readNext(c); - }) - } + }); } }); }, From 11aa986851f7b9b91e337ff2b3e70e6eb4665495 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Mon, 6 Jul 2026 07:22:27 +0200 Subject: [PATCH 4/4] quic: Apply suggestion from @jasnell for changing setImmediate Co-authored-by: James M Snell --- lib/internal/blob.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index 3d2bb5cfdefd25..40238b3f5b2cbb 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -550,13 +550,10 @@ async function* createBlobReaderIterable(reader, options = kEmptyObject) { let wakeup = PromiseWithResolvers(); 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?.(); - }); - } + immediate ??= setImmediate(() => { + immediate = undefined; + wakeup.resolve?.(); + }); }); try {