リロード時のExecutionContext破損を修正
この変更により、ActiveSupport::Reloader#reload! 実行時に ActiveSupport::ExecutionContext が破損する問題が修正されました。この問題は config.enable_reloading = true を設定したテスト環境で発生し、ActiveSupport::CurrentAttributes から NoMethodError が発生する原因となっていました。
背景
この問題は #55247 で導入された、テスト環境での ExecutionContext のネスト機能によるリグレッションです。ExecutionContext::Record は @store が常に Hash であることを前提としていますが、リロード後に nil になるケースがありました。
Springなどの開発ツールでは config.enable_reloading = true の設定が推奨されており、この設定下でテストを実行すると問題が顕在化します。以下のような操作シーケンスで再現します:
class Current < ActiveSupport::CurrentAttributes
attribute :trace_id
end
Rails.application.reloader.reload!
# NoMethodError: undefined method '[]' for nil
Current.trace_id
問題の根本原因は、リロード時のフック実行順序にあります。app.executor.to_run で push されたコンテキストが、app.reloader.before_class_unload で clear され、その後 app.executor.to_complete で pop される際にスタックが空になっていました。
技術的な変更
本PRでは、リロード時のコンテキストリセット方法を変更しています。新たに ExecutionContext.flush メソッドが追加され、スタック深度を保持したままコンテキスト内容をクリアします。
変更前:
app.reloader.before_class_unload do
ActiveSupport::CurrentAttributes.clear_all
ActiveSupport::ExecutionContext.clear
ActiveSupport.event_reporter.clear_context
end
変更後:
app.reloader.before_class_unload do
ActiveSupport::CurrentAttributes.clear_all
ActiveSupport::ExecutionContext.flush
ActiveSupport.event_reporter.clear_context
end
flush メソッドの実装では、スタックの各要素を空の Hash で初期化し、@store と @current_attributes_instances もリセットします:
def flush
@stack = Array.new(@stack.size) { {} }
@store = {}
@current_attributes_instances = {}
self
end
clear がスタック自体を破棄していたのに対し、flush はスタックサイズを維持します。これにより、push -> flush -> pop のシーケンスでも正しく動作するようになりました。
設計判断
スタック深度の保持 という設計が採用されました。
clear を使い続ける代わりに flush を導入した理由は、テスト環境でのネストされた実行コンテキストとの互換性です。#55247 でテストケース内でのコンテキストネストが可能になりましたが、リロード時にスタックが完全にクリアされると、外側のコンテキストに戻れなくなります。
flush は各スタックレベルの内容をクリアしながらも構造を保持するため、pop が正しく前の状態を復元できます。この設計により、テストの実行コンテキストとコントローラ/ジョブの実行コンテキストを分離するという #55247 の目的を損なうことなく、リロード機能との両立を実現しています。
テストでは push -> flush -> pop のシーケンスが正常に動作することを検証しており、リロード後もコンテキストが有効な状態を保つことが保証されています。
まとめ
本PRは、リロード機能とネストされた実行コンテキストの両立を可能にする修正です。ExecutionContext.clear を flush に置き換えることで、スタック構造を保持したままコンテキストをリセットし、Springなどの開発ツールでの config.enable_reloading = true 設定との互換性を確保しています。