`class_attribute`をRactor対応に再設計、クロージャからivarへ

rails/rails

ActiveSupport::ClassAttributeの実装が、クロージャベースからインスタンス変数ベースへと再設計され、Ractor安全な動作を実現しました。パフォーマンスへの影響は10%未満に抑えられています。

背景

従来のclass_attributeは、値をクロージャに閉じ込めて管理するアプローチを採っており、これがRactorとの根本的な非互換の原因でした。Ractorはオブジェクトの共有に厳しい制約を課すため、クロージャが参照するオブジェクトがshareable(共有可能)でない限り、複数のRactorから安全にアクセスできませんでした。

また、このPRはバグの修正も含んでいます。シングルトンクラスへの代入時に、instance_reader: falseが指定されていてもinstance_readerが無条件に定義されてしまう問題がありました。新しいテストケース「disabling instance reader is not bypassed by assigning on the singleton class」がその修正を保証します。

技術的な変更

実装の核心は、「値を保持するメソッド」を「オーナーを返すメソッド」と「値を読むattr_reader」に分割したことです。

変更前は、__class_attr__nameという1つのプライベートメソッドがクロージャ内の変数として値を保持していました。値が変更されるたびにdefine_methodでメソッド自体を再定義し、新しいクロージャに値を閉じ込めていました。

変更後は、責務を2つに分離しています:

  • __class_attr_#{name}_owner: 値の「オーナー」となるクラスを返すメソッド。クラス自体はRactor間で共有可能なため、Ractor.shareable_procでwrapできる
  • __class_attr_#{name}: attr_readerとして定義され、オーナークラスのインスタンス変数@__class_attr_#{name}から値を読む
# 変更後のメソッド定義
reader_method = :"__class_attr_#{name}"
owner_method = :"__class_attr_#{name}_owner"

singleton_class.attr_reader(reader_method)

class_methods << "def #{name}; #{owner_method}.#{reader_method}; end"
class_methods << <<~RUBY
  def #{name}=(value)
    if #{owner_method}.equal?(self)
      @#{reader_method} = value
    else
      ::ActiveSupport::ClassAttribute.redefine(self, :#{name}, :#{owner_method}, :#{reader_method}, value, #{!!instance_reader})
    end
  end
RUBY

redefineメソッド内では、値をインスタンス変数にセットし、オーナーを示すprocをRactor.shareable_procでwrapします。

def redefine(owner, name, owner_method, reader_method, value, instance_reader)
  ivar_name = :"@#{reader_method}"
  owner.instance_variable_set(ivar_name, value)

  owner_proc =
    if defined?(Ractor.shareable_proc)
      Ractor.shareable_proc { owner }
    else
      -> { owner }
    end

  redefine_method(owner.singleton_class, owner_method, private: true, &owner_proc)
end

読み取り時のフローはクラスメソッド → owner_method → attr_readerとなります。Base.settingを呼ぶとBase.__class_attr_setting_owner(= Base)を取得し、そのクラスの__class_attr_setting attr_readerでインスタンス変数を読みます。継承したサブクラスが値を上書きしていない場合はowner_methodが親クラスを返し、親クラスのivarが参照されます。

ActiveSupport::Callbacksの最適化ハックも対応して更新されています。set_callbacksが値の変更を検出する際にチェックするメソッド名が、__class_attr__callbacksから__class_attr__callbacks_ownerに変更されました。

設計判断

「オーナークラスのivar」を値の保管場所とする設計が、Ractor安全性の鍵です。Ractorはivarへのアクセスに対して明確なセマンティクスを持ちます。値がshareable(例:freeze済みオブジェクト)であれば全Ractorから読め、そうでなければメインRactorのみが読めるという自然な制約が適用されます。これは再定義で回避するよりも堅牢な境界です。

Ractor.shareable_procが利用可能かどうかで分岐している点も見逃せません。この分岐により、Ractorをサポートしないルビーバージョンとの後方互換性を維持しつつ、サポートする環境では最適化されたprocを使う設計になっています。

シングルトンクラスへの代入時のinstance_reader修正については、owner.singleton_class? && !owner.attached_object.is_a?(Module) && instance_readerという条件で、インスタンスがModuleでない(つまり通常オブジェクトのシングルトンクラスである)場合にのみself.singleton_class.reader_methodを参照するインスタンスメソッドを再定義する処理が追加されました。これにより従来のバグが解消されています。

まとめ

この変更は、クロージャによる値の閉じ込めという従来の実装を、クラスのivarを権威ある保管場所とするアーキテクチャへと転換しました。Ractor安全性をパフォーマンスをほぼ維持したまま獲得しており、RubyのRactor普及に向けたActive Supportの対応を前進させる変更です。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
3cc7e9b7

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「総論→各論→結論」の構成が記事全体で明確に適用されています。リード文、背景、技術詳細、設計判断、まとめの各要素が過不足なく配置されており、非常に分かりやすい構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのシンタックスハイライト(```言語:ファイルパス)や、PR番号のリンク記法([PR #57317](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Ractor、クロージャ、ivarなど専門的な用語を前提としており、Railsの内部実装に関心のあるエンジニアという対象読者に完全に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論から始まり、各パラグラフがトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が遵守されています。これにより、記事の要点を素早く把握できます。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードは、提供されたDiffの内容を正確に反映しています。メソッドの分割や`redefine`メソッドのロジックなど、重要な変更点が正しく抽出されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`Ractor-safe`, `shareable`, `ivar`, `singleton_class`などの技術用語が、PRの情報と一致しており、文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

クロージャベースからインスタンス変数ベースへの変更理由、Ractor安全性を実現する仕組み、バグ修正の内容など、すべての技術的な説明がPR情報とDiffによって裏付けられており、正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

パフォーマンスへの影響(10%未満)、バグ修正の存在、実装方針の変更など、記事内のすべての主張がPRのDescriptionやDiffの内容に基づいており、ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#57317)やパフォーマンスに関する数値(10%未満)が正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRの主題「Ractor-safe class attribute implementation」を的確に要約し、変更の核心(クロージャからivarへ)を明確に伝えています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事は提供されたPR情報に忠実であり、サポート状況やリリース予定など、PRに記載のない外部知識を持ち込んでいません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「従来の」「変更後は」といった時間や前後関係を示す表現が正確に使われており、読者の誤解を招くような歪曲はありません。