2
0

More than 1 year has passed since last update.

Node.jsでAbortControllerを使って、child_processを強制的に終了させる方法

Posted at

はいさい!ちゅらデータぬオースティンやいびーん!

概要

AbortControllerを使って、Node.jsで実行したchild_processを強制的に終了させる方法を紹介します。

背景

本記事は、前回投稿した、Node.jsでffmpegを使って動画を変換する記事を元に作成しています。

変換の作業に時間がかかりすぎて、サーバーの負荷が過ぎないように、ある一定の時間が経っても子プロセスが終了しなかったら、強制的に終了させる必要があります。

ここで、Abort Controllerを使ってShellに終了のシグナルを送ることができますので、解説していきたいと思います。

事前知識

前回の記事を読んでいただければわかりやすいかと思います!

コード

前回のコードのPOSTのところを修正します。

spawnの引数の設定オブジェクトに、signalを追加します。

src/index.ts
...

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を実行してみる

最初に、単純にsetTimeoutcontroller.abortを実行してみて、どうなるかみてみましょう。

先ほどのPromiseの後に、以下のコードを追加します。

...

    setTimeout(() => controller.abort(), 10);

...

すると、変換が1で失敗します。
スクリーンショット 2022-07-13 13.26.36.png

これだけでできます!

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);
  }
});

...

こうすると、上記のように変換のプロセスがタイムアウトした時に、以下のようなエラーが出ます。
スクリーンショット 2022-07-13 13.35.47.png

まとめ

ここまでAbortControllerを使って、child_processを外部から強制的に終了させる方法を紹介しましたが、いかがでしょうか?

本記事の他に、多数のAbortController関連の記事を書いていますが、読んでいただければ分かるように、AbortControllerは非常に便利です。

何かを止めたい!と考えたら、まずはAbortControllerでできないか考えるようにしましょう!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0