Ractor support - #741
Conversation
59af1b7 to
f6977bd
Compare
Replaces the mutable RESERVED_KEYS array with a frozen @reserved_keys class ivar and deprecates the constant.
Freezes fallback instances so they are shareable across Ractors, and avoids writing the class variable on read when no fallbacks are set.
INTERPOLATION_PATTERNS_CACHE) cannot be made shareable, so let's move it to Ractor local storage. Falls back to the original ivar on Rubies without Ractor support.
520aadd to
231a3d4
Compare
b99a69a to
672b299
Compare
Remove the default block Proc from Concurrent::Hash.new so Ractor.make_shareable can freeze the backend. Adds i18n/ractorize.rb to add in hooks to eager load values and make them shareable so they may be referenced on ractors.
Converts config values to class level variables, and loads them eagerly and makes them shareable in i18n/ractorize.rb.
Uses class variables so locale tags may be referenced in ractors. Adds constant to eager_load!
672b299 to
58aa1dc
Compare
|
@radar if you have time to provide some feedback on this, I would greatly appreciate it. Many essential libraries in Ruby are undergoing efforts to be Ractor safe, and it would be very helpful for I18n to do the same. Let me know what you think. |
|
Hey guys! Ran this branch through Audition and its skill in Claude, and verified everything below on Ruby 4.0.6. Two things look weird, according to them: 1. The comment above the reader says reads from non-main Ractors do not trigger isolation violations, but class-variable reads raise require "i18n"
require "i18n/backend/fallbacks"
require "i18n/ractorize"
Ractor.new { I18n.fallbacks[:en] }.value
# the worker raises Ractor::IsolationError: can not access class
# variables from non-main Ractors (@@fallbacks from I18n)
# at fallbacks.rb:22 (Fiber[:i18n_fallbacks] || @@fallbacks)Moving 2. The PR description says it is frozen, and an earlier version of the interpolate changes had the INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS)It is deprecated, but any code still reading it from a non-main Ractor raises, since an unfrozen Regexp is not shareable (verified: the For the record, and probably intentional given the "most of what Rails needs" scope: |
| @@fallbacks ||= I18n::Locale::Fallbacks.new | ||
| if Fiber.respond_to?(:[]) | ||
| current = if Fiber.respond_to?(:[]) | ||
| Fiber[:i18n_fallbacks] || @@fallbacks |
There was a problem hiding this comment.
This still uses the @@fallbacks class variable which can't be Ractor-safe in Ruby 4.0 (even if they will be in 4.1).
Action View reads it on every render because LookupContext's :locale detail calls I18n.fallbacks[I18n.locale] whenever I18n.respond_to?(:fallbacks), and it does once i18n/ractorize runs I18n.eager_load!, which loads this file. So rendering anything from a Ractor worker raises:
Ractor::IsolationError (can not access class variables from non-main Ractors (@@fallbacks from I18n)):
i18n/lib/i18n/backend/fallbacks.rb:22:in 'I18n.fallbacks'
rails/actionview/lib/action_view/lookup_context.rb:48:in 'block in <class:LookupContext>'
Rails has recently started implementing experimental support for Ractors, and I noticed this library needs a few changes to properly be used on non-main Ractors. Here's the changes I made:
I18n.reserved_keysthat uses an instance variable instead ofRESERVED_KEYSand assigns and re-freezes keys.I18n::INTERPOLATION_PATTERNand convertsINTERPOLATION_PATTERNS_CACHEto a instance variable that assigns and re-freezes the cache.I18n.normalized_key_cachethat uses Ractor-local storage when not on the main Ractor.i18n/ractorizeWe may want to look at refactoring other pieces of I18n to freeze + use class variables + eagerly memoize in order to make Ractor safe, but this looks sufficient for most of what Rails needs.