テストのポーリングループを `wait_for` ヘルパーに統一

rails/rails

ActiveRecordのテストに残っていたアドホックなポーリングループを wait_for ヘルパーに置き換え、タイムアウト時に明示的なエラーを発生させるようにしました。これにより、ハング時のデバッグが困難だった問題とサイレントなタイムアウトが解消されます。

背景

#57309connection_pool_test.rb の無限ループを修正したことを受け、同様の問題を抱える残りのテストを統一的に修正するフォローアップです。

#57309 では、test_checkout_fairness_by_group がCIで無限にハングする事象が報告されていました。スレッドが正常・エラーのいずれのパスにも到達しない場合、loop { sleep 0.1; break if ... } が永久に回り続け、ビルドエージェントのジョブタイムアウトで強制終了されるまで CI が詰まる状態でした。

今回対象となった3箇所のポーリングループには、それぞれ異なる問題がありました。reaper_test.rb の2箇所は until ... { Thread.pass } という上限なしのビジーウェイトで、条件が満たされなければ永久にハングします。asynchronous_queries_test.rb の1箇所は 500.times { sleep 0.02 } という10秒上限付きのループでしたが、タイムアウト後も例外を発生させずにそのまま処理を続行するため、後続のアサーションが誤った結果を出力する可能性がありました。これらのパターンの共通の問題は、失敗の根本原因(条件が満たされなかったこと)とアサーションの失敗箇所が分離してしまう点にあります。

技術的な変更

3箇所のポーリングループを wait_for ヘルパーに置き換え、タイムアウト時に Timeout::Error を発生させるよう統一しました。

reaper_test.rb では上限なしのビジーウェイトが2箇所修正されています。

変更前:

until fp.flushed
  Thread.pass
end

変更後:

wait_for(message: "fake pool was not flushed") { fp.flushed }

asynchronous_queries_test.rb では、サイレントにタイムアウトするポーリングが修正されています。

変更前:

def wait_for_future_result(result)
  500.times do
    break unless result.pending?
    sleep 0.02
  end
end

変更後:

def wait_for_future_result(result)
  wait_for(message: "future result still pending", timeout: 10, interval: 0.02) { !result.pending? }
end

いずれのファイルも、ActiveRecord::TestCase::WaitForTestHelperinclude することで wait_for ヘルパーを利用可能にしています。reaper_test.rb 内の wait_for_conn_idle メソッドは既存の timeout 引数を受け取る実装になっており、wait_fortimeout: キーワード引数にそのまま委譲する形で書き直されています。

設計判断

wait_for ヘルパーへの一元化が採用された背景には、失敗箇所を正確に特定するという明確な目標があります。

アドホックなポーリングパターンはそれぞれ異なる欠陥を持つため、テストが失敗したときの挙動も一貫しません。wait_for を共通インターフェースにすることで、タイムアウト時の動作(Timeout::Error を発生させ、message: でコンテキストを示す)が統一されます。これにより、ハングではなくテスト失敗として扱われ、CIログで原因箇所を即座に特定できます。段階的なリファクタリングを選ぶことで、変更範囲を最小限に抑えながら最も危険な「無限ハング」の問題を解消しています。

まとめ

本PRは、テストの信頼性を損なうアドホックなポーリングパターンを wait_for ヘルパーに統一することで、CIのハングとサイレントな誤検知の両方を排除しました。タイムアウト時に Timeout::Error が実際の失敗箇所で発生するようになったことで、テストの失敗がより診断しやすくなっています。

記事メタデータ

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

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術・設計(各論)→まとめ(結論)という構成が明確で、理想的な記事構造になっています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きコードブロックやGitHubへのリンク記法が、ガイドラインに準拠して正しく使用されています。

対象読者への適合性 ✓ PASS

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

「ポーリングループ」や「アサーション」といった専門用語を前提としており、対象読者である専門エンジニアにとって適切なレベルで記述されています。

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

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

各セクションが「総論→各論→結論」の構造を持ち、各パラグラフもトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事で引用されているコードは、提供されたDiffの内容と正確に一致しており、変更点が正しく示されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「ビジーウェイト」や「サイレントタイムアウト」など、PRの文脈に合った技術用語が正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

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

PRの変更意図(ハングを`Timeout::Error`に変換する)を技術的に正確に解説しており、説明とコードの整合性が取れています。

事実の突合 ✓ PASS

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

記事内のすべての主張が、PRのDescriptionやDiff内のコードによって裏付けられており、ハルシネーションは検出されませんでした。

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

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

PR番号(#57324, #57309)やコード内の数値(500.times, 0.02)が正確に引用されています。

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

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

記事のタイトルはPR「Use wait_for helper in reaper and async query tests」の内容を的確に要約しており、一貫性があります。

外部知識の正確性 ✓ PASS

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

記事の内容は提供されたPR情報に限定されており、根拠のない外部知識の追加は見られませんでした。

時間表現の正確性 ✓ PASS

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

記事内に不正確な時間表現はなく、PRの状況を正しく伝えています。