さかなソフトブログ

プログラミングやソフトウェア開発に関する情報

プログラミング

child_processの標準出力を正しく拾う為にはexitイベントではなくcloseイベントを使おう

更新日:

child_process.spawnはコマンド実行が比較的長い時間掛かる事もあると予想される場合に子プロセスを起動して並行実行が可能です。しかしながら後どれくらい時間が掛かるかをユーザーに伝えたい等の理由で途中で状況を把握したいことがあるかもしれません。今回、サンプルとしてコマンドは標準出力に進捗率が

progress: 1.51%
progress: 2.34%
...

と各行に表示されて状況が把握可能になっているケースを想定してみます。onProgressハンドラを渡して受け取った側でユーザーに進捗率をGUIで表示するイメージで以下を実装してみました(※バグあり):

const child_process = require("child_process")

function exec(path, onProgress) {
    return new Promise((resolve) => {
        let child_process = child_process.spawn(path)
        child_process.stdout.on("data", (data) => {
            let progress = data.toString().match(/^progress: (\d+\.?\d+)%/)
            onProgress(Number(progress[1]))
        })
        child_process.on("exit", (code) => {
            resolve({result: (code === 0 ? "success" : "failed")})
        })
    })
}

一見、子プロセスを実行して標準出力に書き込まれたら進捗率をonProgressで通知していて問題無さそうに思います。が、進捗率が100%まで表示されないことがあり、原因が分からず対策を一晩寝かしました...

exitイベントはstdioが閉じるのを待たない

dataイベントでprogressが終了間際にロストしているので終了のタイミングが早いとかそんなんだろうなと予想していましたが、ビンゴでした。Child Process | Node.js v9.3.0 Documentationをみると、ん?closeというイベントが・・・ということで仕様をみてみると、

'close'イベントは子プロセスのstdioストリームがクローズされた時に呼ばれます。同じstdioが複数のプロセスで共有される事もあるので'exit'イベントとは異なります。

と書いてあるようです。どうも、'exit'イベントは子プロセスがSIGTEMを受けたり子プロセス内でexit(x)を呼ばれて終了すれば即呼ばれるようです。そのために、resolve呼び出しが標準出力を受け取りきる前に呼ばれてPromiseでの処理が完了してしまい、後はどう挙動するか保証できない状態になったということのようです。

紛らわしい...。確かにプロセスは終わってるのかもしれないけどstdioデータを渡し切れてないタイミングをexitと呼んでいいのかはちょっと疑問に思いました。むしろ、'exit'→'will-exit'、'close'→'exit'ってイメージです。exitイベントはv0.1.90、closeイベントはv0.7.7で仕様定義されてるのでもしかしたら仕様後付けで悩んだのかもしれないですね。

というわけで、子プロセスの標準出力は全て受け取りたいので終了はcloseイベントを使いましょう:

const child_process = require("child_process")

function exec(path, onProgress) {
    return new Promise((resolve) => {
        let child_process = child_process.spawn(path)
        child_process.stdout.on("data", (data) => {
            let progress = data.toString().match(/^progress: (\d+\.?\d+)%/)
            onProgress(Number(progress[1]))
        })
        child_process.on("close", (code) => {
            resolve({result: (code === 0 ? "success" : "failed")})
        })
    })
}

めでたしめでたし。

正方形336

正方形336

-プログラミング
-

Copyright© さかなソフトブログ , 2019 All Rights Reserved.