Skip to content

Commit 007d729

Browse files
authored
TransformStream cleanup using transformer.cancel
Add a "cancel" hook to "Transformer". This allows users to perform resource cleanup when the readable side of the TransformStream is cancelled, or the writable side is aborted. To preserve existing behavior, when the readable side is cancelled with a reason, the writable side is always immediately aborted with that same reason. The same is true in the reverse case. This means that the status of both sides is always either "closed", "erroring", or "erroring" when the "cancel" hook is called. "flush" and "cancel" are never both called. As per existing behavior, when the writable side is closed the "flush" hook is called. If the readable side is cancelled while a promise returned from "flush" is still pending, "cancel" is not called. In this scenario the readable side ends up in the "errored" state, while the writable side ends up in the "closed" state.
1 parent 449df68 commit 007d729

File tree

4 files changed

+234
-43
lines changed

4 files changed

+234
-43
lines changed

index.bs

+131-25
Original file line numberDiff line numberDiff line change
@@ -5501,13 +5501,15 @@ dictionary Transformer {
55015501
TransformerStartCallback start;
55025502
TransformerTransformCallback transform;
55035503
TransformerFlushCallback flush;
5504+
TransformerCancelCallback cancel;
55045505
any readableType;
55055506
any writableType;
55065507
};
55075508

55085509
callback TransformerStartCallback = any (TransformStreamDefaultController controller);
55095510
callback TransformerFlushCallback = Promise<undefined> (TransformStreamDefaultController controller);
55105511
callback TransformerTransformCallback = Promise<undefined> (any chunk, TransformStreamDefaultController controller);
5512+
callback TransformerCancelCallback = Promise<undefined> (any reason);
55115513
</xmp>
55125514

55135515
<dl>
@@ -5570,6 +5572,25 @@ callback TransformerTransformCallback = Promise<undefined> (any chunk, Transform
55705572
{{Transformer/flush|flush()}}; the stream is already in the process of successfully closing down,
55715573
and terminating it would be counterproductive.)
55725574

