Rack Hijack時のExecutor状態を即時解放する修正

rails/rails

Rack HijackやWebSocketアップグレード時に ActionDispatch::Executor がExecutor状態を即座に解放するようになりました。これにより、fiber-scheduledサーバー(Falconなど)でのリロードブロック問題が解消されます。

背景

ActionDispatch::Executor はレスポンスボディの close コールバック、またはサーバーが提供する rack.response_finished を通じてExecutor状態(リローダーのshareロック)を解放していました。しかし、WebSocketアップグレードやfull rack hijackではボディが長命なストリーミング接続になるため、ソケットが閉じるまで close が発火しません。

この挙動が問題を引き起こす経路は、サーバーによって異なります。Puma ではAction Cableのhijackがワーカープールへのデタッチを行うため、元のリクエストスレッドが解放され問題が表面化しませんでした。一方、Falcon のようなfiber-scheduledサーバーではリクエストfiberがhijackされたソケットをインラインで読み続けるため、shareロックがクライアント切断まで保持されます。開発環境のリローダーが before_class_unload で排他ロック(:unload)を取得しようとすると、shareが保持されている間は取得不可能なため、クライアントが切断するまでコード再ロードがブロックされます。

関連する #57423ActiveSupport::Concurrency::ShareLock がfiber isolationのもとで同一スレッド上の全fiberを同一オーナーとして扱うバグを修正しており、本PRと組み合わせることで問題が完全に解消されます。

技術的な変更

Hijackを検出してExecutor状態を即時に完了させるため、ActionDispatch::Executor#call に3つの変更が加えられました。

べき等な finalize クロージャの導入

従来は state.complete! の呼び出しが3箇所に直書きされていましたが、二重完了を防ぐためのフラグ付き finalize クロージャに統一されました。

変更前:

state = @executor.run!(reset: true)
if response_finished = env["rack.response_finished"]
  response_finished << proc { state.complete! }
end
# ...
unless response_finished
  response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
end
# ...
if !returned && !response_finished
  state.complete!
end

変更後:

state = @executor.run!(reset: true)

completed = false
finalize = -> {
  next if completed
  completed = true
  state.complete!
}

if response_finished = env["rack.response_finished"]
  response_finished << proc { finalize.call }
end
# ...
if hijacked?(env, response)
  finalize.call
elsif !response_finished
  response << ::Rack::BodyProxy.new(response.pop) { finalize.call }
end
# ...
if !returned && !response_finished
  finalize.call
end

hijacked? プライベートメソッドの追加

Hijack検出は新しいプライベートメソッド hijacked? が担います。env["rack.hijack_io"] の存在(full rack hijack)またはレスポンスステータス 101(WebSocketアップグレード)のいずれかが真であれば hijack と判定します。

def hijacked?(env, response)
  return false unless response

  env["rack.hijack_io"] || response.first == 101
end

なお、env["rack.hijack?"] はサーバーがhijackをサポートすることを示すフラグであり、hijackが実際に発生したかどうかとは無関係なため、検出条件には含まれていません。

hijackが検出された場合は finalize.call を即座に呼び出し、その後 rack.response_finished のコールバックが発火しても completed フラグにより二重完了が防がれます。

設計判断

finalize クロージャによる一元管理が採用されました。Hijack検出パスを追加するにあたり、新たな state.complete! の呼び出しを加えると既存の3パス(rack.response_finishedBodyProxy・ensureブロック)との組み合わせで二重完了リスクが生じます。べき等なクロージャへの統一は、この問題を完全に排除するとともに、将来的な完了パスの追加時にも同様の保護を提供します。

また、Long-lived hijackのコンシューマー(Action Cable)は before_class_unload でのコネクション切断によってリロードを管理しているため、hijack時点でshareを解放してもオートロードされた定数がクリアされるリスクはありません。リクエストの処理に必要なコードはhijackレスポンスを返す時点で解決済みであり、この判断が設計の前提になっています。

まとめ

本PRはfiber-scheduledサーバー環境でのリロードブロックという実用上の問題に対し、既存の完了パスをべき等なクロージャに統一したうえでhijack検出を追加するという最小限の変更で解決しています。ActionDispatch::Executor の完了ライフサイクルが明確に整理されたことで、将来的な新しい完了パスの追加も安全に行える基盤が整いました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
92caae60

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「リード文(総論)→背景・技術・設計(各論)→まとめ(結論)」という理想的な3部構成が明確に守られています。各セクションの役割がはっきりしており、非常に読みやすいです。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:filepath)とGitHubのPRリンク記法([#123](URL))が、ガイドライン通りに正しく使用されています。

対象読者への適合性 ✓ PASS

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

「fiber-scheduledサーバー」「shareロック」などの専門用語が注釈なしで使われており、専門知識を持つエンジニアという対象読者に完全に適合しています。

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

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

各セクションが「総論→各論」で構成され、各パラグラフがトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されています。可読性が非常に高いです。

Diff内容との照合 ✓ PASS

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

記事内の「変更前」「変更後」のコードブロックは、提供されたDiff情報を正確に反映しています。`hijacked?`メソッドの引用も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「shareロック」「排他ロック」「べき等なクロージャ」など、PRの文脈における技術用語を正確かつ適切に使用しています。

説明の技術的正確性 ✓ PASS

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

PumaとFalconでの挙動の違いや、`rack.hijack?`を検出条件に含めない理由など、PRの技術的背景や判断を正確に説明できています。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescriptionやDiff情報によって裏付けられています。ハルシネーション(情報の捏造)は見られません。

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

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

PR番号(#57425, #57423)やHTTPステータスコード(101)など、記事中の数値や固有名詞はすべて正確です。

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

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

記事のタイトル「Rack Hijack時のExecutor状態を即時解放する修正」は、PRのタイトル「Release reloader share on rack hijack in ActionDispatch::Executor」の内容を的確に要約しています。

外部知識の正確性 ✓ PASS

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

記事に含まれる情報はすべてPR内で言及されている範囲に留まっており、サポート状況やリリース日程といったPR外の知識を不適切に付与していません。

時間表現の正確性 ✓ PASS

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

「~ようになりました」といった表現は、この変更が既に行われた事実として記述されており、PRの内容と時間的な認識にズレはありません。