ActionView の view_context_class 初期化を早期化し Ractor 共有を実現
この PR は enable_reloading が無効なケースで不要なミューテックス取得を削減し、Ractor 環境で view_context_class をそのまま共有できるようにします。結果としてスレッド競合を減らしつつ、Ractor 用の追加設定を不要にします。
背景
この変更は enable_reloading が無効な場合に不要なミューテックス取得を排除し、Ractor 環境での view_context_class の共有を可能にすることを目的としています。従来、view_context_class は毎回 @view_context_mutex.synchronize 内で初期化され、リロードが無効でもロックが発生していました。さらに、Ractor から直接参照できないことがテストで指摘されており、追加のセットアップが必要でした。
技術的な変更
actionview/lib/action_view/lookup_context.rb では view_context_class の取得ロジックを刷新し、既にインスタンスが存在すればロックを回避してすぐに返すようにしました。
@@
- def self.view_context_class
- @view_context_mutex.synchronize do
- @view_context_class ||= ActionView::Base.with_empty_template_cache
- end
- end
+ def self.view_context_class
+ return @view_context_class if @view_context_class
+ base = ActionView::Base # prevent recursive locking
+ @view_context_mutex.synchronize do
+ @view_context_class = base.with_empty_template_cache
+ end
+ end
reset_view_context_class を新たに導入し、clear 時にミューテックスで安全にインスタンスをリセットできるようにしました。
@@
- def self.clear
+ def self.clear
@@
- @view_context_class = nil
+ ActionView::LookupContext.reset_view_context_class
ミューテックス本体はメソッド定義の後方に遅延初期化し、ActiveSupport.on_load(:action_view) フックでロード時に view_context_class を先行取得させることで、Ractor がクラスを共有できる状態にします。
@@
- @view_context_mutex = Mutex.new
- ActiveSupport.on_load(:action_view) { ActionView::LookupContext.view_context_class }
+ @view_context_mutex = Mutex.new
+ ActiveSupport.on_load(:action_view) { ActionView::LookupContext.view_context_class }
actionview/lib/action_view/rendering.rb の参照先を LookupContext に統一し、view_context_class の取得が一元化されました。
@@
- klass = ActionView::LookupContext::DetailsKey.view_context_class
+ klass = ActionView::LookupContext.view_context_class
テスト actionview/test/template/lookup_context_test.rb に Ractor 用の検証が追加され、view_context_class が Ractor 間で同一オブジェクトであることが保証されました。
if RUBY_VERSION >= "4.0"
class LookupContextRactorTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
test "view_context_class is Ractor-shareable" do
@original_experimental_warning = Warning[:experimental]
Warning[:experimental] = false
ActionView::LookupContext.view_context_class # eager-load
assert_same ActionView::LookupContext.view_context_class,
Ractor.new { ActionView::LookupContext.view_context_class }.value
ensure
Warning[:experimental] = @original_experimental_warning
end
end
end
設計判断
view_context_class の取得を LookupContext に集約し、早期リターンでロック回避を実装したことで、不要な同期コストを排除しつつ既存のスレッド安全性は保持されています。reset_view_context_class を公開し clear から呼び出す設計は、状態リセットを一元管理できる点で拡張性を高めています。ActiveSupport.on_load フックの追加は、Ractor がクラスをロードするタイミングを保証し、追加のセットアップなしで共有可能にした実用的な選択です。
まとめ
view_context_class の初期化を早期化しロック取得を回避した本変更は、enable_reloading が無効な場合でもパフォーマンスを向上させ、Ractor で安全に共有できるようにしました。設計上は既存 API 互換を保ちつつ、リセットロジックとロードフックで拡張性と並行性を両立させています。