`preconnect`に`reaper_lock`を追加してリーパーとの競合状態を修正

rails/rails

pool.preconnectがリーパースレッドと並行実行された際に発生する断続的なテスト失敗を、reaper_lockの適用範囲を拡張することで修正しました。

背景

CIでtest_preconnectが断続的に失敗するという問題が報告されていました。リーパースレッドはメンテナンスサイクルの一部としてpreconnectを呼び出す際に reaper_lock を保持しています。一方、テストがpool.preconnectを並行して呼び出した場合、リーパーがcheckout_for_maintenanceを通じてコネクションをチェックアウト済みの状態(allow_preconnect=false@raw_connection=nilという過渡的な状態)にしている可能性があり、テスト側のpreconnectがそのコネクションを処理できませんでした。

この問題はDocker上のMariaDB 11.4を低リソース環境(--cpus=0.02 --memory=128m)で再現検証されており、修正前は7、16、36、65イテレーション目で失敗が確認されています。修正後は100回以上のイテレーションがすべて成功し、reaper_lockの待機が86回以上トリガーされたことも確認されています。

技術的な変更

preconnectメソッドの処理全体をreaper_lockブロックで囲む、最小限の変更が加えられました。

変更前:

def preconnect
  sequential_maintenance -> c { (!c.connected? || !c.verified?) && c.allow_preconnect } do |conn|
    conn.connect!
  rescue
    # Wholesale rescue: there's nothing we can do but move on. The
    # connection will go back to the pool, and the next consumer will
    # presumably try to connect again -- which will either work, or
    # fail and they'll be able to report the exception.
  end
end

変更後:

def preconnect
  reaper_lock do
    sequential_maintenance -> c { (!c.connected? || !c.verified?) && c.allow_preconnect } do |conn|
      conn.connect!
    rescue
      # Wholesale rescue: there's nothing we can do but move on. The
      # connection will go back to the pool, and the next consumer will
      # presumably try to connect again -- which will either work, or
      # fail and they'll be able to report the exception.
    end
  end
end

この変更により、preconnectの実行中はリーパースレッドがcheckout_for_maintenanceでコネクションを過渡状態に置くことができなくなります。

設計判断

reaper_lockを再入可能なMonitorとして実装する既存パターンを踏襲することで、デッドロックを発生させずに排他制御を実現しています。

reaper_lockはリエントラントなMonitorを使用しているため、リーパー自身がpreconnectを呼び出す際(すでにreaper_lockを保持している状態)はブロックされることなくそのまま通過します。この設計は、e45d8acc746disconnectdiscard!with_exclusively_acquired_all_connectionsに適用された「グローバル操作中はプールをロックする」パターンの一貫した拡張です。preconnectのみがそのパターンから漏れていたことが今回の競合状態の根本原因でした。

変更差分は9行の追加と7行の削除という小さなものですが、sequential_maintenanceブロック全体がreaper_lockのスコープに収まるよう正確にネストされており、既存のエラーハンドリングロジックには手が加えられていません。

まとめ

本PRはpreconnectを既存のロック戦略に組み込むことで、グローバル操作に対する一貫した排他制御を実現しました。リエントラントなMonitorの特性を活かすことで、リーパー自身の動作を妨げることなく競合状態を排除しており、コネクションプールのスレッドセーフティが一段と強固になっています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
3b247c80

この記事は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番号やコミットIDのGitHubリンク記法がすべて正しく使用されています。

対象読者への適合性 ✓ PASS

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

専門用語を適切に用い、冗長な説明を省いており、専門知識を持つエンジニアという対象読者に完全に適合しています。

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

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

各セクションが総論→各論の構成で書かれ、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られています。段落の長さも適切で、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは、提供されたDiff情報を正確に反映しており、変更前後の状態が正しく示されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`reaper_lock`や`reentrant Monitor`などの技術用語が、PRの文脈に沿って正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

競合状態の発生メカニズムやロックによる解決策の説明は、技術的に正確かつ論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張(CIの失敗、再現環境、具体的な数値など)は、提供されたPR Descriptionによって完全に裏付けられており、ハルシネーションは一切ありません。

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

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

PR番号、コミットID、バージョン番号、再現テストのイテレーション数など、すべての数値と固有名詞が正確に記載されています。

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

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

記事のタイトルは元のPRのタイトルを的確に要約しており、記事内容と完全に一致しています。

外部知識の正確性 ✓ PASS

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

記事の内容はすべて提供されたPR情報に基づいており、PRに記載のない外部知識の追加(捏造)は見られません。

時間表現の正確性 ✓ PASS

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

「報告されていました」といった過去の事象を示す時間表現が正確に使用されており、PR情報との間に矛盾はありません。