はいさい!ちゅらデータぬオースティンやいびーん!
概要
AbortControllerを使って、Node.jsで実行したchild_process
を強制的に終了させる方法を紹介します。
背景
本記事は、前回投稿した、Node.jsでffmpegを使って動画を変換する記事を元に作成しています。
変換の作業に時間がかかりすぎて、サーバーの負荷が過ぎないように、ある一定の時間が経っても子プロセスが終了しなかったら、強制的に終了させる必要があります。
ここで、Abort Controllerを使ってShellに終了のシグナルを送ることができますので、解説していきたいと思います。
事前知識
前回の記事を読んでいただければわかりやすいかと思います!
コード
前回のコードのPOSTのところを修正します。
spawn
の引数の設定オブジェクトに、signal
を追加します。
...
app.post("/", upload.single("file"), async (req, res) => {
const controller = new AbortController();
try {
if (!req.file) throw Error("A file must be provided.");
const filename = req.file.filename;
const filenameWithoutExtension = filename.split(".")[0];
const result = await new Promise<string>((resolve, reject) => {
const childProcess = spawn(
`ffmpeg -i uploads/${filename} -c:v libx264 -preset veryfast -r 30 -vf "scale=720:-1" -c:a copy converted/${filenameWithoutExtension}.mp4`,
{
shell: true,
signal: controller.signal // ここで、AbortControllerのsignalを渡します!
}
);
childProcess.on("exit", (code, signal) => {
if (code !== 0) {
reject(`Child process failed with exit code: ${code}`);
}
resolve(`${filenameWithoutExtension}.mp4`);
});
});
res.statusCode = 200;
res.send(result);
} catch (error) {
res.statusCode = 500;
res.send(error);
}
});
...
setTimeoutでAbortController.abortを実行してみる
最初に、単純にsetTimeout
でcontroller.abort
を実行してみて、どうなるかみてみましょう。
先ほどのPromiseの後に、以下のコードを追加します。
...
setTimeout(() => controller.abort(), 10);
...
これだけでできます!
Promise.raceで終了させる方法
Promise.raceでsetTimeout
を包むと、エラーメッセージを設定することができます。
...
app.post("/", upload.single("file"), async (req, res) => {
const controller = new AbortController();
try {
if (!req.file) throw Error("A file must be provided.");
const filename = req.file.filename;
const filenameWithoutExtension = filename.split(".")[0];
const result = await Promise.race<string>([
new Promise((resolve, reject) => {
const childProcess = spawn(
`ffmpeg -i uploads/${filename} -c:v libx264 -preset veryfast -r 30 -vf "scale=720:-1" -c:a copy converted/${filenameWithoutExtension}.mp4`,
{
shell: true,
signal: controller.signal,
}
);
childProcess.on("exit", (code, signal) => {
if (code !== 0) {
reject(`Child process failed with exit code: ${code}`);
}
resolve(`${filenameWithoutExtension}.mp4`);
});
}),
new Promise((_, reject) => setTimeout(() => reject("Process exceeded time limit."), 1000)),
]);
res.statusCode = 200;
res.send(result);
} catch (error) {
res.statusCode = 500;
res.send(error);
}
});
...
こうすると、上記のように変換のプロセスがタイムアウトした時に、以下のようなエラーが出ます。
まとめ
ここまでAbortControllerを使って、child_processを外部から強制的に終了させる方法を紹介しましたが、いかがでしょうか?
本記事の他に、多数のAbortController関連の記事を書いていますが、読んでいただければ分かるように、AbortControllerは非常に便利です。
何かを止めたい!と考えたら、まずはAbortControllerでできないか考えるようにしましょう!