5575+
<dt><dfn dict-member for="Transformer" lt="cancel">cancel(<var ignore>reason</var>)</dfn></dt>
5576+
<dd>
5577+
<p>A function called when the [=readable side=] is cancelled, or when the [=writable side=] is
5578+
aborted.
5579+
5580+
<p>Typically this is used to clean up underlying transformer resources when the stream is aborted
5581+
or cancelled.
5582+
5583+
<p>If the cancellation process is asynchronous, the function can return a promise to signal
5584+
success or failure; the result will be communicated to the caller of
5585+
{{WritableStream/abort()|stream.writable.abort()}} or
5586+
{{ReadableStream/cancel()|stream.readable.cancel()}}. Throwing an exception is treated the same
5587+
as returning a rejected promise.
5588+
5589+
<p>(Note that there is no need to call
5590+
{{TransformStreamDefaultController/terminate()|controller.terminate()}} inside
5591+
{{Transformer/cancel|cancel()}}; the stream is already in the process of cancelling/aborting, and
5592+
terminating it would be counterproductive.)
5593+
55735594
<dt><dfn dict-member for="Transformer">readableType</dfn></dt>
55745595
<dd>
55755596
<p>This property is reserved for future use, so any attempts to supply a value will throw an
@@ -5583,8 +5604,8 @@ callback TransformerTransformCallback = Promise<undefined> (any chunk, Transform
55835604

55845605
The <code>controller</code> object passed to {{Transformer/start|start()}},
55855606
{{Transformer/transform|transform()}}, and {{Transformer/flush|flush()}} is an instance of
5586-
{{TransformStreamDefaultController}}, and has the ability to enqueue [=chunks=] to the [=readable
5587-
side=], or to terminate or error the stream.
5607+
{{TransformStreamDefaultController}}, and has the ability to enqueue [=chunks=] to the
5608+
[=readable side=], or to terminate or error the stream.
55885609

55895610
<h4 id="ts-prototype">Constructor and properties</h4>
55905611

@@ -5738,6 +5759,16 @@ the following table:
57385759
<th>Internal Slot</th>
57395760
<th>Description (<em>non-normative</em>)</th>
57405761
<tbody>
5762+
<tr>
5763+
<td><dfn>\[[cancelAlgorithm]]</dfn>
5764+
<td class="non-normative">A promise-returning algorithm, taking one argument (the reason for
5765+
cancellation), which communicates a requested cancellation to the [=transformer=]
5766+
<tr>
5767+
<td><dfn>\[[finishPromise]]</dfn>
5768+
<td class="non-normative">A promise which resolves on completion of either the
5769+
[=TransformStreamDefaultController/[[cancelAlgorithm]]=] or the
5770+
[=TransformStreamDefaultController/[[flushAlgorithm]]=]. If this field is unpopulated (that is,
5771+
undefined), then neither of those algorithms have been [=invoked=] yet
57415772
<tr>
57425773
<td><dfn>\[[flushAlgorithm]]</dfn>
57435774
<td class="non-normative">A promise-returning algorithm which communicates a requested close to
@@ -5831,8 +5862,7 @@ The following abstract operations operate on {{TransformStream}} instances at a
58315862
1. Let |pullAlgorithm| be the following steps:
58325863
1. Return ! [$TransformStreamDefaultSourcePullAlgorithm$](|stream|).
58335864
1. Let |cancelAlgorithm| be the following steps, taking a |reason| argument:
5834-
1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |reason|).
5835-
1. Return [=a promise resolved with=] undefined.
5865+
1. Return ! [$TransformStreamDefaultSourceCancelAlgorithm$](|stream|, |reason|).
58365866
1. Set |stream|.[=TransformStream/[[readable]]=] to ! [$CreateReadableStream$](|startAlgorithm|,
58375867
|pullAlgorithm|, |cancelAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|).
58385868
1. Set |stream|.[=TransformStream/[[backpressure]]=] and
@@ -5866,12 +5896,7 @@ The following abstract operations operate on {{TransformStream}} instances at a
58665896
1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|stream|.[=TransformStream/[[controller]]=]).
58675897
1. Perform !
58685898
[$WritableStreamDefaultControllerErrorIfNeeded$](|stream|.[=TransformStream/[[writable]]=].[=WritableStream/[[controller]]=], |e|).
5869-
1. If |stream|.[=TransformStream/[[backpressure]]=] is true, perform ! [$TransformStreamSetBackpressure$](|stream|,
5870-
false).
5871-
5872-
<p class="note">The [$TransformStreamDefaultSinkWriteAlgorithm$] abstract operation could be
5873-
waiting for the promise stored in the [=TransformStream/[[backpressureChangePromise]]=] slot to
5874-
resolve. The call to [$TransformStreamSetBackpressure$] ensures that the promise always resolves.
5899+
1. Perform ! [$TransformStreamUnblockWrite$](|stream|).
58755900
</div>
58765901

58775902
<div algorithm>
@@ -5886,6 +5911,19 @@ The following abstract operations operate on {{TransformStream}} instances at a
58865911
1. Set |stream|.[=TransformStream/[[backpressure]]=] to |backpressure|.
58875912
</div>
58885913

5914+
<div algorithm>
5915+
<dfn abstract-op lt="TransformStreamUnblockWrite"
5916+
id="transform-stream-unblock-write">TransformStreamUnblockWrite(|stream|)</dfn> performs the
5917+
following steps:
5918+
5919+
1. If |stream|.[=TransformStream/[[backpressure]]=] is true, perform ! [$TransformStreamSetBackpressure$](|stream|,
5920+
false).
5921+
5922+
<p class="note">The [$TransformStreamDefaultSinkWriteAlgorithm$] abstract operation could be
5923+
waiting for the promise stored in the [=TransformStream/[[backpressureChangePromise]]=] slot to
5924+
resolve. The call to [$TransformStreamSetBackpressure$] ensures that the promise always resolves.
5925+
</div>
5926+
58895927
<h4 id="ts-default-controller-abstract-ops">Default controllers</h4>
58905928

58915929
The following abstract operations support the implementaiton of the
@@ -5894,7 +5932,8 @@ The following abstract operations support the implementaiton of the
58945932
<div algorithm>
58955933
<dfn abstract-op lt="SetUpTransformStreamDefaultController"
58965934
id="set-up-transform-stream-default-controller">SetUpTransformStreamDefaultController(|stream|,
5897-
|controller|, |transformAlgorithm|, |flushAlgorithm|)</dfn> performs the following steps:
5935+
|controller|, |transformAlgorithm|, |flushAlgorithm|, |cancelAlgorithm|)</dfn> performs the
5936+
following steps:
58985937

