Title: pipe() allow destroying source! · Issue #55010 · nodejs/node · GitHub
Open Graph Title: pipe() allow destroying source! · Issue #55010 · nodejs/node
X Title: pipe() allow destroying source! · Issue #55010 · nodejs/node
Description: Node.js JavaScript runtime ✨🐢🚀✨. Contribute to nodejs/node development by creating an account on GitHub.
Open Graph Description: Node.js JavaScript runtime ✨🐢🚀✨. Contribute to nodejs/node development by creating an account on GitHub.
X Description: Node.js JavaScript runtime ✨🐢🚀✨. Contribute to nodejs/node development by creating an account on GitHub.
Opengraph URL: https://github.com/nodejs/node
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"pipe() allow destroying source!","articleBody":"### What is the problem this feature will solve?\r\n\r\nnode 20.15.1. The idea is to pass around a stream without having to track the sources and being able to destroy it. So the transformer kinda starts owning control of end of life of it's source. Example use case: Implemet tail -f and abort after 5 lines. Then reading bytes is the source, and turning into lines is a transformer. The consumer (who wants to find a mysqld ready for connections in the log) should not care about the tail -f implementation.\r\n\r\n```\r\nstream = createSource().pipe(transformer)\r\nstream.destroy()\r\n\r\n```\r\nshould destroy the source, too.\r\nThis doesn't happen.\r\n\r\ncomplete test case\r\n\r\n```\r\n// this file tests that using pipeDestroySource actually destroys\r\n// the reader while standard .pipe() doesn't ?\r\nimport {Readable, Writable, Transform} from \"node:stream\";\r\n\r\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))\r\n\r\nexport const pipeDestroySource = (source: Readable, destination: Writable) => {\r\n // unpipe or close might work\r\n destination.on('unpipe', (e) => { if (e == source) source.destroy()})\r\n return source.pipe(destination)\r\n}\r\n\r\nexport type OnDestroy = () => void\r\n\r\nclass MyReadStream extends Readable {\r\n\r\n constructor(private ondestroy: OnDestroy) {\r\n super({autoDestroy: true});\r\n }\r\n\r\n _read() {\r\n this.push(Buffer.from(\"A\\n\"))\r\n }\r\n _destroy(err: any, callback: any) {\r\n console.log(\"MyReadStream _destroy\");\r\n this.ondestroy()\r\n callback(err);\r\n }\r\n} \r\n\r\n\r\nexport class MyTransform extends Transform {\r\n\r\n constructor(private ondestroy: OnDestroy) {\r\n super({objectMode: true, autoDestroy: true});\r\n }\r\n\r\n _transform(chunk: Buffer, encoding: string, callback: any) {\r\n this.push(chunk)\r\n callback();\r\n }\r\n\r\n _destroy(err: any, callback: any){\r\n console.log('MyTransform _destroy');\r\n this.ondestroy()\r\n super._destroy(err, callback)\r\n }\r\n\r\n}\r\nconst main = async () => {\r\n const test = async (title: string, pipe: (source: Readable, destination: Writable) => Writable, expectations: boolean[] ) => {\r\n console.log(`=== test ${title} started`);\r\n let r_destroy_called = false\r\n let t_destroy_called = false\r\n const r = new MyReadStream(() => { r_destroy_called = true})\r\n const t = new MyTransform(() => { t_destroy_called = true })\r\n const s = pipe(r, t)\r\n s.destroy()\r\n\r\n // logging won't work because the events actions will be run after this method returned!\r\n // console.log({r_destroy_called, t_destroy_called});\r\n console.log(`test ${title} done`);\r\n }\r\n\r\n await test( \"pipe\",\r\n (s, d) => s.pipe(d),\r\n [false, true]\r\n )\r\n\r\n await sleep(1000)\r\n\r\n await test(\r\n \"pipeDestroySource\",\r\n pipeDestroySource,\r\n [true, true]\r\n )\r\n}\r\n\r\nmain().catch((e) => {\r\n console.log(\"== ERROR uncaught exception ==\");\r\n console.log(e);\r\n process.exit(1)\r\n})\r\n\r\n```\r\n\r\n\r\n### What is the feature you are proposing to solve the problem?\r\n\r\n```\r\nstream = createSource().pipe(transformer, { destroySource: true })\r\nstream.destroy()\r\n```\r\n\r\n### What alternatives have you considered?\r\n\r\nusing this code instead:\r\n```\r\nexport const pipeDestroySource = (source: Readable, destination: Writable) => {\r\n // unpipe or close might work\r\n destination.on('unpipe', (e) => { if (e == source) source.destroy()})\r\n return source.pipe(destination)\r\n}\r\n```","author":{"url":"https://github.com/MarcWeber","@type":"Person","name":"MarcWeber"},"datePublished":"2024-09-19T12:38:21Z","url":"https://github.com/nodejs/node/issues/55010"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /:user_id/:repository/issues/:id(.:format) |
| route-controller | issues |
| route-action | show |
| fetch-nonce | v2:bd08d169-b9c2-6bad-52a3-0f436165db22 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9DEE:34E7AF:9A67E4:CA7883:6AED3FC8 |
| html-safe-nonce | e88d6ea365c869dd3fcb48cf8d17fa6f3f263c150674cc91ce8e08377e68db5f |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5REVFOjM0RTdBRjo5QTY3RTQ6Q0E3ODgzOjZBRUQzRkM4IiwidmlzaXRvcl9pZCI6IjE1Nzg3NTcyNzQ1OTcxNDY1NjgiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | bcc7de9590a8431a25ecbf3b58ef0b485a0722b986a99071062df237594fa31d |
| hovercard-subject-tag | repository:27193779 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/nodejs/node/issues/55010 |
| twitter:image | https://opengraph.githubassets.com/c98c20307051bad41306fc36e9903183c09244beae86eb8696467effbe10f1d6/nodejs/node |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/c98c20307051bad41306fc36e9903183c09244beae86eb8696467effbe10f1d6/nodejs/node |
| og:image:alt | Node.js JavaScript runtime ✨🐢🚀✨. Contribute to nodejs/node development by creating an account on GitHub. |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 26c92a9388ddfad3fa88d132b5d5f0b4747030233dcc4e5efb05f1cbae152184 |
| turbo-cache-control | no-cache |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | c629ad99c254d306375fc8354d04321c2c362236 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width