Mysql2Adapter#discard! が子プロセスで親接続を破損するバグを修正

rails/rails

Mysql2Adapter#discard! がフォーク後の子プロセスで残存するステートメントのファイナライズにより、親プロセスの MySQL 接続が切断される問題を、ソケットを IO::NULL にリダイレクトする形で解決しました。

背景

この変更は、activerecord mysql2 (master) [prepared_statements] の Nightly テストが、フォークされた子プロセスが親接続を破壊するケースで失敗したことが発端です。テストは ActiveRecord::ConnectionFailed を示し、MySQL サーバ側で "Lost connection to MySQL server during query" が記録されました。

MYSQL_PREPARED_STATEMENTS=true が有効な状態では、親プロセスの @statements キャッシュに Mysql2::Statement オブジェクトが保持され、fork 後に子プロセスが同一のソケットファイルディスクリプタと Ruby のステートメント参照を継承します。その結果、子プロセスの終了時にステートメントのファイナライザが COM_STMT_CLOSE を送信し、親プロセスの接続がサーバ側で閉じられてしまいます。

技術的な変更

Mysql2Adapter#discard! に一行の追加が行われ、子プロセスでのソケットリダイレクトを実装しました。具体的には IO.for_fd(@raw_connection.socket, autoclose: false).reopen(IO::NULL)super の呼び出し直後に挿入し、@raw_connection が存在すれば安全に /dev/null へ再オープンします。

変更前:

def discard! # :nodoc:
  @lock.synchronize do
    super
    @raw_connection&.automatic_close = false
    @raw_connection = nil
  end
end

変更後:

def discard! # :nodoc:
  @lock.synchronize do
    super
    IO.for_fd(@raw_connection.socket, autoclose: false).reopen(IO::NULL) if @raw_connection rescue nil
    @raw_connection&.automatic_close = false
    @raw_connection = nil
  end
end

この一行は、子プロセスが継承したソケットに対して書き込み系のファイナライザが実行されても、実体が IO::NULL であるため親接続へ影響を与えません。rescue nil によりソケットが取得できないケースでも例外が抑制され、既存の動作に干渉しない安全策となっています。

設計判断

本修正は PostgreSQLAdapter#discard! が採用している "socket を IO::NULL に再オープンする" 手法を Mysql2Adapter にも適用したものです。PR のコメントからは、設定キーを追加せず、既存メソッドに最小限のロジック追加で対応する方針が選択されたことが読み取れます。

この設計は 後方互換性侵入性の低さ を重視しています。discard! のシグネチャや呼び出し側コードは一切変更せず、内部実装だけを差し替えることで、既存アプリケーションへの影響を回避しています。また、標準ライブラリのみで完結するため、追加の依存やプラットフォーム固有のコードを導入しない点も評価できます。

まとめ

Mysql2Adapter#discard! にソケットを IO::NULL へリダイレクトする処理を加えることで、フォークされた子プロセスが親の MySQL 接続を破壊するバグを解消しました。テストスイートは prepared_statements 環境下でも全て成功し、fork safety が確保されたことが確認されています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
6e0f9393

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断(任意)、まとめの5要素が揃っており、総論→各論→結論の流れが明確です。まとめはリード文の単なる繰り返しではなく、修正の意義を述べています。

カスタムMarkdown構文 ✓ PASS

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

コードブロックは `ruby:ファイルパス` 形式で正しく記述され、PRリンクは `[#57460](URL)` の形式で正しくリンク化されています。

対象読者への適合性 ✓ PASS

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

高度な ActiveRecord と MySQL の内部挙動を前提とした記述で、エンジニア向けに適切です。初心者向けの余計な解説はありません。

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

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

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

Diff内容との照合 ✓ PASS

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

記事中の変更前・変更後コードは Diff の追加行と完全に一致しており、抜けや余計な記述はありません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている用語(socket、IO::NULL、fork safety、prepared statements など)は PR と一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

技術的な説明は PR の背景・詳細と合致し、因果関係や影響範囲の記述も正確です。

事実の突合 ✓ PASS

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

すべての主張は PR のタイトル、Description、Diff、テスト結果に裏付けられており、外部知識や推測は含まれていません。

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

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

PR 番号 #57460 など数値は正しく記載されています。

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

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

記事タイトルは PR タイトルの意味合いを正確に日本語化しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

記事にバージョンサポートやリリース日程等の外部情報は含まれておらず、PR 内容の範囲内に収まっています。

時間表現の正確性 ✓ PASS

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

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