58995938
1. Assert: |stream| [=implements=] {{TransformStream}}.
59005939
1. Assert: |stream|.[=TransformStream/[[controller]]=] is undefined.
@@ -5903,6 +5942,7 @@ The following abstract operations support the implementaiton of the
59035942
1. Set |controller|.[=TransformStreamDefaultController/[[transformAlgorithm]]=] to
59045943
|transformAlgorithm|.
59055944
1. Set |controller|.[=TransformStreamDefaultController/[[flushAlgorithm]]=] to |flushAlgorithm|.
5945+
1. Set |controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=] to |cancelAlgorithm|.
59065946
</div>
59075947

59085948
<div algorithm>
@@ -5916,15 +5956,20 @@ The following abstract operations support the implementaiton of the
59165956
1. If |result| is an abrupt completion, return [=a promise rejected with=] |result|.\[[Value]].
59175957
1. Otherwise, return [=a promise resolved with=] undefined.
59185958
1. Let |flushAlgorithm| be an algorithm which returns [=a promise resolved with=] undefined.
5959+
1. Let |cancelAlgorithm| be an algorithm which returns [=a promise resolved with=] undefined.
59195960
1. If |transformerDict|["{{Transformer/transform}}"] [=map/exists=], set |transformAlgorithm| to an
59205961
algorithm which takes an argument |chunk| and returns the result of [=invoking=]
59215962
|transformerDict|["{{Transformer/transform}}"] with argument list «&nbsp;|chunk|,
59225963
|controller|&nbsp;» and [=callback this value=] |transformer|.
59235964
1. If |transformerDict|["{{Transformer/flush}}"] [=map/exists=], set |flushAlgorithm| to an
59245965
algorithm which returns the result of [=invoking=] |transformerDict|["{{Transformer/flush}}"]
59255966
with argument list «&nbsp;|controller|&nbsp;» and [=callback this value=] |transformer|.
5967+
1. If |transformerDict|["{{Transformer/cancel}}"] [=map/exists=], set |cancelAlgorithm| to an
5968+
algorithm which takes an argument |reason| and returns the result of [=invoking=]
5969+
|transformerDict|["{{Transformer/cancel}}"] with argument list «&nbsp;|reason|&nbsp;» and
5970+
[=callback this value=] |transformer|.
59265971
1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|,
5927-
|transformAlgorithm|, |flushAlgorithm|).
5972+
|transformAlgorithm|, |flushAlgorithm|, |cancelAlgorithm|).
59285973
</div>
59295974

59305975
<div algorithm>
@@ -5943,6 +5988,7 @@ The following abstract operations support the implementaiton of the
59435988

59445989
1. Set |controller|.[=TransformStreamDefaultController/[[transformAlgorithm]]=] to undefined.
59455990
1. Set |controller|.[=TransformStreamDefaultController/[[flushAlgorithm]]=] to undefined.
5991+
1. Set |controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=] to undefined.
59465992
</div>
59475993

59485994
<div algorithm>
@@ -6033,36 +6079,89 @@ side=] of [=transform streams=].
60336079
id="transform-stream-default-sink-abort-algorithm">TransformStreamDefaultSinkAbortAlgorithm(|stream|,
60346080
|reason|)</dfn> performs the following steps:
60356081

