Skip to content

Commit 6450334

Browse files
authored
feat(ext/web): cancel support for TransformStream (#20815)
1 parent 6bbccb7 commit 6450334

3 files changed

Lines changed: 112 additions & 34 deletions

File tree

ext/web/06_streams.js

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ const _controller = Symbol("[[controller]]");
317317
const _detached = Symbol("[[Detached]]");
318318
const _disturbed = Symbol("[[disturbed]]");
319319
const _errorSteps = Symbol("[[ErrorSteps]]");
320+
const _finishPromise = Symbol("[[finishPromise]]");
320321
const _flushAlgorithm = Symbol("[[flushAlgorithm]]");
321322
const _globalObject = Symbol("[[globalObject]]");
322323
const _highWaterMark = Symbol("[[highWaterMark]]");
@@ -609,8 +610,7 @@ function initializeTransformStream(
609610
}
610611

611612
function cancelAlgorithm(reason) {
612-
transformStreamErrorWritableAndUnblockWrite(stream, reason);
613-
return resolvePromiseWith(undefined);
613+
return transformStreamDefaultSourceCancelAlgorithm(stream, reason);
614614
}
615615

616616
stream[_readable] = createReadableStream(
@@ -3690,19 +3690,22 @@ function setUpReadableStreamDefaultReader(reader, stream) {
36903690
* @param {TransformStreamDefaultController<O>} controller
36913691
* @param {(chunk: O, controller: TransformStreamDefaultController<O>) => Promise<void>} transformAlgorithm
36923692
* @param {(controller: TransformStreamDefaultController<O>) => Promise<void>} flushAlgorithm
3693+
* @param {(reason: any) => Promise<void>} cancelAlgorithm
36933694
*/
36943695
function setUpTransformStreamDefaultController(
36953696
stream,
36963697
controller,
36973698
transformAlgorithm,
36983699
flushAlgorithm,
3700+
cancelAlgorithm,
36993701
) {
37003702
assert(ObjectPrototypeIsPrototypeOf(TransformStreamPrototype, stream));
37013703
assert(stream[_controller] === undefined);
37023704
controller[_stream] = stream;
37033705
stream[_controller] = controller;
37043706
controller[_transformAlgorithm] = transformAlgorithm;
37053707
controller[_flushAlgorithm] = flushAlgorithm;
3708+
controller[_cancelAlgorithm] = cancelAlgorithm;
37063709
}
37073710

37083711
/**
@@ -3730,6 +3733,8 @@ function setUpTransformStreamDefaultControllerFromTransformer(
37303733
};
37313734
/** @type {(controller: TransformStreamDefaultController<O>) => Promise<void>} */
37323735
let flushAlgorithm = () => resolvePromiseWith(undefined);
3736+
/** @type {(reason: any) => Promise<void>} */
3737+
let cancelAlgorithm = () => resolvePromiseWith(undefined);
37333738
if (transformerDict.transform !== undefined) {
37343739
transformAlgorithm = (chunk, controller) =>
37353740
webidl.invokeCallbackFunction(
@@ -3752,11 +3757,23 @@ function setUpTransformStreamDefaultControllerFromTransformer(
37523757
true,
37533758
);
37543759
}
3760+
if (transformerDict.cancel !== undefined) {
3761+
cancelAlgorithm = (reason) =>
3762+
webidl.invokeCallbackFunction(
3763+
transformerDict.cancel,
3764+
[reason],
3765+
transformer,
3766+
webidl.converters["Promise<undefined>"],
3767+
"Failed to call 'cancelAlgorithm' on 'TransformStreamDefaultController'",
3768+
true,
3769+
);
3770+
}
37553771
setUpTransformStreamDefaultController(
37563772
stream,
37573773
controller,
37583774
transformAlgorithm,
37593775
flushAlgorithm,
3776+
cancelAlgorithm,
37603777
);
37613778
}
37623779

@@ -3938,6 +3955,7 @@ function setUpWritableStreamDefaultWriter(writer, stream) {
39383955
function transformStreamDefaultControllerClearAlgorithms(controller) {
39393956
controller[_transformAlgorithm] = undefined;
39403957
controller[_flushAlgorithm] = undefined;
3958+
controller[_cancelAlgorithm] = undefined;
39413959
}
39423960

39433961
/**
@@ -4007,13 +4025,33 @@ function transformStreamDefaultControllerTerminate(controller) {
40074025
}
40084026

40094027
/**
4010-
* @param {TransformStream} stream
4028+
* @template I
4029+
* @template O
4030+
* @param {TransformStream<I, O>} stream
40114031
* @param {any=} reason
40124032
* @returns {Promise<void>}
40134033
*/
40144034
function transformStreamDefaultSinkAbortAlgorithm(stream, reason) {
4015-
transformStreamError(stream, reason);
4016-
return resolvePromiseWith(undefined);
4035+
const controller = stream[_controller];
4036+
if (controller[_finishPromise] !== undefined) {
4037+
return controller[_finishPromise].promise;
4038+
}
4039+
const readable = stream[_readable];
4040+
controller[_finishPromise] = new Deferred();
4041+
const cancelPromise = controller[_cancelAlgorithm](reason);
4042+
transformStreamDefaultControllerClearAlgorithms(controller);
4043+
transformPromiseWith(cancelPromise, () => {
4044+
if (readable[_state] === "errored") {
4045+
controller[_finishPromise].reject(readable[_storedError]);
4046+
} else {
4047+
readableStreamDefaultControllerError(readable[_controller], reason);
4048+
controller[_finishPromise].resolve(undefined);
4049+
}
4050+
}, (r) => {
4051+
readableStreamDefaultControllerError(readable[_controller], r);
4052+
controller[_finishPromise].reject(r);
4053+
});
4054+
return controller[_finishPromise].promise;
40174055
}
40184056

40194057
/**
@@ -4023,21 +4061,26 @@ function transformStreamDefaultSinkAbortAlgorithm(stream, reason) {
40234061
* @returns {Promise<void>}
40244062
*/
40254063
function transformStreamDefaultSinkCloseAlgorithm(stream) {
4026-
const readable = stream[_readable];
40274064
const controller = stream[_controller];
4065+
if (controller[_finishPromise] !== undefined) {
4066+
return controller[_finishPromise].promise;
4067+
}
4068+
const readable = stream[_readable];
4069+
controller[_finishPromise] = new Deferred();
40284070
const flushPromise = controller[_flushAlgorithm](controller);
40294071
transformStreamDefaultControllerClearAlgorithms(controller);
4030-
return transformPromiseWith(flushPromise, () => {
4072+
transformPromiseWith(flushPromise, () => {
40314073
if (readable[_state] === "errored") {
4032-
throw readable[_storedError];
4074+
controller[_finishPromise].reject(readable[_storedError]);
4075+
} else {
4076+
readableStreamDefaultControllerClose(readable[_controller]);
4077+
controller[_finishPromise].resolve(undefined);
40334078
}
4034-
readableStreamDefaultControllerClose(
4035-
/** @type {ReadableStreamDefaultController} */ readable[_controller],
4036-
);
40374079
}, (r) => {
4038-
transformStreamError(stream, r);
4039-
throw readable[_storedError];
4080+
readableStreamDefaultControllerError(readable[_controller], r);
4081+
controller[_finishPromise].reject(r);
40404082
});
4083+
return controller[_finishPromise].promise;
40414084
}
40424085

40434086
/**
@@ -4069,6 +4112,41 @@ function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) {
40694112
return transformStreamDefaultControllerPerformTransform(controller, chunk);
40704113
}
40714114

4115+
/**
4116+
* @template I
4117+
* @template O
4118+
* @param {TransformStream<I, O>} stream
4119+
* @param {any=} reason
4120+
* @returns {Promise<void>}
4121+
*/
4122+
function transformStreamDefaultSourceCancelAlgorithm(stream, reason) {
4123+
const controller = stream[_controller];
4124+
if (controller[_finishPromise] !== undefined) {
4125+
return controller[_finishPromise].promise;
4126+
}
4127+
const writable = stream[_writable];
4128+
controller[_finishPromise] = new Deferred();
4129+
const cancelPromise = controller[_cancelAlgorithm](reason);
4130+
transformStreamDefaultControllerClearAlgorithms(controller);
4131+
transformPromiseWith(cancelPromise, () => {
4132+
if (writable[_state] === "errored") {
4133+
controller[_finishPromise].reject(writable[_storedError]);
4134+
} else {
4135+
writableStreamDefaultControllerErrorIfNeeded(
4136+
writable[_controller],
4137+
reason,
4138+
);
4139+
transformStreamUnblockWrite(stream);
4140+
controller[_finishPromise].resolve(undefined);
4141+
}
4142+
}, (r) => {
4143+
writableStreamDefaultControllerErrorIfNeeded(writable[_controller], r);
4144+
transformStreamUnblockWrite(stream);
4145+
controller[_finishPromise].reject(r);
4146+
});
4147+
return controller[_finishPromise].promise;
4148+
}
4149+
40724150
/**
40734151
* @param {TransformStream} stream
40744152
* @returns {Promise<void>}
@@ -4104,9 +4182,7 @@ function transformStreamErrorWritableAndUnblockWrite(stream, e) {
41044182
stream[_writable][_controller],
41054183
e,
41064184
);
4107-
if (stream[_backpressure] === true) {
4108-
transformStreamSetBackpressure(stream, false);
4109-
}
4185+
transformStreamUnblockWrite(stream);
41104186
}
41114187

41124188
/**
@@ -4122,6 +4198,15 @@ function transformStreamSetBackpressure(stream, backpressure) {
41224198
stream[_backpressure] = backpressure;
41234199
}
41244200

4201+
/**
4202+
* @param {TransformStream} stream
4203+
*/
4204+
function transformStreamUnblockWrite(stream) {
4205+
if (stream[_backpressure] === true) {
4206+
transformStreamSetBackpressure(stream, false);
4207+
}
4208+
}
4209+
41254210
/**
41264211
* @param {WritableStream} stream
41274212
* @param {any=} reason
@@ -6007,6 +6092,10 @@ const TransformStreamPrototype = TransformStream.prototype;
60076092

60086093
/** @template O */
60096094
class TransformStreamDefaultController {
6095+
/** @type {(reason: any) => Promise<void>} */
6096+
[_cancelAlgorithm];
6097+
/** @type {Promise<void> | undefined} */
6098+
[_finishPromise];
60106099
/** @type {(controller: this) => Promise<void>} */
60116100
[_flushAlgorithm];
60126101
/** @type {TransformStream<O>} */

ext/web/lib.deno_web.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@ declare interface Transformer<I = any, O = any> {
933933
readableType?: undefined;
934934
start?: TransformStreamDefaultControllerCallback<O>;
935935
transform?: TransformStreamDefaultControllerTransformCallback<I, O>;
936+
cancel?: (reason: any) => Promise<void>;
936937
writableType?: undefined;
937938
}
938939

tools/wpt/expectation.json

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3161,32 +3161,20 @@
31613161
"transform-streams": {
31623162
"backpressure.any.html": true,
31633163
"backpressure.any.worker.html": true,
3164-
"errors.any.html": [
3165-
"controller.error() should close writable immediately after readable.cancel()"
3166-
],
3167-
"errors.any.worker.html": [
3168-
"controller.error() should close writable immediately after readable.cancel()"
3169-
],
3164+
"errors.any.html": true,
3165+
"errors.any.worker.html": true,
31703166
"flush.any.html": true,
31713167
"flush.any.worker.html": true,
3172-
"general.any.html": [
3173-
"terminate() should abort writable immediately after readable.cancel()"
3174-
],
3175-
"general.any.worker.html": [
3176-
"terminate() should abort writable immediately after readable.cancel()"
3177-
],
3168+
"general.any.html": true,
3169+
"general.any.worker.html": true,
31783170
"lipfuzz.any.html": true,
31793171
"lipfuzz.any.worker.html": true,
31803172
"patched-global.any.html": true,
31813173
"patched-global.any.worker.html": true,
31823174
"properties.any.html": true,
31833175
"properties.any.worker.html": true,
3184-
"reentrant-strategies.any.html": [
3185-
"writer.abort() inside size() should work"
3186-
],
3187-
"reentrant-strategies.any.worker.html": [
3188-
"writer.abort() inside size() should work"
3189-
],
3176+
"reentrant-strategies.any.html": true,
3177+
"reentrant-strategies.any.worker.html": true,
31903178
"strategies.any.html": true,
31913179
"strategies.any.worker.html": true,
31923180
"terminate.any.html": true,

0 commit comments

Comments
 (0)