Ractor共有性メソッドのシムをKernelに追加
Active SupportのRactor対応を進める第一歩として、Ractor.make_shareableなどの共有性APIをRubyバージョンを意識せずに呼び出せるKernel拡張が追加されました。シムはRuby環境に応じて実際のRactorメソッドへの委譲またはno-opに切り替わり、フレームワーク全体でのバージョン分岐コードを排除します。
背景
RailsはRactorサポートの追加に向けて動き出しており、フレームワーク各所でRactor.make_shareableやRactor.shareable_procを呼び出す必要が生じます。しかしRactorのAPIは全てのRubyバージョンで利用可能ではないため、そのまま呼び出せば環境によってエラーが発生します。
従来のアプローチでは呼び出し箇所ごとにdefined?(Ractor) && Ractor.respond_to?(:make_shareable)のようなガード節を書く必要があり、コードベース全体に同様のチェックが散在することになります。このPRはそうした重複を解消するシム(互換レイヤー)を一か所に集約することを目的としています。
PR内ではシムの置き場所としてRactorShareability.shareable?のようなモジュールスコープの実装案も言及されていますが、最終的にはKernelへの拡張が採用されています。
技術的な変更
新ファイル activesupport/lib/active_support/core_ext/kernel/ractor_shareability.rb が追加され、Kernelモジュールに4つのプライベートメソッドが定義されました。各メソッドはdefined?(Ractor) && Ractor.respond_to?(:メソッド名)をモジュール評価時(ロード時)に一度だけ判定し、2つのメソッド定義を切り替えるという構造を取っています。
追加されたメソッドは以下の4つです:
-
ractor_make_shareable(obj, copy: false):Ractor.make_shareable(obj, copy: copy)に委譲、またはobjをそのまま返す -
ractor_shareable?(obj):Ractor.shareable?(obj)に委譲、またはobjをそのまま返す -
ractor_shareable_proc { ... }:Ractor.shareable_proc { ... }に委譲、または渡されたブロックをそのまま返す -
ractor_shareable_lambda { ... }:Ractor.shareable_lambda { ... }に委譲、または渡されたブロックをそのまま返す
実装の核心部分を以下に示します:
module Kernel
private
if defined?(Ractor) && Ractor.respond_to?(:make_shareable)
def ractor_make_shareable(obj, copy: false)
Ractor.make_shareable(obj, copy: copy)
end
else
def ractor_make_shareable(obj, copy: false)
obj
end
end
if defined?(Ractor) && Ractor.respond_to?(:shareable?)
def ractor_shareable?(obj)
Ractor.shareable?(obj)
end
else
def ractor_shareable?(obj)
obj
end
end
end
バージョン判定が実行時ではなくロード時のifで行われる点が重要です。これにより、メソッド呼び出しのたびにdefined?(Ractor)を評価するオーバーヘッドが発生せず、該当環境では直接Ractorメソッドへ委譲する最小コストのパスが生成されます。
テストは RUBY_VERSION >= "4.0" の条件ブロック内に記述されており、ractor_make_shareableでオブジェクトが実際にshareable化されること、ractor_shareable_procとractor_shareable_lambdaがそれぞれself:オプション付きで正しく動作することを検証しています。
設計判断
Kernelへの拡張という方式が採用されました。PRではRactorShareabilityのような専用モジュールへの配置案も示されていましたが、フレームワーク全体のあらゆる場所からプレフィックスなしで呼び出せることを優先した判断です。
no-opがnilやfalseではなく引数や渡されたブロックをそのまま返す設計にも注目できます。これにより、Ractorが利用できない環境でも呼び出し結果をそのまま変数に代入して使用するコードが自然に動作します。ただしractor_shareable?がRactor非対応環境でオブジェクト自体(truthy値)を返す点は、boolean述語として扱う際に注意が必要です。
また、バージョン分岐をrespond_to?で行うことで、defined?(Ractor)が真でも特定メソッドが存在しない中間バージョンへの対応が可能になっています。
まとめ
このPRはRailsのRactor対応の布石として、バージョン判定ロジックをKernelの1ファイルに集約したシム層を確立しました。フレームワーク各所が単純なメソッド呼び出しを記述するだけでRuby環境の差異を透過的に扱えるようになり、今後のRactor対応コードの追加コストを大幅に下げる基盤が整いました。