-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
[stream] uncatchable error thrown during piping if source and dest don't have same objectMode setting #54945
Comments
For reference, a smaller reproduction is: import { Readable, Transform } from 'node:stream';
Readable.from([{}]).pipe(new Transform({
transform(chunk, _, cb) {
this.push(chunk);
}
})); AFAICT this is working as intended. Per the docs, if object mode is CC @nodejs/streams |
@RedYetiDev Thanks for taking a look!
The part that I feel is not working as intended is that there is no way to catch the error that is produced when the pipe attempts to start flowing. It should be possible (and in my opinion, would be preferrable) to throw synchronously in the Individual streams cannot change their object mode once constructed. From the documentation on object mode:
So, it should be sound to throw in The reason I provided a longer repro step was to demonstrate different places in the control flow where an error could be caught. |
I agree we are missing a try/catch in pipe. |
Thanks for taking a look! I assume you mean "we're missing a check and a Anyway, if folks feel this is indeed a bug, and that the right fix is to add a check and throw in |
function ondata(chunk) {
debug('ondata');
try {
const ret = dest.write(chunk);
debug('dest.write', ret);
if (ret === false) {
pause();
}
} catch (err) {
dest.destroy(err);
}
} |
@ronag got it. Thanks. Should there also be a check and throw at I'll add some tests and put up a PR. |
I think it will add a noticeable overhead if that is done for every data chunk. Also, I think this is an unrecoverable programmer error so a crash is expected. Can't we just add a check for the source and destination streams in I mean something like this: diff --git a/lib/internal/streams/readable.js b/lib/internal/streams/readable.js
index 17f8e53ad5..75dc350b59 100644
--- a/lib/internal/streams/readable.js
+++ b/lib/internal/streams/readable.js
@@ -910,6 +910,17 @@ Readable.prototype.pipe = function(dest, pipeOpts) {
const src = this;
const state = this._readableState;
+ if (
+ (state[kState] & kObjectMode &&
+ !dest._writableState[kState] & kObjectMode) ||
+ ((state[kState] & kObjectMode === 0) &&
+ dest._writableState[kState] & kObjectMode)
+ ) {
+ throw new Error(
+ 'The piped streams do not have the same objectMode stetting'
+ );
+ }
+
if (state.pipes.length === 1) {
if ((state[kState] & kMultiAwaitDrain) === 0) {
state[kState] |= kMultiAwaitDrain;
|
Just note that you can pipe non object mode into object mode but not the other way around. |
@lpinca @ronag in my incredibly limited (only one machine / architecture) and not statistically valid (only run a few times, manually) benchmarking, I don't see a performance difference due to the addition of the Assuming there isn't performance overhead, is there a preferred approach (try/catch in onData vs check in .pipe)? Happy to put up a PR for either. Also, are these instructions still current for running benchmarks on CI? (I see the repo they are in is archived.) Also, will I have permissions to trigger such a run on CI? Or does a maintainer need to do that? manual runs of benchmarkNote: I built node with local build, unmodified
|
If there is no performance overhead I would argue that both suggestions should be applied. |
With the check in place, under what circumstances might
See https://github.com/nodejs/node/blob/main/doc/contributing/writing-and-running-benchmarks.md. A collaborator needs to start the benchmark CI. |
"Streamlike" objects. |
Version
22.8.0
Platform
Subsystem
stream
What steps will reproduce the bug?
Run the following code:
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
Probably
Readable.pipe()
should compare theobjectMode
state of the source and the write side of the destination, and if they don't match, it should synchronously throw.So, in the code above, I would expect to catch an error on the line that reads
console.error("caught error when calling pipe", e);
Alternatively, the writable (or transform) stream could emit an error. In that case, in the code above, I would expect to see an error logged by the
passThrough.on("error")
callback.What do you see instead?
There is an uncaught exception. It is caught by the callback to
process.setUncaughtExceptionCaptureCallback
. That exception is:I don't believe there is any way to catch this exception in the code that has the streams in context. (But if there is, then that'd be swell! And there's no issue here! And in that case, apologies for not understanding the right part of the streams API to handle these errors correctly.)
Additional information
Broadly, the stream module is great ❤️ ! I love building stuff with it. Thanks for your hard work.
The text was updated successfully, but these errors were encountered: