並列テストシャットダウン時の無限ループを修正:ワーカーが途中で死んでもハングしない

rails/rails

Server#shutdownwait_for_active_workers ループ中にワーカーが死亡した場合でも、シャットダウンが正常に完了するよう修正されました。これにより、OOMキルやDRb接続断などによってワーカーが stop_worker を呼べなかった際の無限ループが解消されます。

背景

並列テストのシャットダウン処理には、#55794 で修正しきれなかったタイムウィンドウが残っていました。#55794Parallelization#shutdown の冒頭で Process.waitpid(pid, WNOHANG) を一度だけ実行し、シャットダウン開始前に すでに死亡しているワーカーを検出する仕組みを導入しました。しかし、ワーカーが Server#shutdown 内の wait_for_active_workers ループ 実行中に 死亡した場合はカバーされていませんでした。

Server#shutdownwhile active_workers? でワーカーが DRb 越しに stop_worker を呼ぶのを待ちます。ワーカーが parallelize_teardown フック内で例外を起こす、DRb 接続が切断される、またはクリーンアップ途中に OOM キルされるといったケースでは、stop_worker は永遠に呼ばれません。結果として、シャットダウンは無限ループに陥ります。この問題は #57052 として報告されており、Capybara/Cuprite を使ったシステムテストを含む CI 環境で断続的に発生し、ウォッチドッグが約 90 秒後にプロセスを強制終了するまでハングし続けていました。

根本原因は、ワーカーの死亡検知が「一度きりのスナップショット」としてしか行われていなかった点にあります。Server#shutdown のループ内では、ゾンビプロセスを回収する仕組みが存在しなかったため、初期スイープ後に死亡したワーカーは検出不能なまま残り続けていました。

技術的な変更

Server クラスに reap_dead_workers メソッドが追加され、wait_for_active_workers ループの各イテレーションで呼び出されるようになりました。これにより、シャットダウン前だけでなく、シャットダウン中の任意のタイミングで死亡したワーカーを検出できます。

変更前:

def wait_for_active_workers
  while active_workers?
    sleep 0.1
  end
end

変更後:

def wait_for_active_workers
  while active_workers?
    reap_dead_workers
    sleep 0.1
  end
end

def reap_dead_workers
  dead_pids = @worker_pids.values.select do |pid|
    Process.waitpid(pid, Process::WNOHANG)
  rescue Errno::ECHILD
    true
  end

  remove_dead_workers(dead_pids)
end

reap_dead_workers@worker_pids に登録されている全 PID に対して Process.waitpid(pid, Process::WNOHANG) を呼び出します。WNOHANG フラグにより、子プロセスが終了していない場合でもブロックせずに nil を返すため、ループのパフォーマンスに影響を与えません。プロセスが終了済みであれば PID を返し、そのエントリが dead_pids に収集されます。また、プロセスが既に回収済みの場合は Errno::ECHILD が発生しますが、これも true として扱い dead_pids に含めることで、取りこぼしを防いでいます。収集した dead_pids は既存の remove_dead_workers メソッドに渡され、アクティブワーカーリストから除去されます。

リグレッションテストも追加されました。テストは、ワーカーをフォークして登録させた後、親プロセスが Server#shutdown に入った 後で そのワーカーを SIGKILL で強制終了するシナリオを再現します。Timeout.timeout(3, Minitest::Assertion, "Expected shutdown to not hang") でラップすることで、修正なしではテストがハングすることを明示的に検証できます。

設計判断

wait_for_active_workers各ポーリングサイクル でゾンビ回収を実行するアプローチが採用されました。

検出タイミングを Parallelization#shutdown の冒頭に限定する前回の方式との違いは、「スナップショット型」から「継続的監視型」への転換にあります。0.1 秒のスリープごとに全ワーカーの生死を確認するため、シャットダウン中のどの瞬間に死亡したワーカーも、最大 0.1 秒以内に検出されます。WNOHANG を使用することでブロッキングを回避し、既存のポーリング構造に最小限の変更で組み込める設計となっています。

Errno::ECHILDtrue として扱うことも重要な判断です。これは、別のコードパスで既に waitpid が完了しているプロセスを「死亡済み」として正しく分類するためのもので、二重回収を安全に扱います。

まとめ

この修正は、「一度きりの死亡検知」から「ループ内での継続的な死亡検知」へと移行することで、#55794 が残していたタイムウィンドウを完全に塞ぎます。OOMキルや DRb 切断など、ワーカーが stop_worker を呼べないあらゆる異常終了シナリオに対して、並列テストのシャットダウンが確実に完了するようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
e2ffb51b

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「総論→各論→結論」の構成が明確です。リード文、背景、技術的な変更、設計判断、まとめの各要素が適切に配置されており、理想的な記事構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト、PR/Issue番号のリンク記法など、すべてのカスタムMarkdown構文が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門用語を適切に用い、前提知識を持つエンジニアを対象とした記述ができています。冗長な説明がなく、技術レベルが対象読者に完全に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

セクション内が総論→各論で構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られています。非常に読みやすい構造です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内のコードブロックは、提供されたDiffの内容を正確に引用しています。ファイル名も一致しており、改変や省略はありません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PR情報に含まれる技術用語(WNOHANG, DRb, OOM-killedなど)や、一般的な技術用語(ゾンビプロセス, ポーリングサイクルなど)が正確かつ適切な文脈で使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

コード変更の目的、動作原理、そして問題の根本原因に関する説明は、技術的に正確で論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(以前の修正の問題点、CIでのハング時間、Issue番号など)は、PRのDescriptionやDiffの内容によって裏付けられています。ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号、Issue番号、CIでのハング時間(約90秒)など、記事に含まれるすべての数値・固有名詞は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRのタイトルと主題を正確に反映しており、内容の齟齬はありません。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に基づかない外部知識(バージョンサポート状況、リリース日程など)の記載はなく、事実に基づいた記述が徹底されています。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

シャットダウン「前」と「中」という時間的な前後関係がPR情報と一致しており、正確に表現されています。