Skip to content

Commit c05a97a

Browse files
mcollinaaduh95
authored andcommitted
stream: reject writes during WritableStream close
WritableStreamDefaultControllerProcessClose clears the controller algorithms while the stream is still 'writable', before the sink close settles. A write in that window, such as writer.write() after writer.close() while the sink's close() or a TransformStream's flush() is pending, reached the cleared size algorithm and failed an internal assertion instead of being rejected as closing. Accept that state in writableStreamDefaultControllerGetChunkSize so the write is rejected with a TypeError, as it is once the stream has closed. Refs: #57272 Refs: whatwg/streams#1341 Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #67058 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 101e729 commit c05a97a

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

‎lib/internal/webstreams/writablestream.js‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,9 +1264,11 @@ function writableStreamDefaultControllerGetChunkSize(controller, chunk) {
12641264
sizeAlgorithm,
12651265
} = controller[kState];
12661266
if (sizeAlgorithm === undefined) {
1267-
assert(stream[kState].state === 'closed' ||
1268-
stream[kState].state === 'errored' ||
1269-
stream[kState].state === 'erroring');
1267+
// The algorithms are also cleared while the close request is in
1268+
// flight, before the stream leaves the 'writable' state; the caller
1269+
// then rejects the write as closing.
1270+
assert(stream[kState].state !== 'writable' ||
1271+
stream[kState].closeQueuedOrInFlight);
12701272
return 1;
12711273
}
12721274

‎test/parallel/test-whatwg-writablestream-close.js‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,40 @@ test('should throw error when writing after close', async (t) => {
2727
}
2828
);
2929
});
30+
31+
test('should reject writes while the close is in flight', async () => {
32+
const { promise, resolve } = Promise.withResolvers();
33+
const writable = new WritableStream({
34+
close() {
35+
return promise;
36+
},
37+
});
38+
39+
const writer = writable.getWriter();
40+
const closed = writer.close();
41+
// Let the close request reach the sink.
42+
await new Promise(setImmediate);
43+
44+
await assert.rejects(writer.write('World'), { name: 'TypeError' });
45+
46+
resolve();
47+
await closed;
48+
});
49+
50+
test('should reject writes to a TransformStream while flush() is pending', async () => {
51+
const { promise, resolve } = Promise.withResolvers();
52+
const transform = new TransformStream({
53+
flush() {
54+
return promise;
55+
},
56+
});
57+
58+
const writer = transform.writable.getWriter();
59+
const closed = writer.close();
60+
await new Promise(setImmediate);
61+
62+
await assert.rejects(writer.write('World'), { name: 'TypeError' });
63+
64+
resolve();
65+
await closed;
66+
});

0 commit comments

Comments
 (0)