Skip to content

Commit c81f894

Browse files
mcollinaaduh95
authored andcommitted
stream: cut per-chunk overhead in WHATWG streams
Consolidate the spec's per-chunk predicate chains (CanCloseOrEnqueue, IsLocked, HasDefaultReader, GetNumReadRequests, GetDesiredSize and the writable-side equivalents) into single passes over the controller and stream state, mirror "close queued or in flight" as a boolean flag maintained at the few close-request transition sites, and materialize the TransformStream [[backpressureChangePromise]] record lazily on first observation so backpressure flips nobody is waiting on allocate nothing. Assisted-by: Claude Code Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64252 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent d940f02 commit c81f894

4 files changed

Lines changed: 119 additions & 98 deletions

File tree

lib/internal/webstreams/readablestream.js

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,21 +2480,26 @@ function readableStreamDefaultControllerClose(controller) {
24802480
}
24812481

24822482
function readableStreamDefaultControllerEnqueue(controller, chunk) {
2483-
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller))
2483+
// Equivalent to readableStreamDefaultControllerCanCloseOrEnqueue()
2484+
// followed by isReadableStreamLocked() and
2485+
// readableStreamGetNumReadRequests(), but with the state loaded once:
2486+
// this runs for every enqueued chunk.
2487+
const controllerState = controller[kState];
2488+
const stream = controllerState.stream;
2489+
if (controllerState.closeRequested || stream[kState].state !== 'readable')
24842490
return;
24852491

