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
{{ message }}
Repository navigation
stream.pipeline destroys writable stream when error is occurred #26311
stream.pipeline is helpful to handle error and interoperable to Promise.
However, I found a behavior that is not suitable my usecase.
I am creating a web server with stream.pipeline.
If my readable stream emits an error like "file not found", I would like to send error response to my clients.
code example is as follows.
constfs=require('fs')consthttp=require('http')const{ pipeline }=require('stream')constserver=http.createServer((req,res)=>{constr=fs.createReadStream('./foo2.txt')pipeline(r,res,(err)=>{if(err){console.log(err)// No such filereturnres.end("error!!!");// write error response but this message is not shown.}})})server.listen(9000)
Is this an expected behaviour?
In my understandings, writable stream should be closed anyway, but in this case, we would like to close writable stream manually.
In this situation, we need to create custom writable stream?
I could send a PR to pass an option like { destroyOnError: false } to avoid destory automatically.
Anyway, I understood stream.pipeline should call destroy() automatically.
destroy function does not catch error object
I have tried to create custom Writable Stream to destroy manually.
constfs=require('fs')consthttp=require('http')const{ pipeline, Writable }=require('stream')classResponseWriterextendsWritable{constructor(options){super(options);this.res=options.res;}_write(chunk,encoding,callback){this.res.write(chunk);this.data+=chunk;callback();}_final(callback){this.data+=this.res.end();callback();}_destroy(err,callback){console.log("destroy",err);// does not show error.this.res.end("ERROR!! "+err);this.res.destroy(err);callback(err);}}constserver=http.createServer((req,res)=>{constr=fs.createReadStream('./foo2.txt')constw=newResponseWriter({
res,});pipeline(r,w,(err)=>{console.log(err)// No such file});})server.listen(9000)
But this solution does not work well. cause _destroy cannot catch the err object.
in node.js core, stream.pipeline does not pass err object to the destroy function.
This is expected behavior. .destroy(err) is supposed to be called with the error that caused the current stream to fail. If you want to always handle the "outer" logic, you should do that in the pipeline callback (or returned promise). Note that you don't want to call stream.destroy(err) because it will cause all streams to emit 'error' events, which can cause nasty side effects because of missing 'error' handlers. I don't think we can support that.
Note that from an http standpoint, adding this.res.end("ERROR!! " + err); after a byte is sent is not correct, as you'll be appending to data that is already flowing.
I think the source of these problems is not pipeline or pipe, but rather that the implementation of HttpResponse is not matching what you'd expect. Essentially if you are piping a stream into a response, you want to handle error conditions on a case-by-cases basis, making pipeline useless for you.
Essentially if you are piping a stream into a response, you want to handle error conditions on a case-by-cases basis, making pipeline useless for you.
I agree with that, but my opinion is different.
pipeline would be better to pass an option (pipeline(...streams, [option], callback)) to handle case-by-case basis like Stream.finished that has an option to handle case-by-case basis (finished(stream[, options], callback)).
I know HttpResponse usecase is a little bit different from other stream usecases (http.request, fs.read/write), but other TCP/UDP network server using stream like ftp would return error response to their clients, this usecase is not so rare case.
I still don't understand why adding a passthrough in the middle is going to fix this problem.
I think this is documented behavior to some extent: pipeline destroys all the streams when there is an error. Note that sending res.end()could create issues, because in the generic case we could be in the middle of a transfer - so we should not really send anything.
The correct fix for this issues is to wait that the file is correctly opened, and then use pipeline. If the file cannot be opened correctly, we should send that message to the user. That's what https://www.npmjs.com/package/send does.
it's not about this particular case. This case is just simple enough to show the concept. There is a lot of cases when you need to pipe some read stream to a transformation stream and to response(e.g., check my image resizing sample). And you need a way to handle possible ALL errors which can appear.
it was probably the reason why the original pump from @mafintosh was returning the last stream in the end.
constr=fs.createReadStream('./foo2.txt')awaitfinished(pipeline(r,transform,(err)=>{}).on('error',err=>{console.log(err)// No such fileres.end("error!!!");// write error response but this message is not shown. }).pipe(res))
There are some disagreements in this feature and no consensus, looks like it was the reason that #34133 was closed. Otherwise, I can re-create that PR with some adjustments.
There has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be closed 6 months after the last non-automated comment.
Activity
mcollina commentedon Feb 28, 2019
The whole reason of pipeline is to call destroy(). If you don’t want this behavior you can use plain old source.pipe().
yosuke-furukawa commentedon Mar 1, 2019
hm. I would like to use Promise and Stream seamlessly, stream.pipeline is seems to be helpful for my situation in API doc.
Anyway, I understood
stream.pipelineshould call destroy() automatically.destroy function does not catch error object
I have tried to create custom
Writable Streamto destroy manually.But this solution does not work well. cause
_destroycannot catch theerrobject.in node.js core,
stream.pipelinedoes not passerrobject to thedestroyfunction.Is this expected behavior? If not, I will send PR.
mcollina commentedon Mar 2, 2019
This is expected behavior.
.destroy(err)is supposed to be called with the error that caused the current stream to fail. If you want to always handle the "outer" logic, you should do that in the pipeline callback (or returned promise). Note that you don't want to callstream.destroy(err)because it will cause all streams to emit'error'events, which can cause nasty side effects because of missing'error'handlers. I don't think we can support that.Note that from an http standpoint, adding
this.res.end("ERROR!! " + err);after a byte is sent is not correct, as you'll be appending to data that is already flowing.I think the source of these problems is not pipeline or pipe, but rather that the implementation of
HttpResponseis not matching what you'd expect. Essentially if you are piping a stream into a response, you want to handle error conditions on a case-by-cases basis, makingpipelineuseless for you.yosuke-furukawa commentedon Mar 4, 2019
I agree with that, but my opinion is different.
pipelinewould be better to pass an option (pipeline(...streams, [option], callback)) to handle case-by-case basis likeStream.finishedthat has an option to handle case-by-case basis (finished(stream[, options], callback)).https://nodejs.org/api/stream.html#stream_stream_finished_stream_options_callback
I know
HttpResponseusecase is a little bit different from other stream usecases (http.request, fs.read/write), but other TCP/UDP network server usingstreamlikeftpwould return error response to their clients, this usecase is not so rare case.mcollina commentedon Mar 4, 2019
I'm willing to discuss adding new features/options to
pipelineas long as the default behavior stays consistent. Send a PR!kharandziuk commentedon Jul 3, 2019
@yosuke-furukawa
I believe you can solve your issue with the PassThrough stream. Look for the code below:
@mcollina what do you think? does it make any sense to add this trick(with PassThrough) into the documentation? This problem appears quite often
mcollina commentedon Jul 3, 2019
I don't understand how the above trick helps.
kharandziuk commentedon Jul 3, 2019
a changed code sample from @yosuke-furukawa
and we run the server and call:
the server output:
so, I achieve the expected behavior in a simple way without changing the interface. Do you see my point now?
mcollina commentedon Jul 4, 2019
I still don't understand why adding a passthrough in the middle is going to fix this problem.
I think this is documented behavior to some extent:
pipelinedestroys all the streams when there is an error. Note that sendingres.end()could create issues, because in the generic case we could be in the middle of a transfer - so we should not really send anything.The correct fix for this issues is to wait that the file is correctly opened, and then use
pipeline. If the file cannot be opened correctly, we should send that message to the user. That's what https://www.npmjs.com/package/send does.kharandziuk commentedon Jul 4, 2019
yep. but in a case of HttpResponse you don't want to destroy it.
I just changed the code from @yosuke-furukawa
it's not about this particular case. This case is just simple enough to show the concept. There is a lot of cases when you need to pipe some read stream to a transformation stream and to response(e.g., check my image resizing sample). And you need a way to handle possible ALL errors which can appear.
it was probably the reason why the original pump from @mafintosh was returning the last stream in the end.
rickyes commentedon Jun 28, 2020
I am in favour of adding options to
pipelineand will send PR if I can.mcollina commentedon Jun 28, 2020
We'll be happy to evaluate that change, send it over.
ronag commentedon Jul 1, 2020
What about:
5 remaining items
mcollina commentedon Dec 4, 2021
I’m sorry but it does not seem that #34133 is resolved. Why do you think so?
RafaelGSS commentedon Dec 4, 2021
There are some disagreements in this feature and no consensus, looks like it was the reason that #34133 was closed. Otherwise, I can re-create that PR with some adjustments.
mcollina commentedon Dec 4, 2021
It seems that PR was closed due to:
Having a fresh PR would help, I think we can get this landed. Wdyt @ronag?
ronag commentedon Dec 4, 2021
I agree.
rickyes commentedon Dec 5, 2021
Any good ideas, maybe I can reopen this PR.
github-actions commentedon Jun 3, 2022
There has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be closed 6 months after the last non-automated comment.
For more information on how the project manages feature requests, please consult the feature request management document.
RafaelGSS commentedon Jun 3, 2022
Closed by #41796
Lordfirespeed commentedon Aug 10, 2024
For others like me looking for a workaround for this:
Although the wrapping
Writableisdestroyed, the underlyingsinkstream remains open.fix(send): nodejs/node#26311