Zombie Task・Orphan Process:タスク終了後にプロセスが残る理由

約14分developer-tools
#ゾンビタスク#孤児プロセス#エージェントスウォーム#バイナリ#依存関係#AIコーディングツール#用語集
※ 編集方針:以下の業務状況や人物は、用語の理解を助けるための架空のシナリオです。実際の著者の経験や特定の企業での出来さを再現したものではありません。
大容量の画像変換スクリプトを長時間実行した後、タスク終了の表示が出たにもかかわらずプロセスが残っている状況を想定してみましょう。
戻ってきてノートPCを閉じようとしたところ、ファンの音が依然としてけたたましく鳴っていました。タスクはすでに終了したと表示されているにもかかわらず、です。

タスクが終了したはずなのにノートPC의ファンが回り続けるとき、ゾンビタスクの正体

bash
1$ ps aux | grep node
2user 4821 98.2 node convert-images.js
3user 4822 0.0 node convert-images.js <defunct>
検索してみると、出力の後ろの方にある <defunct> の表示がポイントでした。タスク自体は終了しているものの、その結果を最終的にクリーンアップする親プロセスが、まだその事実を確認していない状態で残っていました。処理は終わっているのに、後片付けだけがされないままリソースを占有している状態でした。
なぜあえて「ゾンビ」という名前がついているのかも、そのときようやく理解できました。死んでいるのに完全に消え去ることはなく、居座り続けている様子は、語源を知らないときにはただの不気味な隠語のように聞こえていました。
タスクは終了したものの、親プロセスが結果を回収していないため、プロセスだけが残っている状態
タスクは終了したものの、親プロセスが結果을回収していないため、プロセスだけが残っている状態
スリープさせてから数日後、再び ps aux を実行してみたところ、今度は少し違うものが見つかりました。

ターミナルのウィンドウを閉じたのにプロセスが生きているとき、孤児プロセスが残る理由

bash
1$ ps aux | grep node
2user 5310 1 node long-crawler.js # PPID가 1
PPID(親プロセスID)が「1」であるのが奇妙に思え、ログを読み直しました。1番は本来、システムが最初に起動するプロセスのIDです。数日前にターミナルウィンドウをそのまま閉じてしまったクローラーのタスクが、元の親であったターミナルが消滅したため、システムにそのまま引き取られて動き続けていたのです。
親が先に消えても子プロセスは自動的に終了せず、システムが代わりに引き取って実行し続けます。親を失ったからといって一緒に終了するのではなく、静かに別の保護者のもとで働き続けていたわけです。
問題は、これが目立たないという点でした。ターミナルを閉じたのだから当然一緒に終了したと思い込んでいたのですが、クローラーはその後も3日間回り続け、ディスクを圧迫していました。
ターミナルが消滅した後も、子プロセスだけが時間の経過とともに実行され続ける流れ
ターミナルが消滅した後も、子プロセスだけが時間の経過とともに実行され続ける流れ
ps auxコマンドの結果ウィンドウを覗き込み、PPIDの数値を指で指し示している場面
一人で作業していると、こうした疑問を聞ける相手がなかなかいなかったのですが、ちょうど以前の会社の同僚から「最近どうしてる?」と連絡がありました。

ターミナルにプロセスが大量に表示されるとき、エージェントスウォーム

画面をキャプチャして見せると、同僚は笑いながら言いました。「それ、私も最近よく見かけるよ。大きなリファクタリングをするときに、あえて複数立ち上げているんだ」
同僚の説明によると、何百ものファイルに分かれたタスクを1つのエージェントが順番に処理する代わりに、複数を同時に立ち上げて分担させる方法があるとのことでした。蜂の群れ(スウォーム)のように複数のエージェントが同時にそれぞれの持ち分を処理し、終わると解散する仕組みなので、プロセスが一度に複数見えてもおかしくはないそうです。
一人で作業していたときは、プロセスが複数立ち上がると何かが間違っているのかと思っていましたが、あえてそのように実行する手法が別にあることをその日初めて知りました。
1つのタスクを複数のエージェントが同時に領域ごとに分担して処理する構造
1つのタスクを複数のエージェントが同時に領域ごとに分担して処理する構造
一人で作業していると初めて直面する概念が多いため、疑問が生じるとまず公式ドキュメントを調べる癖がつきました。クローラーがダウンロードしてきたファイルを1つ開いてみようとしたとき、また行き詰まりました。

