Commit 70e1db3deb6 for nodejs
commit 70e1db3deb6ed16dad31ace0dbc61e0d8c6be0ff
Author: Matteo Collina <hello@matteocollina.com>
Date: Mon Nov 2 09:35:07 2026 +0100
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: https://github.com/nodejs/node/issues/57272
Refs: https://github.com/whatwg/streams/issues/1341
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/67058
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js
index dfde280e2da..d790264d000 100644
--- a/lib/internal/webstreams/writablestream.js
+++ b/lib/internal/webstreams/writablestream.js
@@ -1264,9 +1264,11 @@ function writableStreamDefaultControllerGetChunkSize(controller, chunk) {
sizeAlgorithm,
} = controller[kState];
if (sizeAlgorithm === undefined) {
- assert(stream[kState].state === 'closed' ||
- stream[kState].state === 'errored' ||
- stream[kState].state === 'erroring');
+ // The algorithms are also cleared while the close request is in
+ // flight, before the stream leaves the 'writable' state; the caller
+ // then rejects the write as closing.
+ assert(stream[kState].state !== 'writable' ||
+ stream[kState].closeQueuedOrInFlight);
return 1;
}
diff --git a/test/parallel/test-whatwg-writablestream-close.js b/test/parallel/test-whatwg-writablestream-close.js
index 1b987cf0c41..d89340cbfcb 100644
--- a/test/parallel/test-whatwg-writablestream-close.js
+++ b/test/parallel/test-whatwg-writablestream-close.js
@@ -27,3 +27,40 @@ test('should throw error when writing after close', async (t) => {
}
);
});
+
+test('should reject writes while the close is in flight', async () => {
+ const { promise, resolve } = Promise.withResolvers();
+ const writable = new WritableStream({
+ close() {
+ return promise;
+ },
+ });
+
+ const writer = writable.getWriter();
+ const closed = writer.close();
+ // Let the close request reach the sink.
+ await new Promise(setImmediate);
+
+ await assert.rejects(writer.write('World'), { name: 'TypeError' });
+
+ resolve();
+ await closed;
+});
+
+test('should reject writes to a TransformStream while flush() is pending', async () => {
+ const { promise, resolve } = Promise.withResolvers();
+ const transform = new TransformStream({
+ flush() {
+ return promise;
+ },
+ });
+
+ const writer = transform.writable.getWriter();
+ const closed = writer.close();
+ await new Promise(setImmediate);
+
+ await assert.rejects(writer.write('World'), { name: 'TypeError' });
+
+ resolve();
+ await closed;
+});