ExecutionWrapper の active_key を継承フックで確実に設定
ExecutionWrapper のサブクラスが Ractor 環境でクラスインスタンス変数を設定できない制約を回避するため、ロード時に active_key を自動割り当てする変更が加えられました。この変更により、最初のリクエストが Ractor 上で処理されても IsolatedExecutionState が期待するキーが常に存在し、隔離エラーが防止されます。
背景
Ractor では、メインスレッド以外からクラスインスタンス変数への代入が禁止されており、ExecutionWrapper のサブクラスがロードされた直後に @active_key が未設定だと IsolatedExecutionState.key?(active_key) が失敗します。その結果、最初のリクエストが Ractor 上で処理される際に、期待した実行コンテキストが取得できず例外が発生していました。従来は active_key を遅延初期化するメソッドが呼び出されるまでキーが生成されなかったため、Ractor の隔離性と相容れない状態になっていました。
この問題を根本的に解決するには、サブクラスが ロード時 に必ずキーを設定できる仕組みが必要です。そこで PR は ExecutionWrapper に inherited フック を導入し、サブクラスが継承された瞬間に @active_key を初期化するロジックを追加しました。これにより、Ractor の制約下でも安全にキーが確保され、実行コンテキスト管理が安定します。
技術的な変更
変更点は主に三箇所です。まずクラスメソッド部に attr_reader :active_key を追加し、外部から読み取り専用で取得できるようにしました。次に self.inherited フックを実装し、サブクラスが生成されるたびに @active_key を :"active_execution_wrapper_#{object_id}" 形式で設定します。最後に従来の遅延初期化メソッド self.active_key を削除し、キー生成ロジックをフックに集約しました。
変更前:
def self.active_key # :nodoc:
@active_key ||= :"active_execution_wrapper_#{object_id}"
end
変更後:
class << self
attr_reader :active_key # :nodoc:
end
def self.inherited(subclass)
subclass.class_eval { @active_key = :"active_execution_wrapper_#{object_id}" }
end
これらの変更により、ExecutionWrapper.active? が参照する active_key は常に事前に設定された状態となり、IsolatedExecutionState.key?(active_key) の判定が正しく機能します。コードベースへの影響は active_key の取得方法が内部実装に限定されるため、公開 API の変更はありません。
設計判断
遅延初期化から継承時初期化への方針転換 が本 PR の核心です。Ractor の制約を考慮すると、実行時にクラスインスタンス変数を設定できないため、最初のアクセス時にキーを生成する方式は安全ではありません。inherited フックを利用してロード時にキーを確定させることで、クラス状態が常に一貫していることを保証し、後続ロジックの簡潔化を実現しました。さらに attr_reader に置き換えることで、内部で余計な再評価や条件分岐が除去され、コードの可読性が向上しています。
この設計は既存のサブクラスや外部コードに対して後方互換性を保ちつつ、Ractor 環境への適応を可能にする点で評価できます。キー生成の責務をクラス定義時に明示化したことにより、実行時エラーのリスクが根本的に除去されました。
まとめ
ExecutionWrapper のサブクラスが 継承時に active_key を自動設定 するようになったことで、Ractor 環境下でのクラスインスタンス変数操作エラーが防止されます。この変更は遅延初期化からロード時初期化へのシフトという設計判断に基づき、既存 API を維持しつつ実行コンテキストの信頼性を高めました。