-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
[x] stream: eof & pipeline compat #29724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,18 @@ function isRequest(stream) { | |
| return stream.setHeader && typeof stream.abort === 'function'; | ||
| } | ||
|
|
||
| function isReadable(stream) { | ||
| return typeof stream.readable === 'boolean' || | ||
| typeof stream.readableEnded === 'boolean' || | ||
| !!stream._readableState; | ||
| } | ||
|
|
||
| function isWritable(stream) { | ||
| return typeof stream.writable === 'boolean' || | ||
| typeof stream.writableEnded === 'boolean' || | ||
| !!stream._writableState; | ||
| } | ||
|
|
||
| function eos(stream, opts, callback) { | ||
| if (arguments.length === 2) { | ||
| callback = opts; | ||
|
|
@@ -28,43 +40,72 @@ function eos(stream, opts, callback) { | |
|
|
||
| callback = once(callback); | ||
|
|
||
| let readable = opts.readable || (opts.readable !== false && stream.readable); | ||
| let writable = opts.writable || (opts.writable !== false && stream.writable); | ||
| const onerror = (err) => { | ||
| callback.call(stream, err); | ||
| }; | ||
|
|
||
| const w = stream._writableState; | ||
| const r = stream._readableState; | ||
|
|
||
| let writableFinished = stream.writableFinished || (w && w.finished); | ||
| let readableEnded = stream.readableEnded || (r && r.endEmitted); | ||
|
|
||
| const errorEmitted = (w && w.errorEmitted) || (r && r.errorEmitted); | ||
| const closed = (w && w.closed) || (r && r.closed); | ||
|
|
||
| if (writableFinished || readableEnded || errorEmitted || closed) { | ||
| // TODO(ronag): rethrow if errorEmitted? | ||
| // TODO(ronag): premature close if closed but not | ||
| // errored, finished or ended? | ||
|
|
||
| // Swallow any error past this point. | ||
| if (opts.error !== false) stream.on('error', onerror); | ||
|
|
||
| process.nextTick(callback.bind(stream)); | ||
|
|
||
| return () => { | ||
| stream.removeListener('error', onerror); | ||
| }; | ||
| } | ||
|
|
||
| const readable = opts.readable || | ||
| (opts.readable !== false && isReadable(stream)); | ||
| const writable = opts.writable || | ||
| (opts.writable !== false && isWritable(stream)); | ||
|
|
||
| const onlegacyfinish = () => { | ||
| if (!stream.writable) onfinish(); | ||
| }; | ||
|
|
||
| let writableEnded = stream._writableState && stream._writableState.finished; | ||
| const onfinish = () => { | ||
| writable = false; | ||
| writableEnded = true; | ||
| if (!readable) callback.call(stream); | ||
| writableFinished = true; | ||
| if (!readable || readableEnded) callback.call(stream); | ||
| }; | ||
|
|
||
| let readableEnded = stream.readableEnded || | ||
| (stream._readableState && stream._readableState.endEmitted); | ||
| const onend = () => { | ||
| readable = false; | ||
| readableEnded = true; | ||
| if (!writable) callback.call(stream); | ||
| }; | ||
|
|
||
| const onerror = (err) => { | ||
| callback.call(stream, err); | ||
| if (!writable || writableFinished) callback.call(stream); | ||
| }; | ||
|
|
||
| const onclose = () => { | ||
| let err; | ||
| if (readable && !readableEnded) { | ||
| if (!stream._readableState || !stream._readableState.ended) | ||
| err = new ERR_STREAM_PREMATURE_CLOSE(); | ||
| return callback.call(stream, err); | ||
| } | ||
| if (writable && !writableEnded) { | ||
| if (!stream._writableState || !stream._writableState.ended) | ||
| err = new ERR_STREAM_PREMATURE_CLOSE(); | ||
| return callback.call(stream, err); | ||
| if (readable) { | ||
| const ended = (stream._readableState && | ||
| stream._readableState.endEmitted) || stream.readableEnded; | ||
| if (!ended && !readableEnded) { | ||
| callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); | ||
| } else if (!readableEnded) { | ||
| // Compat. Stream has ended but haven't emitted 'end'. | ||
| callback.call(stream); | ||
| } | ||
| } else if (writable) { | ||
| const finished = (stream._writableState && | ||
| stream._writableState.finished) || stream.writableFinished; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ws.finished in not the same as ws.ended is it? the old compat was to ignore premature close if the stream had been ended (ie ws.end() was called)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mafintosh: correct, the workaround for that is that pipeline ignores that premature error in a safe way. See the changes in pipeline.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That way we get "correct" behaviour from eos while not breaking and still having "correct" behaviour in pipeline.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, okay, let me try to grok that a bit. Thinking out loud it would be really nice to check if a stream is autoDestroy'ing (which I think at some point should be the default) and then simply wait for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then we could keep the compat mode around for "old" streams but autoDestroying stream would run down the simple path.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I follow this. Did you want something changed in this PR and if so can you describe it further, or was this just thinking out loud for future changes?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mafintosh Any further "groking"?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point was that is a stream has autoDestroy set to true, which is probably gonna be the default behaviour at some some, we just need to check if close has been emitted so all of this becomes much easier. I do think that's worth adding relatively soon. |
||
| if (!finished && !writableFinished) { | ||
| callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); | ||
| } else if (!writableFinished) { | ||
| // Compat. Stream has finished but haven't emitted 'finish'. | ||
| callback.call(stream); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,29 @@ function destroyer(stream, reading, writing, callback) { | |
|
|
||
| if (eos === undefined) eos = require('internal/streams/end-of-stream'); | ||
| eos(stream, { readable: reading, writable: writing }, (err) => { | ||
| if ( | ||
| err && | ||
| err.code === 'ERR_STREAM_PREMATURE_CLOSE' && | ||
| reading && | ||
| (stream._readableState && stream._readableState.ended) | ||
| ) { | ||
| // Some readable streams will emit 'close' before 'end'. However, since | ||
| // this is on the readable side 'end' should still be emitted if the | ||
| // stream has been ended and no error emitted. This should be allowed in | ||
| // favor of backwards compatibility. Since the stream is piped to a | ||
| // destination this should not result in any observable difference. | ||
| // We don't need to check if this is a writable premature close since | ||
| // eos will only fail with premature close on the reading side for | ||
| // duplex streams. | ||
| stream | ||
| .on('end', () => { | ||
| closed = true; | ||
| callback(); | ||
| }) | ||
| .on('error', callback); | ||
| return; | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer if this logic lived in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? I can't think of a way to do it in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mafintosh: See here
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I completely follow this still |
||
|
|
||
| if (err) return callback(err); | ||
| closed = true; | ||
| callback(); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.