Skip to content

Commit beeec2e

Browse files
committed
Revert "stream: decouple transform backpressure changes"
This reverts commit 86d1196. Resolving a parked write from the readable side ran the first parked transform() in the reader's async context. Conflicts with later commits are resolved by keeping their state record classes and the kParkedAlgorithmResult check used by ReadableStream.from(). Refs: #65143 Refs: #66906 Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 6491a18 commit beeec2e

1 file changed

Lines changed: 43 additions & 104 deletions

File tree

‎lib/internal/webstreams/transformstream.js‎

Lines changed: 43 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ const {
55
ObjectDefineProperties,
66
ObjectSetPrototypeOf,
77
PromisePrototypeThen,
8-
PromiseReject,
9-
PromiseResolve,
108
PromiseWithResolvers,
119
Symbol,
1210
SymbolToStringTag,
@@ -46,14 +44,12 @@ const {
4644

4745
const {
4846
createPromiseCallback1Param,
49-
createRawCallback2Params,
47+
createPromiseCallback2Params,
5048
customInspect,
5149
extractHighWaterMark,
5250
extractSizeAlgorithm,
5351
getNonWritablePropertyDescriptor,
5452
isBrandCheck,
55-
kParkedAlgorithmResult,
56-
kResolvedPromise,
5753
kState,
5854
kType,
5955
nonOpCancel,
@@ -261,12 +257,7 @@ class TransformStreamState {
261257
writable = undefined;
262258
controller = undefined;
263259
backpressure = undefined;
264-
// Continuation slots replacing the spec's
265-
// [[backpressureChangePromise]]; see transformStreamSetBackpressure.
266-
pullPending = false;
267-
pendingWrite = undefined;
268-
pendingWriteChunk = undefined;
269-
writeContinuation = undefined;
260+
backpressureChange = undefined;
270261
}
271262
ObjectSetPrototypeOf(TransformStreamState.prototype, null);
272263

@@ -372,9 +363,7 @@ const isTransformStream =
372363
const isTransformStreamDefaultController =
373364
isBrandCheck('TransformStreamDefaultController');
374365

375-
// Raw callback (see createRawCallback*): invoked inside the try/catch of
376-
// transformStreamDefaultControllerPerformTransform.
377-
function defaultTransformAlgorithm(chunk, controller) {
366+
async function defaultTransformAlgorithm(chunk, controller) {
378367
transformStreamDefaultControllerEnqueue(controller, chunk);
379368
}
380369

@@ -444,30 +433,24 @@ function transformStreamUnblockWrite(stream) {
444433
// The spec's [[backpressureChangePromise]] is only ever observed by the
445434
// source pull algorithm (settles when backpressure next becomes true) and
446435
// by a sink write arriving while backpressure is set (settles when
447-
// backpressure next becomes false). Both observers are internal, so the
448-
// promise record is replaced by continuation slots: a parked pull is
449-
// completed by delivering the readable controller's pull-fulfilled step,
450-
// and a parked write by the cached write continuation (see
451-
// transformStreamDefaultSinkWriteAlgorithm). Each is enqueued on the
452-
// shared resolved promise at the exact microtask position the old
453-
// record's reaction would have had.
436+
// backpressure next becomes false). Instead of allocating a fresh promise
437+
// record on every flip, the record is materialized lazily on first
438+
// observation and dropped once settled; flips nobody is waiting on
439+
// allocate nothing.
440+
function transformStreamBackpressureChangePromise(stream) {
441+
const state = stream[kState];
442+
return (state.backpressureChange ??= PromiseWithResolvers()).promise;
443+
}
444+
454445
function transformStreamSetBackpressure(stream, backpressure) {
455446
const state = stream[kState];
456447
assert(state.backpressure !== backpressure);
457-
state.backpressure = backpressure;
458-
if (backpressure) {
459-
if (state.pullPending) {
460-
state.pullPending = false;
461-
// The pull-fulfilled step exists: a pull parked it (see
462-
// transformStreamDefaultSourcePullAlgorithm), and the readable
463-
// controller creates it before invoking the pull algorithm.
464-
PromisePrototypeThen(
465-
kResolvedPromise,
466-
state.readable[kState].controller[kState].pullFulfilled);
467-
}
468-
} else if (state.pendingWrite !== undefined) {
469-
PromisePrototypeThen(kResolvedPromise, state.writeContinuation);
448+
const backpressureChange = state.backpressureChange;
449+
if (backpressureChange !== undefined) {
450+
state.backpressureChange = undefined;
451+
backpressureChange.resolve();
470452
}
453+
state.backpressure = backpressure;
471454
}
472455

473456
function setupTransformStreamDefaultController(
@@ -495,7 +478,7 @@ function setupTransformStreamDefaultControllerFromTransformer(
495478
const flush = transformer?.flush;
496479
const cancel = transformer?.cancel;
497480
const transformAlgorithm = transform ?
498-
createRawCallback2Params('transformer.transform', transform, transformer) :
481+
createPromiseCallback2Params('transformer.transform', transform, transformer) :
499482
defaultTransformAlgorithm;
500483
const flushAlgorithm = flush ?
501484
createPromiseCallback1Param('transformer.flush', flush, transformer) :
@@ -548,40 +531,18 @@ function transformStreamDefaultControllerError(controller, error) {
548531
transformStreamError(controller[kState].stream, error);
549532
}
550533

551-
// Mirrors the reference implementation's
552-
// `promiseCall(transformAlgorithm, ...).then(undefined, rejectionSteps)`:
553-
// the returned promise settles one microtask after the (coerced) result
554-
// does, and a rejection errors the transform stream before propagating.
555-
// The raw transform callback plus the shared resolved promise for
556-
// non-thenable results replace the previous async wrapper's two implicit
557-
// promises per chunk.
558-
function transformStreamDefaultControllerPerformTransform(controller, chunk) {
559-
const controllerState = controller[kState];
560-
const transformAlgorithm = controllerState.transformAlgorithm;
561-
if (transformAlgorithm === undefined) {
562-
// Algorithms were cleared by a concurrent cancel/abort/close.
563-
return kResolvedPromise;
564-
}
565-
let result;
534+
async function transformStreamDefaultControllerPerformTransform(controller, chunk) {
566535
try {
567-
result = transformAlgorithm(chunk, controller);
536+
const transformAlgorithm = controller[kState].transformAlgorithm;
537+
if (transformAlgorithm === undefined) {
538+
// Algorithms were cleared by a concurrent cancel/abort/close.
539+
return;
540+
}
541+
return await transformAlgorithm(chunk, controller);
568542
} catch (error) {
569-
result = PromiseReject(error);
570-
}
571-
if (result === null ||
572-
(typeof result !== 'object' && typeof result !== 'function')) {
573-
result = kResolvedPromise;
574-
} else {
575-
result = PromiseResolve(result);
576-
}
577-
controllerState.performTransformRejected ??= (error) => {
578543
transformStreamError(controller[kState].stream, error);
579544
throw error;
580-
};
581-
return PromisePrototypeThen(
582-
result,
583-
undefined,
584-
controllerState.performTransformRejected);
545+
}
585546
}
586547

587548
function transformStreamDefaultControllerTerminate(controller) {
@@ -602,42 +563,26 @@ function transformStreamDefaultControllerTerminate(controller) {
602563
}
603564

604565
function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) {
605-
const state = stream[kState];
606566
const {
607567
writable,
608568
controller,
609-
} = state;
569+
} = stream[kState];
610570
assert(writable[kState].state === 'writable');
611-
if (state.backpressure) {
612-
// Park the chunk and one promise record; the backpressure -> false
613-
// flip delivers the cached continuation (see
614-
// transformStreamSetBackpressure) at the same microtask position as
615-
// the old [[backpressureChangePromise]] reaction. The continuation
616-
// resolves the sink promise with the perform-transform promise, so
617-
// adoption reproduces the old derived-chain settle depth exactly.
618-
// The writable dispatches a single write at a time, so one pending
619-
// slot suffices.
620-
assert(state.pendingWrite === undefined);
621-
const pendingWrite = PromiseWithResolvers();
622-
state.pendingWrite = pendingWrite;
623-
state.pendingWriteChunk = chunk;
624-
state.writeContinuation ??= () => {
625-
const pending = state.pendingWrite;
626-
const pendingChunk = state.pendingWriteChunk;
627-
state.pendingWrite = undefined;
628-
state.pendingWriteChunk = undefined;
629-
const writableState = state.writable[kState];
630-
if (writableState.state === 'erroring') {
631-
pending.reject(writableState.storedError);
632-
return;
633-
}
634-
assert(writableState.state === 'writable');
635-
pending.resolve(
636-
transformStreamDefaultControllerPerformTransform(
571+
if (stream[kState].backpressure) {
572+
const backpressureChange = transformStreamBackpressureChangePromise(stream);
573+
return PromisePrototypeThen(
574+
backpressureChange,
575+
() => {
576+
const {
577+
writable,
578+
} = stream[kState];
579+
if (writable[kState].state === 'erroring')
580+
throw writable[kState].storedError;
581+
assert(writable[kState].state === 'writable');
582+
return transformStreamDefaultControllerPerformTransform(
637583
controller,
638-
pendingChunk));
639-
};
640-
return pendingWrite.promise;
584+
chunk);
585+
});
641586
}
642587
return transformStreamDefaultControllerPerformTransform(controller, chunk);
643588
}
@@ -707,15 +652,9 @@ function transformStreamDefaultSinkCloseAlgorithm(stream) {
707652
}
708653

709654
function transformStreamDefaultSourcePullAlgorithm(stream) {
710-
const state = stream[kState];
711-
assert(state.backpressure);
655+
assert(stream[kState].backpressure);
712656
transformStreamSetBackpressure(stream, false);
713-
// Park the pull: the next backpressure -> true flip delivers the
714-
// pull-fulfilled step (see transformStreamSetBackpressure). The old
715-
// [[backpressureChangePromise]] this replaces was only ever resolved,
716-
// so the parked pull needs no rejection delivery.
717-
state.pullPending = true;
718-
return kParkedAlgorithmResult;
657+
return transformStreamBackpressureChangePromise(stream);
719658
}
720659

721660
function transformStreamDefaultSourceCancelAlgorithm(stream, reason) {

0 commit comments

Comments
 (0)