Windows子プロセス安全管理の知識マップ(知識グラフ)
Job Object
誰の子かではなくどのJobに属するかでプロセス群を束ね、制限やまとめての終了を行えるWindowsの仕組み。
ジョブオブジェクト
標準入出力のデッドロック
Windowsのpipeが有限バッファであるため、片方の標準出力しか読まないと書き手が止まり、読み手も待ち続けて共倒れになる状態。
stdout/stderrの詰まり
監視プロセス(watchdog)
本体プロセスの起動・終了・生存を外から見張り、exit codeや再起動回数を記録する別プロセス。
親サービス
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
最後のjob handleが閉じられたときに、Jobに関連付けられた全プロセスを終了させるJob Objectの制限フラグ。
親を失った子プロセスの残存
親プロセスが異常終了したあとも回収されずに残り続ける子・孫プロセス。
取り残された子プロセス
親クラッシュ時の自動回収
親プロセスが異常終了した場合でも、その子プロセス木を自動的に片づける仕組み。
Process.Kill(entireProcessTree: true)
.NET Core 3.0以降で使える、指定プロセスとその子孫プロセスをまとめて強制終了する.NETのAPI。
.NETのKill(entireProcessTree: true)
PROC_THREAD_ATTRIBUTE_JOB_LIST
STARTUPINFOEXと組み合わせ、プロセスの起動時点からJobへ所属させるための属性。Windows 10/Windows Server 2016以降で使える。
プロセスグループ(コンソール)
console signalをどのプロセス群へ送るかを決める仕組み。親クラッシュ時のcleanupやGUI子プロセスの終了までは担わない。
コンソール制御イベント
console childへ協調終了を依頼するために送る、CTRL_BREAK_EVENTなどの制御イベント。
graceful shutdown
強制終了ではなく、止める合図を出して進行中の処理をなるべく整えてから終了させる終了方式。
正常終了処理
heartbeatによる生存確認
プロセスが生きているのに進んでいない(ハング)状態を検出するために、子プロセスから定期的に出す信号をアプリ層で確認する仕組み。
再起動予算(restart budget)
一定時間内に何回まで再起動してよいかの上限で、backoffや連続失敗時の停止・通知と組み合わせて持つ運用ポリシー。
crash loop
即再起動してもまた即落ちることを繰り返し、ログだけが大量に出る状態。
再起動の繰り返し
stdout/stderrの並列drain
標準出力と標準エラーを片方だけ読み切ってからもう片方を読むのではなく、それぞれ独立したハンドラで並行して読み進める実装。
並列読み取り
I/O完了ポート(IOCP)
多数の非同期I/Oの完了通知を1つのFIFOキューに集約しつつ、実行可能なスレッド数をコンカレンシー値以下に保つスケジューリング機構を兼ねる、Windowsカーネルの仕組み。
プロセス木(process tree)
親から起動した子、その子が起動した孫までを含めた一族のプロセス群。
JOB_OBJECT_LIMIT_BREAKAWAY_OK
子プロセスがJobから離脱(breakaway)することを許可するJob Objectの制限フラグ。意図なく付けると、cleanupできるつもりだった木から一部が抜ける原因になる。
Job ObjectはJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEを付けることで、最後のjob handleが閉じられたときに全プロセスを終了させるよう構成できる
KILL_ON_JOB_CLOSEにより、親の異常終了まで含めてcleanupを親の寿命へ寄せられるため、親を失った子プロセスの残存を防げる。ただしjob handleを別プロセスへ複製・継承させて最終所有者が曖昧になっている場合や、BREAKAWAY系のオプションで木の一部が離脱している場合は、この効果は成立しない
親の生死と子プロセス木の寿命を結びつけたいなら、基準点はJob Objectであると本記事は結論づけている
Kill(entireProcessTree: true)は明示的に止めるAPIとして便利だが、親クラッシュ時の自動回収やgraceful shutdownまで含む設計の代替にはならない
PROC_THREAD_ATTRIBUTE_JOB_LISTで起動時点からJob所属にするには、あらかじめ作成したJob Objectのハンドルを渡す必要がある
process groupはCTRL_BREAK_EVENTなどのconsole制御イベントをどこへ送るかを決める仕組みである
console signalを受け取れるのはconsoleを共有しているプロセスだけで、特定のgroupへ限定して送るにはCREATE_NEW_PROCESS_GROUPで作成したprocess groupが前提になる
協調終了を依頼し、短いtimeoutで待ち、最後にJobごと強制終了する3段階の順番にすると、正常終了は保ちつつハング時も回収できる
console childへの協調終了は、CREATE_NEW_PROCESS_GROUPで起動しCTRL_BREAK_EVENTを送るという手段で行う
watchdogを監視対象と同じJobに入れると、監視対象を片づける際に再起動役まで一緒に消えるため、Jobの外に置くべきである
プロセスが生きているかだけでは判定できないハングまで見たいwatchdogは、heartbeatなどアプリ層の生存確認を組み合わせる
watchdogはworker世代ごとにJobを作り直す再起動運用を、restart budgetの範囲内で自動化する
backoffや一定時間内の再起動回数上限、連続失敗時の停止・通知といったrestart budgetを持つことで、即再起動と即落ちを繰り返すcrash loopを避けられる
stdoutとstderrを独立したハンドラで並行して読み進めれば、片方が満杯になって親子が読み待ち・書き待ちで固まる状態は成立しない
Job ObjectはI/O完了ポートを関連付けてプロセスの起動・終了通知を受け取れるが、その通知だけでcorrectnessを組むべきではなく監視・集計向けと位置付けられる
Jobの境界がプロセス木の境界であり、誰の子かではなくどのJobに属するかでプロセス木を束ねる
JOB_OBJECT_LIMIT_BREAKAWAY_OKやSILENT_BREAKAWAY_OKを意図なく付けると、cleanupできるつもりだった木から一部のプロセスが抜けて残り得る