スレッドプール飽和時のインストゥルメンターリーク修正

rails/rails

FutureResult#execute_or_skip がスレッドプール飽和時にカレントスレッドのインストゥルメンターを EventBuffer で上書きしたまま復元しない不具合が修正されました。これにより、caller_runs フォールバック発生後も sql.active_record 通知が正常に配信されるようになります。

背景

async_query_executor:global_thread_pool に設定している環境で、一部のリクエストの同期クエリがインストゥルメンテーションおよびログに記録されない問題が報告されていました。この問題はスレッドプールが飽和した際に発生していました。

グローバルスレッドプールは fallback_policy: :caller_runs を採用しており、スレッドおよびキューが飽和した際には非同期タスクをカレントスレッドで実行します。この動作は activerecord/lib/active_record.rb の設定に明示されています。

根本原因は execute_or_skip の設計上の想定にあります。このメソッドはバックグラウンドスレッドで実行されることを前提として設計されており、スレッドローカルなインストゥルメンターの復元処理を持っていませんでした。caller_runs によってリクエストスレッド上で実行された場合、ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] に設定された EventBuffer がそのスレッドに残り続けます。Rails はリクエスト間で IsolatedExecutionState:active_record_instrumenter キーをクリアしないため、汚染はスレッドのライフタイム全体にわたって持続します。

技術的な変更

execute_or_skip のミューテックス確保後、EventBuffer のセットアップ前にインストゥルメンターの現在値を保存し、ensure ブロックで復元するよう変更されました。

変更箇所は activerecord/lib/active_record/future_result.rb の2行追加のみです。

変更前:

@pool.with_connection do |connection|
  return unless @mutex.try_lock
  begin
    if pending?
      @event_buffer = EventBuffer.new(self, @instrumenter)
      ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
      execute_query(connection, async: true)
    end
  ensure
    @mutex.unlock
  end
end

変更後:

@pool.with_connection do |connection|
  return unless @mutex.try_lock
  previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
  begin
    if pending?
      @event_buffer = EventBuffer.new(self, @instrumenter)
      ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
      execute_query(connection, async: true)
    end
  ensure
    ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
    @mutex.unlock
  end
end

バックグラウンドスレッドで実行される通常ケースでは previous_instrumenternil になるため、復元後も従来と同じ動作を維持します。caller_runs によってリクエストスレッドで実行される場合は、元のインストゥルメンターが ensure で確実に復元されます。

あわせて、caller_runs フォールバックをシミュレートするテストが activerecord/test/cases/relation/load_async_test.rb に追加されました。テストでは pool.singleton_classschedule_query を再定義してカレントスレッドで execute_or_skip を実行させ、その後の同期クエリで sql.active_record 通知が発行されることを ActiveSupport::Notifications.subscribed で検証しています。

設計判断

ミューテックス取得直後previous_instrumenter を保存し、ensure ブロックで復元する配置が選ばれました。

ミューテックス取得後に保存することで、EventBuffer が設定される直前の状態を確実にキャプチャできます。また、ensure ブロックはミューテックスのアンロックと同じブロックに置かれており、インストゥルメンターの復元とロック解放が常にペアで実行されることを保証します。return unless @mutex.try_lock でロック取得に失敗した場合は即座にリターンするため、インストゥルメンターの変更は発生せず、復元処理も不要になるという整合性も保たれています。

本修正は #56963 の 8.1 バックポートとして実装されています。

まとめ

2行の変更でスレッドプール飽和時のインストゥルメンター汚染を根本的に解消し、sql.active_record 通知の配信保証を回復します。IsolatedExecutionState を変更するコードでは「変更前の値を保存して ensure で復元する」パターンが重要であることを、このPRは改めて示しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
0a788c98

この記事は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リンク記法の正確性

ファイル名付きのコードブロック構文、PR番号のリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Railsの非同期処理やインストゥルメンテーションに関する深い知識を前提としており、専門的なエンジニアという対象読者に完全に適合しています。

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

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

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

Diff内容との照合 ✓ PASS

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

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

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PRで使われている`FutureResult`, `EventBuffer`, `IsolatedExecutionState`などの技術用語を正確に使用しており、誤用は見られません。

説明の技術的正確性 ✓ PASS

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

スレッドプール飽和時の問題発生メカニズムと、`ensure`ブロックによる修正内容の説明は、PR情報とDiffの内容に裏付けられており、技術的に正確です。

事実の突合 ⚠ WARNING

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

ほぼすべての主張がPR情報で裏付けられていますが、「この動作は `activerecord/lib/active_record.rb` の設定に明示されています」という記述は、PR情報には直接含まれていない補足情報です。

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

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

PR番号(#56965, #56963)やバージョン番号(8.1)などの数値・固有名詞はすべて正確に記載されています。

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

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

記事のタイトル「スレッドプール飽和時のインストゥルメンターリーク修正」は、PRの主題である「インストゥルメンターの復元」という技術的変更がもたらす結果を的確に表現しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

PR情報に記載のないバージョンサポート状況やリリース日程などの外部知識は含まれておらず、事実に忠実です。

時間表現の正確性 ✓ PASS

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

「報告されていました」「持続します」など、問題の発生状況や状態に関する時間表現は、PRの情報と一致しており正確です。