バックトレースクリーンナーをデフォルトでRactor共有可能に
Rails のバックトレースクリーンナーが、デフォルトフィルタおよびサイレンサーを Ractors.shareable_proc でラップすることで、Rails.application 全体を Ractor に渡す際の共有エラーを解消します。
背景
Rails.application を Ractor に移行すると、内部で保持している Proc オブジェクト が非共有オブジェクトとなり、Ractor.make_shareable が失敗します。バックトレースクリーンナーはデフォルトで複数のフィルタとサイレンサーを持つため、手動で共有可能に変換する手間が発生していました。この PR は、デフォルト実装だけで共有可能にし、開発者が追加の設定を行わなくても Ractor 化できるようにすることを目的としています。
技術的な変更
ActiveSupport::BacktraceCleaner の変更
add_gem_filter、add_core_silencer、add_gem_silencer、add_stdlib_silencer が、従来のブロックを Ractors.shareable_proc でラップした形に書き換えられました。これにより、各フィルタ・サイレンサーが Ractor 共有可能な Proc として登録されます。
- add_filter { |line| line.sub(gems_regexp, gems_result) }
+ add_filter(&Ractors.shareable_proc { |line| line.sub(gems_regexp, gems_result) })
@@
- add_silencer { |line| line.include?("<internal:") }
+ add_silencer(&Ractors.shareable_proc { |line| line.include?("<internal:") })
@@
- add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
+ add_silencer(&Ractors.shareable_proc { |line| FORMATTED_GEMS_PATTERN.match?(line) })
@@
- add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
+ add_silencer(&Ractors.shareable_proc { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) })
Rails::BacktraceCleaner の変更
initialize メソッド内で追加されるカスタムフィルタとサイレンサーが ActiveSupport::Ractors.shareable_proc でラップされました。さらに、root の取得ロジックをクラスメソッドに切り出し、freeze 時に参照を確保することで、遅延評価の影響を抑制しています。
@@
- add_filter do |line|
- @root ||= Rails.root && "#{Rails.root}/"
- @root && line.start_with?(@root) ? line.from(@root.size) : line
- end
+ add_filter(&ActiveSupport::Ractors.shareable_proc do |line|
+ BacktraceCleaner.root && line.start_with?(BacktraceCleaner.root) ? line.from(BacktraceCleaner.root.size) : line
+ end)
@@
- add_filter do |line|
- if RENDER_TEMPLATE_PATTERN.match?(line)
- line.sub(RENDER_TEMPLATE_PATTERN, "")
- else
- line
- end
- end
+ add_filter(&ActiveSupport::Ractors.shareable_proc do |line|
+ if RENDER_TEMPLATE_PATTERN.match?(line)
+ line.sub(RENDER_TEMPLATE_PATTERN, "")
+ else
+ line
+ end
+ end)
@@
- add_silencer do |line|
- line.start_with?(File::SEPARATOR, "vendor/", "bin/")
- end
+ add_silencer(&ActiveSupport::Ractors.shareable_proc do |line|
+ line.start_with?(File::SEPARATOR, "vendor/", "bin/")
+ end)
テストの追加
ActiveSupport::Testing::RactorsAssertions モジュールで assert_ractor_shareable ヘルパーが導入され、BacktraceCleanerDefaultFilterAndSilencerTest と BacktraceCleanerTest に共有可能性を検証するテストが追加されました。テストは Ruby 4.0 以上では実際に Ractor.make_shareable を呼び出し、例外が起きないことを確認します。
module ActiveSupport
module Testing
module RactorsAssertions # :nodoc: all
private
if RUBY_VERSION >= "4.0"
def assert_ractor_shareable(obj)
assert_nothing_raised { Ractor.make_shareable(obj) }
end
else
def assert_ractor_shareable(obj)
assert true
end
end
end
end
end
設計判断
デフォルト実装に対してのみ Ractors.shareable_proc を適用する方針が選択されました。PR 本文に記載された通り、ユーザーが追加したカスタム Proc は self 参照の有無を判定できないため、デフォルトフィルタ・サイレンサーだけを安全に共有可能にすることで既存の動作を保ちつつ Ractor 互換性を確保しています。クラス側で instance_exec を使用するマクロ系のブロックは別途対応が検討されていますが、今回の変更は後方互換性と安全性を最優先した実装です。
まとめ
この PR により、Rails のバックトレースクリーンナーはデフォルトで Ractor 共有可能となり、Rails.application 全体を Ractor に渡す際の障壁が除去されました。既存のフィルタ・サイレンサーの動作は変わらず、追加されたテストで共有可能性が保証されているため、開発者は設定変更なしで安全に Ractor 化を利用できます。