非StandardError例外でクエリが中断された際に接続を未検証にマークするプール整合性修正
リード文:ConnectionAdapters::AbstractAdapter#with_raw_connection が StandardError しか捕捉しないため、Timeout や Async::Stop などの Exception 系例外でクエリが中断されると、接続が未検証のままプールに戻り、次回のチェックアウトで EOFError 等の二次エラーが発生します。本修正はその接続を未検証状態にマークし、再利用時に再検証を強制することでプールの整合性を保ちます。
背景
with_raw_connection が StandardError 限定で例外を捕捉している点が問題です。このメソッドはクエリ実行中に例外が発生した場合、downgrade_connection_after_error を呼び出して接続を降格させますが、StandardError の子孫以外の例外は rescue 節に到達せず、接続はそのまま verified フラグが立った状態でプールに返ります。
結果として、次のチェックアウトで未検証のソケットが再利用され EOFError や ActiveRecord::ConnectionFailed が発生します。この現象は、Fiber スケジューラが Async::Stop などの例外をクエリ途中で投げた場合に顕在化し、1 件の中断リクエストが無関係な後続リクエストを破壊します。
このプール整合性の欠如は、接続の状態管理が例外階層に依存していることに起因しています。with_raw_connection が例外を捕捉できなければ、@verified が true のまま残り、最近使用されたとみなされて検証がスキップされるロジックが働き続けます。
技術的な変更
with_raw_connection に rescue 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.rb に test "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 以外の例外でクエリが中断された際に接続を未検証にマークし、次回チェックアウト時に必ず再検証を行うことでプールの整合性を回復します。最小限のフラグ操作と既存パターンの踏襲により、既存コードへの影響を抑えつつ信頼性を向上させました。