You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a behavior that streams used to have (as of streams 1), but was never really fully specified, and was removed in streams2+. The result is that there's no standard way to prematurely end a stream from the outside, and the effects of prematurely ended streams are undefined (or surprising to our users).
FWIW: in WHATWG streams, we have readableStream.cancel() and writableStream.abort(). The former makes the readable stream look closed, whereas the latter makes the writable stream look errored. I can go into more detail on the reasoning if desired.
I think an overridable ._destroy(cb) would be good way to do this and then have all streams have a public .destroy that would call ._destroy once and only once. When the callback is called the stream would emit close and possibly error if an error is passed.
myStream._destroy=function(cb){closeResources(function(err){cb(err)})}myStream.destroy()// calls the above methodmyStream.destroy()// doesn't do anything
+1! I always end up bike-shedding my own destroy method, eg. for cleaning up the underlying source of a Readable. However I've never really been sure in what state I leave the stream...
close() normally, which will let any enqueued data finish
abort(err), which will error the stream and throw away any enqueued data, saying nobody should ever use it again
a third thing we haven't given a verb for, because it seemed uncommon and kind of bad, where you flush any enqueued data but still act as if the stream was closed normally. It seems bad especially to pretend things are OK if you've thrown away enqueued data, so we didn't add this.
@chrisdickinson the scenarios look right to me! at some point we should consider if .pipe should return to streams1 behavior and call .destroy on close as well (but that should be a different issue)
@domenic Ah, cool. That makes sense. Do errors travel up pipelines? (i.e., would something like http.createServer((req, res) => { fs.createReadStream(file).pipe(res) }) still leak descriptors when res aborts?)
@chrisdickinson yeah piping by default propagates errors backward and forward. If res becomes errored then fs.createReadStream(file) gets canceled so no leaked descriptors. Similarly if fs.createReadStream(file) becomes errored res would get aborted.
I think it is possible to have a method of propagating this kind of stream state up and down the pipeline that would be awesome too. At the moment the amount if error handling needed to properly prevent against potential leaks and errors is quite verbose and easily overlooked from the slick stream .pipe examples (particularly in the case of fs & http streams.
Activity
domenic commentedon Apr 3, 2015
FWIW: in WHATWG streams, we have
readableStream.cancel()andwritableStream.abort(). The former makes the readable stream look closed, whereas the latter makes the writable stream look errored. I can go into more detail on the reasoning if desired.mafintosh commentedon Apr 3, 2015
I think an overridable
._destroy(cb)would be good way to do this and then have all streams have a public.destroythat would call._destroyonce and only once. When the callback is called the stream would emitcloseand possiblyerrorif an error is passed.emilbayes commentedon Apr 3, 2015
+1! I always end up bike-shedding my own
destroymethod, eg. for cleaning up the underlying source of aReadable. However I've never really been sure in what state I leave the stream...chrisdickinson commentedon Apr 3, 2015
@domenic I'd definitely be interested in why prematurely ending a writable implies an error.
@mafintosh So, to make sure I've got this correct, a few of the potential event sequences are:
A.pipe(B).pipe(C); A.destroy()– streams2+, default _destroyA is still piped to B, which is still piped to C.
A.pipe(B).pipe(C); B.destroy()– streams2+, default _destroyB is still piped to C. A is still available to be re-piped, and may be paused or flowing.
A.pipe(B).pipe(C); B.destroy()– A is streams1, B and C are streams2+, default _destroyB is still piped to C. A is available for re-piping.
domenic commentedon Apr 3, 2015
@chrisdickinson there's like three things you can do:
mafintosh commentedon Apr 3, 2015
@chrisdickinson the scenarios look right to me! at some point we should consider if .pipe should return to streams1 behavior and call .destroy on close as well (but that should be a different issue)
chrisdickinson commentedon Apr 3, 2015
@domenic Ah, cool. That makes sense. Do errors travel up pipelines? (i.e., would something like
http.createServer((req, res) => { fs.createReadStream(file).pipe(res) })still leak descriptors whenresaborts?)domenic commentedon Apr 3, 2015
@chrisdickinson yeah piping by default propagates errors backward and forward. If
resbecomes errored thenfs.createReadStream(file)gets canceled so no leaked descriptors. Similarly iffs.createReadStream(file)becomes erroredreswould get aborted.chrisdickinson commentedon Apr 4, 2015
@domenic Ooh, jealous. :)
@mafintosh Cool. Starting to collect
.destroyusage throughout core here:net.Socket*Note:
http.IncomingMessageandhttp.OutgoingMessageboth exclusivelycall
.destroy().fs.ReadStreamfs.WriteStreamfs.SyncWriteStreamsonewman commentedon Apr 4, 2015
I'm +1. awesome ascii flow diagrams @chrisdickinson
I think it is possible to have a method of propagating this kind of stream state up and down the pipeline that would be awesome too. At the moment the amount if error handling needed to properly prevent against potential leaks and errors is quite verbose and easily overlooked from the slick stream
.pipeexamples (particularly in the case offs&httpstreams.Interestingly
http.ClientRequestalso has a similarabortmethod (https://github.com/iojs/io.js/blob/v1.x/lib/_http_client.js#L168), which callsnet.Socket#destroy(and emits an error).mcollina commentedon May 19, 2017
This is being worked on in nodejs/node#12925.
mcollina commentedon May 24, 2017
This has landed 🎉 !
jasnell commentedon May 24, 2017
landed and will be in 8.0.0
BobbyBakes commentedon Dec 11, 2018
@chrisdickinson Unrelated* what did you use to generate this? Looking for something similar.