Skip to content

Commit 87668cb

Browse files
committed
stream: fix overlapping broadcast next calls
Preserve the first pending broadcast consumer read when next() is called again before data is available. The overlapping read now closes the consumer without replacing the earlier resolver, allowing the pending read to receive the next chunk. Fixes: #63499 Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5
1 parent 8d3245e commit 87668cb

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

lib/internal/streams/iter/broadcast.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ class BroadcastImpl {
207207
return kDone;
208208
}
209209

210+
if (state.resolve) {
211+
state.detached = true;
212+
return kDone;
213+
}
214+
210215
const { promise, resolve, reject } = PromiseWithResolvers();
211216
state.resolve = resolve;
212217
state.reject = reject;
@@ -312,6 +317,9 @@ class BroadcastImpl {
312317
}
313318
consumer.resolve = null;
314319
consumer.reject = null;
320+
if (consumer.detached && this.#deleteConsumer(consumer)) {
321+
this.#tryTrimBuffer();
322+
}
315323
}
316324
}
317325
}
@@ -396,6 +404,9 @@ class BroadcastImpl {
396404
consumer.resolve = null;
397405
consumer.reject = null;
398406
resolve({ __proto__: null, done: false, value: chunk });
407+
if (consumer.detached && this.#deleteConsumer(consumer)) {
408+
this.#tryTrimBuffer();
409+
}
399410
} else {
400411
// Still waiting -- put back
401412
ArrayPrototypePush(this.#waiters, consumer);

test/parallel/test-stream-iter-broadcast-basic.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,27 @@ async function testLateJoinerSeesBufferedData() {
243243
assert.strictEqual(result, 'before-join');
244244
}
245245

246+
async function testOverlappingNextKeepsEarlierRead() {
247+
const { writer, broadcast: bc } = broadcast();
248+
const it = bc.push()[Symbol.asyncIterator]();
249+
250+
const first = it.next();
251+
const second = it.next();
252+
253+
await writer.write('x');
254+
255+
assert.deepStrictEqual(await second, {
256+
__proto__: null,
257+
done: true,
258+
value: undefined,
259+
});
260+
261+
const result = await first;
262+
assert.strictEqual(result.done, false);
263+
assert.strictEqual(Buffer.concat(result.value).toString(), 'x');
264+
assert.strictEqual(bc.consumerCount, 0);
265+
}
266+
246267
Promise.all([
247268
testBasicBroadcast(),
248269
testMultipleWrites(),
@@ -257,4 +278,5 @@ Promise.all([
257278
testFailDetachesConsumers(),
258279
testWriterFailIdempotent(),
259280
testLateJoinerSeesBufferedData(),
281+
testOverlappingNextKeepsEarlierRead(),
260282
]).then(common.mustCall());

0 commit comments

Comments
 (0)