非StandardError例外でクエリが中断された際に接続を未検証にマークするプール整合性修正

rails/rails

リード文ConnectionAdapters::AbstractAdapter#with_raw_connectionStandardError しか捕捉しないため、TimeoutAsync::Stop などの Exception 系例外でクエリが中断されると、接続が未検証のままプールに戻り、次回のチェックアウトで EOFError 等の二次エラーが発生します。本修正はその接続を未検証状態にマークし、再利用時に再検証を強制することでプールの整合性を保ちます。

背景

with_raw_connectionStandardError 限定で例外を捕捉している点が問題です。このメソッドはクエリ実行中に例外が発生した場合、downgrade_connection_after_error を呼び出して接続を降格させますが、StandardError の子孫以外の例外は rescue 節に到達せず、接続はそのまま verified フラグが立った状態でプールに返ります。

結果として、次のチェックアウトで未検証のソケットが再利用され EOFErrorActiveRecord::ConnectionFailed が発生します。この現象は、Fiber スケジューラが Async::Stop などの例外をクエリ途中で投げた場合に顕在化し、1 件の中断リクエストが無関係な後続リクエストを破壊します。

このプール整合性の欠如は、接続の状態管理が例外階層に依存していることに起因していますwith_raw_connection が例外を捕捉できなければ、@verified が true のまま残り、最近使用されたとみなされて検証がスキップされるロジックが働き続けます。

技術的な変更

with_raw_connectionrescue Exception 節を追加し、接続を未検証にする処理を導入しました。この新しい rescue 節は @last_activity = nil@verified = false を設定した後、例外を再スローします。これにより、例外が StandardError 系でなくても接続は降格され、次回利用時に必ず再検証が走ります。

変更前後のコードは以下の通りです

@@
-            rescue => e
-              downgrade_connection_after_error(translated_exception)
-              raise translated_exception
+            rescue => e
+              downgrade_connection_after_error(translated_exception)
+              raise translated_exception
+            rescue Exception
+              # A non-StandardError (a Timeout, or a fiber scheduler's cancel) abandoned
+              # the query partway through, so we mark the connection unverified, just as
+              # a failed query would, forcing a reconnect before it's used again.
+              @last_activity = nil
+              @verified = false
+              raise

テストケースも追加され、非 StandardError の中断が接続を未検証にすることを検証していますadapter_test.rbtest "a non-StandardError interrupt marks the connection for re-verification" を追加し、例外発生後に verified? が false、@last_activity が nil になることを確認します。

この変更により、StandardError 以外の例外でも既存の降格ロジックと同等の安全策が適用されますdowngrade_connection_after_error の呼び出しは行わず、@last_activity@verified のみを書き換えることで、@needs_reconnect は設定せずに済みます。結果として、接続は次回使用時に再検証され、プール全体の整合性が保たれます。

設計判断

rescue Exception を採用し、既存の downgrade_connection_after_error を呼び出さずにフラグだけを書き換える方針が選択されました。これは StandardError 系の例外処理と同様の効果を最小限のコード変更で実現し、既存のエラーハンドリングフローを乱さないという設計意図に沿っています。

attempt_configure_connection が同様に rescue Exception を使っている点が前例となっています。このファイル内の他の箇所でも Exception 捕捉が行われているため、今回の実装はコードベース全体の例外処理パターンと一貫しています。

このアプローチは後方互換性を維持しつつ、必要最小限の状態変更で安全性を確保するトレードオフです@needs_reconnect を設定しないことで、再接続のタイミングは従来通り @verified が false になる次回の検証で行われ、余計な接続切断を防ぎます。

まとめ

本修正は、StandardError 以外の例外でクエリが中断された際に接続を未検証にマークし、次回チェックアウト時に必ず再検証を行うことでプールの整合性を回復します。最小限のフラグ操作と既存パターンの踏襲により、既存コードへの影響を抑えつつ信頼性を向上させました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
9e325674

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的な変更、設計判断、まとめの5つのセクションが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライトは正しい形式 (```ruby:filepath) で記述され、PRリンクも #番号 の形式で正しくリンク化されています。

対象読者への適合性 ✓ PASS

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

内容は Rails の内部実装に詳しいエンジニア向けで、過度な基礎解説はなく適切です。

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

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

各セクションは総論・各論・結論のパラグラフ構成となり、トピックセンテンスで始まり、1段落1トピック、長さも適切です。空行で区切られています。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供されたDiffと完全に一致しており、変更内容を正確に反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

StandardError、Exception、downgrade_connection_after_error などの用語は PR と一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

技術的な説明は PR の背景・目的・実装と合致し、因果関係も論理的です。

事実の突合 ✓ PASS

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

記事の全ての主張は PR タイトル・説明・Diff で裏付けられており、根拠のない推測はありません。

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

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

PR 番号 #57953 などの数値は正確です。

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

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

記事タイトルは PR タイトルの意味を日本語で適切に表現しています。

外部知識の正確性 ✓ PASS

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

外部知識(バージョンサポート状況等)は一切含まれておらず、PR だけに依拠しています。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PR の記述と一致しています。