拡張子のないファイルが実行されるとき、バイナリの役割

クローラーが画像変換用に取得したファイルをテキストエディタで開いてみると、文字化けしたような読めない文字ばかりが表示されました。ドキュメントを調べてみると、理由は明確でした。
人間が読む文字で保存されたファイルではなく、コンピュータが直接実行または処理できる「0」と「1」だけで構成されたファイルでした。メモ帳で料理のレシピは読めても、コンパイルされた機械語そのものは人間の目では読めないのと同じです。実行ファイルや画像処理ツール、圧縮されたライブラリのほとんどがこのような形式でダウンロードされていました。
人間が読める度合いを基準にすると、バイナリは反対側の極限にある形態
人間が読める度合いを基準にすると、バイナリは反対側の極限にある形態
ファイルの正体は判明したものの、いざこのツールを実行しようとすると、また別のエラーが発生しました。

ツールを1つ実行しただけで他のものが芋づる式に要求されるとき、依存関係が連鎖する構造

text
1Error: sharp module version mismatch
2Expected: 0.33.x, Found: 0.30.x
同じツールを使っている他の人の過去のコミットを調べていたところ、package.jsonに記載されているバージョンが、担当者のコンピュータにインストールされているバージョンと異なっていることに気づきました。
このツールを正しく動作させるには、その内部で動作する他の複数のツールが、特定のバージョンで一緒にインストールされている必要がありました。料理を作る際に、レシピだけでなく指定されたブランドの調味料まで揃えなければならないのと似ています。バージョンを合わせて再インストールしたことで、ようやく変換作業が正常に動作しました。
自分のスクリプトが使用するツールが、その内部でさらに別のバージョンを要求する入れ子状の構造
自分のスクリプトが使用するツールが、その内部でさらに別のバージョンを要求する入れ子状の構造
複数の小さな箱が1つの大きな箱の中にぴったりと収まっていく様子を手で確認している場面
残っていたプロセスをすべてクリーンアップして、ようやくノートPCのファンが静かになりました。その日に遭遇した言葉はそれぞれ異なっていましたが、問いかけていることは1つでした。「自分が終わらせたつもりのタスクは、本当に終了しているのか」ということです。

よくある質問

ゾンビタスクや孤児プロセスをそのままにしておくと危険ですか?

1つや2つ程度なら大きな問題にはなりませんが、蓄積されるとメモリやCPUを消費し続けます。<code>kill</code> コマンドでクリーンアップするか、タスクが終了した際にターミナルをすぐに閉じず、正常終了のメッセージを確認する習慣をつけるのが安全です。

エージェントスウォームは、常に複数を立ち上げる方が有利ですか?

ファイル数が多く、互いに独立したタスクである場合は有利ですが、タスク間で結果をやり取りする必要がある場合は、かえって調整コストが増加します。むやみに複数立ち上げるのではなく、タスクの性質に合わせて判断するのが賢明です。

依存関係のバージョンが合わないと、必ずエラーになりますか?

常にそうとは限りません。わずかなバージョンの違いが無視されてスルーされることもあるため、表面上は正常に動作しているように見えて、特定の機能だけがひっそりと異なる挙動を示すケースの方が厄介です。

AIコーディングツール用語集シリーズの第3話です。次回予告 · 第4話:指導係なしで初デプロイを控えたインターンが直面する見慣れない言葉たち(キルスイッチ・バックオフ・リポジトリ・コンパイル(ビルド)・ランタイム)

参考・根拠

関連記事