2486-
const {
2487-
stream,
2488-
} = controller[kState];
2489-
2490-
if (isReadableStreamLocked(stream) &&
2491-
readableStreamGetNumReadRequests(stream)) {
2492+
const reader = stream[kState].reader;
2493+
if (reader !== undefined &&
2494+
reader[kState] !== undefined &&
2495+
reader[kType] === 'ReadableStreamDefaultReader' &&
2496+
reader[kState].readRequests.length) {
24922497
readableStreamFulfillReadRequest(stream, chunk, false);
24932498
} else {
24942499
try {
24952500
const chunkSize =
24962501
FunctionPrototypeCall(
2497-
controller[kState].sizeAlgorithm,
2502+
controllerState.sizeAlgorithm,
24982503
undefined,
24992504
chunk);
25002505
enqueueValueWithSize(controller, chunk, chunkSize);
@@ -2533,22 +2538,27 @@ function readableStreamDefaultControllerGetDesiredSize(controller) {
25332538
}
25342539

25352540
function readableStreamDefaultControllerShouldCallPull(controller) {
2536-
const {
2537-
stream,
2538-
} = controller[kState];
2539-
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller) ||
2540-
!controller[kState].started)
2541+
// Single-pass version of the spec's predicate chain (CanCloseOrEnqueue,
2542+
// IsLocked, HasDefaultReader, GetNumReadRequests, GetDesiredSize): this
2543+
// runs at least once per chunk on every default-stream path. The
2544+
// desired-size computation is inlined because the stream state is
2545+
// already known to be 'readable' here.
2546+
const controllerState = controller[kState];
2547+
const stream = controllerState.stream;
2548+
if (controllerState.closeRequested ||
2549+
stream[kState].state !== 'readable' ||
2550+
!controllerState.started)
25412551
return false;
25422552

2543-
if (isReadableStreamLocked(stream) &&
2544-
readableStreamGetNumReadRequests(stream)) {
2553+
const reader = stream[kState].reader;
2554+
if (reader !== undefined &&
2555+
reader[kState] !== undefined &&
2556+
reader[kType] === 'ReadableStreamDefaultReader' &&
2557+
reader[kState].readRequests.length) {
25452558
return true;
25462559
}
25472560

2548-
const desiredSize = readableStreamDefaultControllerGetDesiredSize(controller);
2549-
assert(desiredSize !== null);
2550-
2551-
return desiredSize > 0;
2561+
return controllerState.highWaterMark - controllerState.queueTotalSize > 0;
25522562
}
25532563

25542564
function readableStreamDefaultControllerCallPullIfNeeded(controller) {
@@ -2798,28 +2808,29 @@ function readableByteStreamControllerGetDesiredSize(controller) {
27982808
}
27992809

28002810
function readableByteStreamControllerShouldCallPull(controller) {
2801-
const {
2802-
stream,
2803-
} = controller[kState];
2811+
// Single-pass version of the spec's predicate chain (HasDefaultReader,
2812+
// GetNumReadRequests, HasBYOBReader, GetNumReadIntoRequests,
2813+
// GetDesiredSize): this runs at least once per chunk on every byte
2814+
// stream path. The desired-size computation is inlined because the
2815+
// stream state is already known to be 'readable' here.
2816+
const controllerState = controller[kState];
2817+
const stream = controllerState.stream;
28042818
if (stream[kState].state !== 'readable' ||
2805-
controller[kState].closeRequested ||
2806-
!controller[kState].started) {
2819+
controllerState.closeRequested ||
2820+
!controllerState.started) {
28072821
return false;
28082822
}
2809-
if (readableStreamHasDefaultReader(stream) &&
2810-
readableStreamGetNumReadRequests(stream) > 0) {
2811-
return true;
2812-
}
2813-
2814-
if (readableStreamHasBYOBReader(stream) &&
2815-
readableStreamGetNumReadIntoRequests(stream) > 0) {
2816-
return true;
2823+
const reader = stream[kState].reader;
2824+
if (reader !== undefined && reader[kState] !== undefined) {
2825+
const type = reader[kType];
2826+
if (type === 'ReadableStreamDefaultReader') {
2827+
if (reader[kState].readRequests.length) return true;
2828+
} else if (type === 'ReadableStreamBYOBReader') {
2829+
if (reader[kState].readIntoRequests.length) return true;
2830+
}
28172831
}
28182832

2819-
const desiredSize = readableByteStreamControllerGetDesiredSize(controller);
2820-
assert(desiredSize !== null);
2821-
2822-
return desiredSize > 0;
2833+
return controllerState.highWaterMark - controllerState.queueTotalSize > 0;
28232834
}
28242835

28252836
function readableByteStreamControllerHandleQueueDrain(controller) {

lib/internal/webstreams/transformstream.js

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,7 @@ function InternalTransferredTransformStream() {
258258
readable: undefined,
259259
writable: undefined,
260260
backpressure: undefined,
261-
backpressureChange: {
262-
__proto__: null,
263-
promise: undefined,
264-
resolve: undefined,
265-
reject: undefined,
266-
},
261+
backpressureChange: undefined,
267262
controller: undefined,
268263
};
269264
}
@@ -390,12 +385,7 @@ function initializeTransformStream(
390385
writable,
391386
controller: undefined,
392387
backpressure: undefined,
393-
backpressureChange: {
394-
__proto__: null,
395-
promise: undefined,
396-
resolve: undefined,
397-
reject: undefined,
398-
},
388+
backpressureChange: undefined,
399389
};
400390

401391
transformStreamSetBackpressure(stream, true);
@@ -429,12 +419,27 @@ function transformStreamUnblockWrite(stream) {
429419
transformStreamSetBackpressure(stream, false);
430420
}
431421

422+
// The spec's [[backpressureChangePromise]] is only ever observed by the
423+
// source pull algorithm (settles when backpressure next becomes true) and
424+
// by a sink write arriving while backpressure is set (settles when
425+
// backpressure next becomes false). Instead of allocating a fresh promise
426+
// record on every flip, the record is materialized lazily on first
427+
// observation and dropped once settled; flips nobody is waiting on
428+
// allocate nothing.
429+
function transformStreamBackpressureChangePromise(stream) {
430+
const state = stream[kState];
431+
return (state.backpressureChange ??= PromiseWithResolvers()).promise;
432+
}
433+
432434
function transformStreamSetBackpressure(stream, backpressure) {
433-
assert(stream[kState].backpressure !== backpressure);
434-
if (stream[kState].backpressureChange.promise !== undefined)
435-
stream[kState].backpressureChange.resolve?.();
436-
stream[kState].backpressureChange = PromiseWithResolvers();
437-
stream[kState].backpressure = backpressure;
435+
const state = stream[kState];
436+
assert(state.backpressure !== backpressure);
437+
const backpressureChange = state.backpressureChange;
438+
if (backpressureChange !== undefined) {
439+
state.backpressureChange = undefined;
440+
backpressureChange.resolve();
441+
}
442+
state.backpressure = backpressure;
438443
}
439444

440445
function setupTransformStreamDefaultController(
@@ -554,7 +559,7 @@ function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) {
554559
} = stream[kState];
555560
assert(writable[kState].state === 'writable');
556561
if (stream[kState].backpressure) {
557-
const backpressureChange = stream[kState].backpressureChange.promise;
562+
const backpressureChange = transformStreamBackpressureChangePromise(stream);
558563
return PromisePrototypeThen(
559564
backpressureChange,
560565
() => {
@@ -638,9 +643,8 @@ function transformStreamDefaultSinkCloseAlgorithm(stream) {
638643

639644
function transformStreamDefaultSourcePullAlgorithm(stream) {
640645
assert(stream[kState].backpressure);
641-
assert(stream[kState].backpressureChange.promise !== undefined);
642646
transformStreamSetBackpressure(stream, false);
643-
return stream[kState].backpressureChange.promise;
647+
return transformStreamBackpressureChangePromise(stream);
644648
}
645649

646650
function transformStreamDefaultSourceCancelAlgorithm(stream, reason) {

lib/internal/webstreams/util.js

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -134,45 +134,44 @@ function isBrandCheck(brand) {
134134
};
135135
}
136136

137+
// The queue helpers below run once per chunk on the hot paths of every
138+
// default readable/writable stream, so they load the controller state a
139+
// single time and don't assert the existence of the queue fields (both
140+
// are unconditionally initialized during controller setup and only ever
141+
// replaced wholesale).
137142
function dequeueValue(controller) {
138-
assert(controller[kState].queue !== undefined);
139-
assert(controller[kState].queueTotalSize !== undefined);
140-
assert(controller[kState].queue.length);
143+
const state = controller[kState];
144+
assert(state.queue.length);
141145
const {
142146
value,
143147
size,
144-
} = ArrayPrototypeShift(controller[kState].queue);
145-
controller[kState].queueTotalSize =
146-
MathMax(0, controller[kState].queueTotalSize - size);
148+
} = ArrayPrototypeShift(state.queue);
149+
state.queueTotalSize = MathMax(0, state.queueTotalSize - size);
147150
return value;
148151
}
149152

150153
function resetQueue(controller) {
151-
assert(controller[kState].queue !== undefined);
152-
assert(controller[kState].queueTotalSize !== undefined);
153-
controller[kState].queue = [];
154-
controller[kState].queueTotalSize = 0;
154+
const state = controller[kState];
155+
state.queue = [];
156+
state.queueTotalSize = 0;
155157
}
156158

157159
function peekQueueValue(controller) {
158-
assert(controller[kState].queue !== undefined);
159-
assert(controller[kState].queueTotalSize !== undefined);
160-
assert(controller[kState].queue.length);
161-
return controller[kState].queue[0].value;
160+
const state = controller[kState];
161+
assert(state.queue.length);
162+
return state.queue[0].value;
162163
}
163164

164165
function enqueueValueWithSize(controller, value, size) {
165-
assert(controller[kState].queue !== undefined);
166-
assert(controller[kState].queueTotalSize !== undefined);
166+
const state = controller[kState];
167167
const coercedSize = +size;
168168
if (NumberIsNaN(coercedSize) ||
169169
coercedSize < 0 ||
170170
coercedSize === Infinity) {
171171
throw new ERR_INVALID_ARG_VALUE.RangeError('size', size);
172172
}
173-
size = coercedSize;
174-
ArrayPrototypePush(controller[kState].queue, { value, size });
175-
controller[kState].queueTotalSize += size;
173+
ArrayPrototypePush(state.queue, { value, size: coercedSize });
174+
state.queueTotalSize += coercedSize;
176175
}
177176

178177
// Arity-specialized variants of the promise-callback wrapper. The generic

0 commit comments

Comments
 (0)