Node.js:在对其标准输入流进行大量写入操作期间突然关闭子进程时,会引发无法捕获的错误
Node.js: An uncatchable error is thrown when the child process is abruptly closed during a large write operation to it's stdin stream
注意:我已经找到了解决这个问题的方法,张贴在这里以供后人使用。查看所选答案。
以下(简化的)代码抛出无法捕获的“写入 EPIPE”(在某些情况下为“写入 EOF”)错误:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
const p = exec("gibberishThatWillFailImmediately");
p.stdin.write(veryLargeString);
我对这个问题的失败尝试:
- 写入前检查
stdin.destroyed
标志
- 写入前检查
stdin.writeableEnded
标志
- 分块输入并在每个块之前检查
stdin.writeableEnded
。这会导致不确定的行为。
- 用 try-catch
包裹 stdin.write(data)
行
- 调用
stdin.end(data)
而不是 stdin.write(data)
- 传递
stdin.write()
一个回调,它应该得到发生的任何错误。回调得到了错误,但没有阻止它被抛出。
向 stdin
流注册一个 'error' 处理程序似乎可以防止错误被抛出。像这样:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
const p = exec("gibberishThatWillFailImmediately");
p.stdin.on('error', (error) => console.log("error caught: ", error));
p.stdin.write(veryLargeString);
这是一个示例,其中 returns 一个包含错误的承诺,如果没有发生错误则为 null:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
function safelyWriteDataToStdin(stdin, data) {
// Register an awaitable callback that will capture any error occuring during the write operation
const promise = new Promise((resolve, _reject) => {
// Using once() and not on() to remove the listener after the first catch.
stdin.once("error", (error) => resolve(error));
// stdin.end(data, callback) can probably be used here, but I keep the `write()` just in case `end()`'s callback is called before the 'error' event, since the docs are not clear about that. (docs say: "The callback is invoked before 'finish' or on error." for node version 15.0.0. Is "on error" how node people say "after error"? idk.)
stdin.write(
data,
(error) => {
if (!error) resolve(null); // The condition is necessary because when an error occurs, the callback is called before the 'error' event handler
} // Signal the promise to complete when the write operation is complete with no errors. I don't simply use this `error` parameter because the exception will still be thrown if I don't listen to the 'error' event, and the docs say: "If an error occurs, the callback may or may not be called with the error as its first argument. To reliably detect write errors, add a listener for the 'error' event.". Also, I tested it myself and got two different errors in this callback and in the 'error' event handler.
);
});
return promise;
}
const p = exec("gibberishThatWillFailImmediately");
safelyWriteDataToStdin(p.stdin, veryLargeString).then((error)=>console.log("The error is:", error ));
注意:我已经找到了解决这个问题的方法,张贴在这里以供后人使用。查看所选答案。
以下(简化的)代码抛出无法捕获的“写入 EPIPE”(在某些情况下为“写入 EOF”)错误:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
const p = exec("gibberishThatWillFailImmediately");
p.stdin.write(veryLargeString);
我对这个问题的失败尝试:
- 写入前检查
stdin.destroyed
标志 - 写入前检查
stdin.writeableEnded
标志 - 分块输入并在每个块之前检查
stdin.writeableEnded
。这会导致不确定的行为。 - 用 try-catch 包裹
- 调用
stdin.end(data)
而不是stdin.write(data)
- 传递
stdin.write()
一个回调,它应该得到发生的任何错误。回调得到了错误,但没有阻止它被抛出。
stdin.write(data)
行
向 stdin
流注册一个 'error' 处理程序似乎可以防止错误被抛出。像这样:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
const p = exec("gibberishThatWillFailImmediately");
p.stdin.on('error', (error) => console.log("error caught: ", error));
p.stdin.write(veryLargeString);
这是一个示例,其中 returns 一个包含错误的承诺,如果没有发生错误则为 null:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
function safelyWriteDataToStdin(stdin, data) {
// Register an awaitable callback that will capture any error occuring during the write operation
const promise = new Promise((resolve, _reject) => {
// Using once() and not on() to remove the listener after the first catch.
stdin.once("error", (error) => resolve(error));
// stdin.end(data, callback) can probably be used here, but I keep the `write()` just in case `end()`'s callback is called before the 'error' event, since the docs are not clear about that. (docs say: "The callback is invoked before 'finish' or on error." for node version 15.0.0. Is "on error" how node people say "after error"? idk.)
stdin.write(
data,
(error) => {
if (!error) resolve(null); // The condition is necessary because when an error occurs, the callback is called before the 'error' event handler
} // Signal the promise to complete when the write operation is complete with no errors. I don't simply use this `error` parameter because the exception will still be thrown if I don't listen to the 'error' event, and the docs say: "If an error occurs, the callback may or may not be called with the error as its first argument. To reliably detect write errors, add a listener for the 'error' event.". Also, I tested it myself and got two different errors in this callback and in the 'error' event handler.
);
});
return promise;
}
const p = exec("gibberishThatWillFailImmediately");
safelyWriteDataToStdin(p.stdin, veryLargeString).then((error)=>console.log("The error is:", error ));