stream: fix TransformStream race on cancel with pending write#62040
stream: fix TransformStream race on cancel with pending write#62040nodejs-github-bot merged 4 commits intonodejs:mainfrom
Conversation
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #62040 +/- ##
==========================================
- Coverage 89.65% 89.65% -0.01%
==========================================
Files 676 676
Lines 206231 206332 +101
Branches 39505 39532 +27
==========================================
+ Hits 184898 184981 +83
- Misses 13463 13471 +8
- Partials 7870 7880 +10
🚀 New features to boost your workflow:
|
Commit Queue failed- Loading data for nodejs/node/pull/62040 ✔ Done loading data for nodejs/node/pull/62040 ----------------------------------- PR info ------------------------------------ Title stream: fix TransformStream race on cancel with pending write (#62040) Author Marco <marco.piraccini@gmail.com> (@marcopiraccini) Branch marcopiraccini:fix-transform-stream-race -> nodejs:main Labels author ready, web streams Commits 2 - stream: fix TransformStream race on cancel with pending write - linting fixup Committers 1 - marcopiraccini <marco.piraccini@gmail.com> PR-URL: https://github.com/nodejs/node/pull/62040 Fixes: https://github.com/nodejs/node/issues/62036 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> ------------------------------ Generated metadata ------------------------------ PR-URL: https://github.com/nodejs/node/pull/62040 Fixes: https://github.com/nodejs/node/issues/62036 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> -------------------------------------------------------------------------------- ℹ This PR was created on Sat, 28 Feb 2026 13:15:07 GMT ✔ Approvals: 2 ✔ - Matteo Collina (@mcollina) (TSC): https://github.com/nodejs/node/pull/62040#pullrequestreview-3870446976 ✔ - Paolo Insogna (@ShogunPanda) (TSC): https://github.com/nodejs/node/pull/62040#pullrequestreview-3871584746 ✔ Last GitHub CI successful ℹ Last Full PR CI on 2026-02-28T21:40:44Z: https://ci.nodejs.org/job/node-test-pull-request/71504/ - Querying data for job/node-test-pull-request/71504/ ✔ Build data downloaded ✔ Last Jenkins CI successful -------------------------------------------------------------------------------- ✔ No git cherry-pick in progress ✔ No git am in progress ✔ No git rebase in progress -------------------------------------------------------------------------------- - Bringing origin/main up to date... From https://github.com/nodejs/node * branch main -> FETCH_HEAD ✔ origin/main is now up-to-date - Downloading patch for 62040 From https://github.com/nodejs/node * branch refs/pull/62040/merge -> FETCH_HEAD ✔ Fetched commits as a6e9e3217833..398e70014ef6 -------------------------------------------------------------------------------- [main b5306f06be] stream: fix TransformStream race on cancel with pending write Author: marcopiraccini <marco.piraccini@gmail.com> Date: Sat Feb 28 14:09:57 2026 +0100 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-whatwg-transformstream-cancel-write-race.js [main bb247edd45] linting fixup Author: marcopiraccini <marco.piraccini@gmail.com> Date: Sat Feb 28 14:23:30 2026 +0100 1 file changed, 2 insertions(+), 1 deletion(-) ✔ Patches applied There are 2 commits in the PR. Attempting autorebase. (node:368) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated. (Use `node --trace-deprecation ...` to show where the warning was created) Rebasing (2/4) Executing: git node land --amend --yes --------------------------------- New Message ---------------------------------- stream: fix TransformStream race on cancel with pending writehttps://github.com/nodejs/node/actions/runs/22577662568 |
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
|
Landed in 1647288 |
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [node](https://nodejs.org) ([source](https://github.com/nodejs/node)) | patch | `25.8.0` → `25.8.1` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>nodejs/node (node)</summary> ### [`v25.8.1`](https://github.com/nodejs/node/releases/tag/v25.8.1): 2026-03-11, Version 25.8.1 (Current), @​aduh95 [Compare Source](nodejs/node@v25.8.0...v25.8.1) ##### Notable Changes - \[[`ea87eea71a`](nodejs/node@ea87eea71a)] - **module**: fix extensionless CJS files in `"type": "module"` packages (Matteo Collina) [#​62083](nodejs/node#62083) ##### Commits - \[[`bab750d1b3`](nodejs/node@bab750d1b3)] - **build**: do not depend on V8 deps on `--without-bundled-v8` builds (Antoine du Hamel) [#​62033](nodejs/node#62033) - \[[`b26d1c7fcb`](nodejs/node@b26d1c7fcb)] - **crypto**: make --use-system-ca per-env rather than per-process (Aditi) [#​60678](nodejs/node#60678) - \[[`e362635abf`](nodejs/node@e362635abf)] - **crypto**: add missing AES dictionaries (Filip Skokan) [#​62099](nodejs/node#62099) - \[[`6f975db8af`](nodejs/node@6f975db8af)] - **crypto**: fix importKey required argument count check (Filip Skokan) [#​62099](nodejs/node#62099) - \[[`3beaf9c5fc`](nodejs/node@3beaf9c5fc)] - **deps**: update amaro to 1.1.8 (Node.js GitHub Bot) [#​62151](nodejs/node#62151) - \[[`53afb0edd8`](nodejs/node@53afb0edd8)] - **deps**: update sqlite to 3.52.0 (Node.js GitHub Bot) [#​62150](nodejs/node#62150) - \[[`a13ed052a1`](nodejs/node@a13ed052a1)] - **deps**: update merve to 1.2.0 (Node.js GitHub Bot) [#​62149](nodejs/node#62149) - \[[`2c850577b7`](nodejs/node@2c850577b7)] - **deps**: patch resb crate (Richard Lau) [#​62138](nodejs/node#62138) - \[[`37862a6728`](nodejs/node@37862a6728)] - **deps**: V8: cherry-pick [`aa0b288`](nodejs/node@aa0b288f87cc) (Richard Lau) [#​62136](nodejs/node#62136) - \[[`09191ad8b4`](nodejs/node@09191ad8b4)] - **deps**: update ada to 3.4.3 (Node.js GitHub Bot) [#​62049](nodejs/node#62049) - \[[`8d63a178fd`](nodejs/node@8d63a178fd)] - **doc**: copyedit `addons.md` (Antoine du Hamel) [#​62071](nodejs/node#62071) - \[[`83719ffb64`](nodejs/node@83719ffb64)] - **doc**: correct `util.convertProcessSignalToExitCode` validation behavior (René) [#​62134](nodejs/node#62134) - \[[`eeee7c7fb1`](nodejs/node@eeee7c7fb1)] - **doc**: add efekrskl as triager (Efe) [#​61876](nodejs/node#61876) - \[[`db150b2e69`](nodejs/node@db150b2e69)] - **doc**: fix markdown for `expectFailure` values (Jacob Smith) [#​62100](nodejs/node#62100) - \[[`d55a441e60`](nodejs/node@d55a441e60)] - **doc**: add title to index (Aviv Keller) [#​62046](nodejs/node#62046) - \[[`cc46204b48`](nodejs/node@cc46204b48)] - **doc**: include url.resolve() in DEP0169 application deprecation (Mike McCready) [#​62002](nodejs/node#62002) - \[[`1d91a7261e`](nodejs/node@1d91a7261e)] - **doc,module**: add missing doc for syncHooks.deregister() (Joyee Cheung) [#​61959](nodejs/node#61959) - \[[`5198573bee`](nodejs/node@5198573bee)] - **http**: fix use-after-free when freeParser is called during llhttp\_execute (Gerhard Stöbich) [#​62095](nodejs/node#62095) - \[[`f8793f80df`](nodejs/node@f8793f80df)] - **lib**: fix source map url parse in dynamic imports (Chengzhong Wu) [#​61990](nodejs/node#61990) - \[[`5439d0e0cf`](nodejs/node@5439d0e0cf)] - **meta**: bump actions/download-artifact from 7.0.0 to 8.0.0 (dependabot\[bot]) [#​62063](nodejs/node#62063) - \[[`27fd21943a`](nodejs/node@27fd21943a)] - **meta**: bump actions/upload-artifact from 6.0.0 to 7.0.0 (dependabot\[bot]) [#​62062](nodejs/node#62062) - \[[`5b266f3295`](nodejs/node@5b266f3295)] - **meta**: bump step-security/harden-runner from 2.14.2 to 2.15.0 (dependabot\[bot]) [#​62064](nodejs/node#62064) - \[[`ea87eea71a`](nodejs/node@ea87eea71a)] - **module**: fix extensionless CJS files in `"type": "module"` packages (Matteo Collina) [#​62083](nodejs/node#62083) - \[[`851228cd60`](nodejs/node@851228cd60)] - **sqlite**: handle stmt invalidation (Guilherme Araújo) [#​61877](nodejs/node#61877) - \[[`19efe60548`](nodejs/node@19efe60548)] - **src**: expose async context frame debugging helper to JS (Anna Henningsen) [#​62103](nodejs/node#62103) - \[[`0257e8072f`](nodejs/node@0257e8072f)] - **src**: make AsyncWrap subclass internal field counts explicit (Anna Henningsen) [#​62103](nodejs/node#62103) - \[[`975dafbe3b`](nodejs/node@975dafbe3b)] - **src**: release context frame in AsyncWrap::EmitDestroy (Gerhard Stöbich) [#​61995](nodejs/node#61995) - \[[`f2c08c7888`](nodejs/node@f2c08c7888)] - **src**: use validate\_ascii\_with\_errors instead of validate\_ascii (Сковорода Никита Андреевич) [#​61122](nodejs/node#61122) - \[[`0278461d83`](nodejs/node@0278461d83)] - **stream**: optimize webstreams pipeTo (Mattias Buelens) [#​62079](nodejs/node#62079) - \[[`4d62e95bfa`](nodejs/node@4d62e95bfa)] - **stream**: fix brotli error handling in web compression streams (Filip Skokan) [#​62107](nodejs/node#62107) - \[[`4bdcaf2865`](nodejs/node@4bdcaf2865)] - **stream**: improve Web Compression spec compliance (Filip Skokan) [#​62107](nodejs/node#62107) - \[[`a5b1be2045`](nodejs/node@a5b1be2045)] - **stream**: fix UTF-8 character corruption in fast-utf8-stream (Matteo Collina) [#​61745](nodejs/node#61745) - \[[`5632446c4e`](nodejs/node@5632446c4e)] - **stream**: fix TransformStream race on cancel with pending write (Marco) [#​62040](nodejs/node#62040) - \[[`f90fa9cd1a`](nodejs/node@f90fa9cd1a)] - **stream**: accept ArrayBuffer in CompressionStream and DecompressionStream (조수민) [#​61913](nodejs/node#61913) - \[[`00319eaa3a`](nodejs/node@00319eaa3a)] - **test**: update WPT for url to [`c928b19`](nodejs/node@c928b19ab0) (Node.js GitHub Bot) [#​62148](nodejs/node#62148) - \[[`456abc7d20`](nodejs/node@456abc7d20)] - **test**: update WPT for WebCryptoAPI to [`c9e9558`](nodejs/node@c9e955840a) (Node.js GitHub Bot) [#​62147](nodejs/node#62147) - \[[`82770cb7d3`](nodejs/node@82770cb7d3)] - **test**: improve WPT report runner (Filip Skokan) [#​62107](nodejs/node#62107) - \[[`cfc847d233`](nodejs/node@cfc847d233)] - **test**: update WPT compression to [`ae05f5c`](nodejs/node@ae05f5cb53) (Filip Skokan) [#​62107](nodejs/node#62107) - \[[`80f78f2737`](nodejs/node@80f78f2737)] - **test**: update WPT for WebCryptoAPI to [`42e4732`](nodejs/node@42e47329fd) (Node.js GitHub Bot) [#​62048](nodejs/node#62048) - \[[`8048e0508c`](nodejs/node@8048e0508c)] - **test**: fix skipping behavior for `test-runner-run-files-undefined` (Antoine du Hamel) [#​62026](nodejs/node#62026) - \[[`699a6214c6`](nodejs/node@699a6214c6)] - **tools**: revert timezone update GHA workflow to ubuntu-latest (Richard Lau) [#​62140](nodejs/node#62140) - \[[`1a453b550c`](nodejs/node@1a453b550c)] - **tools**: improve error handling in test426 update script (Rich Trott) [#​62121](nodejs/node#62121) - \[[`710dde5ee2`](nodejs/node@710dde5ee2)] - **tools**: fix `--node-builtin-modules-path` value in `shell.nix` (Antoine du Hamel) [#​62102](nodejs/node#62102) - \[[`dcb1cbb21f`](nodejs/node@dcb1cbb21f)] - **tools**: bump the eslint group across 1 directory with 2 updates (dependabot\[bot]) [#​62092](nodejs/node#62092) - \[[`7d0b758583`](nodejs/node@7d0b758583)] - **tools**: fix daily wpt workflow nighly release version lookup (Filip Skokan) [#​62076](nodejs/node#62076) - \[[`3e8c816f2e`](nodejs/node@3e8c816f2e)] - **tools**: fix example in release proposal linter (Richard Lau) [#​62074](nodejs/node#62074) - \[[`772d3d270d`](nodejs/node@772d3d270d)] - **tools**: bump minimatch from 3.1.3 to 3.1.5 in /tools/clang-format (dependabot\[bot]) [#​62013](nodejs/node#62013) - \[[`92f3b42672`](nodejs/node@92f3b42672)] - **tools**: bump eslint to v10, babel to v8.0.0-rc.2 (Huáng Jùnliàng) [#​61905](nodejs/node#61905) - \[[`deead95ec5`](nodejs/node@deead95ec5)] - **url**: suppress warnings from url.format/url.resolve inside node\_modules (René) [#​62005](nodejs/node#62005) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My42MS43IiwidXBkYXRlZEluVmVyIjoiNDMuNjEuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6OnBhdGNoIl19-->
Fixes: #62036
TransformStreamwhere a latewriter.write()racing withreader.cancel()could throw an internalTypeError: controller[kState].transformAlgorithm is not a functioncancel/abort/closeclears the controller's algorithms viatransformStreamDefaultControllerClearAlgorithmswhile a pending write reachestransformStreamDefaultControllerPerformTransformtransformStreamDefaultControllerPerformTransformto check iftransformAlgorithmis still callable before invoking itRace sequence
reader.read()releases backpressurereader.cancel()triggerstransformStreamDefaultSourceCancelAlgorithm, which callstransformStreamDefaultControllerClearAlgorithms— setstransformAlgorithm = undefinedwriter.write()enterstransformStreamDefaultSinkWriteAlgorithmand callstransformStreamDefaultControllerPerformTransformperformTransformtries to invoketransformAlgorithm()which is nowundefined→ TypeErrorFix
Guard
transformStreamDefaultControllerPerformTransformso that iftransformAlgorithmhas been cleared (stream is shutting down), the write returns silently rather than throwing an internal error. The writable side's existing error handling surfaces the appropriate stream-level error to the caller.Spec note
The WHATWG streams reference implementation has the same unguarded call in
TransformStreamDefaultControllerPerformTransform. The race is latent in the spec but rarely surfaces in browsers due to WebIDL callback wrapping adding an extra promise tick that changes the interleaving order (see whatwg/streams#1296). Node.js hits it consistently because it lacks that extra indirection layer.Test plan
test/parallel/test-whatwg-transformstream-cancel-write-race.jsreproduces the race.