6036-
1. Perform ! [$TransformStreamError$](|stream|, |reason|).
6037-
1. Return [=a promise resolved with=] undefined.
6082+
1. Let |controller| be |stream|.[=TransformStream/[[controller]]=].
6083+
1. If |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] is not undefined, return
6084+
|controller|.[=TransformStreamDefaultController/[[finishPromise]]=].
6085+
1. Let |readable| be |stream|.[=TransformStream/[[readable]]=].
6086+
1. Let |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] be a new promise.
6087+
1. Let |cancelPromise| be the result of performing
6088+
|controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=], passing |reason|.
6089+
1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|).
6090+
1. [=React=] to |cancelPromise|:
6091+
1. If |cancelPromise| was fulfilled, then:
6092+
1. If |readable|.[=ReadableStream/[[state]]=] is "`errored`", [=reject=]
6093+
|controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with
6094+
|readable|.[=ReadableStream/[[storedError]]=].
6095+
1. Otherwise:
6096+
1. Perform ! [$ReadableStreamDefaultControllerError$](|readable|.[=ReadableStream/[[controller]]=], |reason|).
6097+
1. [=Resolve=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with undefined.
6098+
1. If |cancelPromise| was rejected with reason |r|, then:
6099+
1. Perform ! [$ReadableStreamDefaultControllerError$](|readable|.[=ReadableStream/[[controller]]=], |r|).
6100+
1. [=Reject=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with |r|.
6101+
1. Return |controller|.[=TransformStreamDefaultController/[[finishPromise]]=].
60386102
</div>
60396103

60406104
<div algorithm>
60416105
<dfn abstract-op lt="TransformStreamDefaultSinkCloseAlgorithm"
60426106
id="transform-stream-default-sink-close-algorithm">TransformStreamDefaultSinkCloseAlgorithm(|stream|)</dfn>
60436107
performs the following steps:
60446108

6045-
1. Let |readable| be |stream|.[=TransformStream/[[readable]]=].
60466109
1. Let |controller| be |stream|.[=TransformStream/[[controller]]=].
6110+
1. If |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] is not undefined, return
6111+
|controller|.[=TransformStreamDefaultController/[[finishPromise]]=].
6112+
1. Let |readable| be |stream|.[=TransformStream/[[readable]]=].
6113+
1. Let |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] be a new promise.
60476114
1. Let |flushPromise| be the result of performing
60486115
|controller|.[=TransformStreamDefaultController/[[flushAlgorithm]]=].
60496116
1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|).
6050-
1. Return the result of [=reacting=] to |flushPromise|:
6117+
1. [=React=] to |flushPromise|:
60516118
1. If |flushPromise| was fulfilled, then:
6052-
1. If |readable|.[=ReadableStream/[[state]]=] is "`errored`", throw
6119+
1. If |readable|.[=ReadableStream/[[state]]=] is "`errored`", [=reject=]
6120+
|controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with
60536121
|readable|.[=ReadableStream/[[storedError]]=].
6054-
1. Perform !
6055-
[$ReadableStreamDefaultControllerClose$](|readable|.[=ReadableStream/[[controller]]=]).
6122+
1. Otherwise:
6123+
1. Perform ! [$ReadableStreamDefaultControllerClose$](|readable|.[=ReadableStream/[[controller]]=]).
6124+
1. [=Resolve=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with undefined.
60566125
1. If |flushPromise| was rejected with reason |r|, then:
6057-
1. Perform ! [$TransformStreamError$](|stream|, |r|).
6058-
1. Throw |readable|.[=ReadableStream/[[storedError]]=].
6126+
1. Perform ! [$ReadableStreamDefaultControllerError$](|readable|.[=ReadableStream/[[controller]]=], |r|).
6127+
1. [=Reject=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with |r|.
6128+
1. Return |controller|.[=TransformStreamDefaultController/[[finishPromise]]=].
60596129
</div>
60606130

60616131
<h4 id="ts-default-source-abstract-ops">Default sources</h4>
60626132

60636133
The following abstract operation is used to implement the [=underlying source=] for the [=readable
60646134
side=] of [=transform streams=].
60656135

6136+
<div algorithm>
6137+
<dfn abstract-op lt="TransformStreamDefaultSourceCancelAlgorithm"
6138+
id="transform-stream-default-source-cancel">TransformStreamDefaultSourceCancelAlgorithm(|stream|,
6139+
|reason|)</dfn> performs the following steps:
6140+
6141+
1. Let |controller| be |stream|.[=TransformStream/[[controller]]=].
6142+
1. If |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] is not undefined, return
6143+
|controller|.[=TransformStreamDefaultController/[[finishPromise]]=].
6144+
1. Let |writable| be |stream|.[=TransformStream/[[writable]]=].
6145+
1. Let |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] be a new promise.
6146+
1. Let |cancelPromise| be the result of performing
6147+
|controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=], passing |reason|.
6148+
1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|).
6149+
1. [=React=] to |cancelPromise|:
6150+
1. If |cancelPromise| was fulfilled, then:
6151+
1. If |writable|.[=WritableStream/[[state]]=] is "`errored`", [=reject=]
6152+
|controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with
6153+
|writable|.[=WritableStream/[[storedError]]=].
6154+
1. Otherwise:
6155+
1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|writable|.[=WritableStream/[[controller]]=], |reason|).
6156+
1. Perform ! [$TransformStreamUnblockWrite$](|stream|).
6157+
1. [=Resolve=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with undefined.
6158+
1. If |cancelPromise| was rejected with reason |r|, then:
6159+
1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|writable|.[=WritableStream/[[controller]]=], |r|).
6160+
1. Perform ! [$TransformStreamUnblockWrite$](|stream|).
6161+
1. [=Reject=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with |r|.
6162+
1. Return |controller|.[=TransformStreamDefaultController/[[finishPromise]]=].
6163+
</div>
6164+
60666165
<div algorithm>
60676166
<dfn abstract-op lt="TransformStreamDefaultSourcePullAlgorithm"
60686167
id="transform-stream-default-source-pull">TransformStreamDefaultSourcePullAlgorithm(|stream|)</dfn>
@@ -7118,9 +7217,10 @@ reason.
71187217
<div algorithm="create a TransformStream">
71197218
To <dfn export for="TransformStream" lt="set up|setting up">set up</dfn> a
71207219
newly-[=new|created-via-Web IDL=] {{TransformStream}} |stream| given an algorithm <dfn export
7121-
for="TransformStream/set up"><var>transformAlgorithm</var></dfn> and an optional algorithm <dfn
7122-
export for="TransformStream/set up"><var>flushAlgorithm</var></dfn>, perform the following steps.
7123-
|transformAlgorithm| and, if given, |flushAlgorithm|, may return a promise.
7220+
for="TransformStream/set up"><var>transformAlgorithm</var></dfn>, an optional algorithm <dfn
7221+
export for="TransformStream/set up"><var>flushAlgorithm</var></dfn>, and an optional algorithm <dfn
7222+
export for="TransformStream/set up"><var>cancelAlgorithm</var></dfn>, perform the following steps.
7223+
|transformAlgorithm| and, if given, |flushAlgorithm| and |cancelAlgorithm|, may return a promise.
71247224

71257225
1. Let |writableHighWaterMark| be 1.
71267226
1. Let |writableSizeAlgorithm| be an algorithm that returns 1.
@@ -7136,12 +7236,18 @@ reason.
71367236
null otherwise. If this throws an exception |e|, return [=a promise rejected with=] |e|.
71377237
1. If |result| is a {{Promise}}, then return |result|.
71387238
1. Return [=a promise resolved with=] undefined.
7239+
1. Let |cancelAlgorithmWrapper| be an algorithm that runs these steps given a value |reason|:
7240+
1. Let |result| be the result of running |cancelAlgorithm| given |reason|, if |cancelAlgorithm|
7241+
was given, or null otherwise. If this throws an exception |e|, return
7242+
[=a promise rejected with=] |e|.
7243+
1. If |result| is a {{Promise}}, then return |result|.
7244+
1. Return [=a promise resolved with=] undefined.
71397245
1. Let |startPromise| be [=a promise resolved with=] undefined.
71407246
1. Perform ! [$InitializeTransformStream$](|stream|, |startPromise|, |writableHighWaterMark|,
71417247
|writableSizeAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|).
71427248
1. Let |controller| be a [=new=] {{TransformStreamDefaultController}}.
71437249
1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|,
7144-
|transformAlgorithmWrapper|, |flushAlgorithmWrapper|).
7250+
|transformAlgorithmWrapper|, |flushAlgorithmWrapper|, |cancelAlgorithmWrapper|).
71457251

71467252
Other specifications should be careful when constructing their
71477253
<i>[=TransformStream/set up/transformAlgorithm=]</i> to avoid [=in parallel=] reads from the given

reference-implementation/lib/Transformer.webidl

+2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ dictionary Transformer {
22
TransformerStartCallback start;
33
TransformerTransformCallback transform;
44
TransformerFlushCallback flush;
5+
TransformerCancelCallback cancel;
56
any readableType;
67
any writableType;
78
};
89

910
callback TransformerStartCallback = any (TransformStreamDefaultController controller);
1011
callback TransformerFlushCallback = Promise<undefined> (TransformStreamDefaultController controller);
1112
callback TransformerTransformCallback = Promise<undefined> (any chunk, TransformStreamDefaultController controller);
13+
callback TransformerCancelCallback = Promise<undefined> (any reason);

0 commit comments

Comments
 (0)