Repository navigation
Conversation
Ruby 4.0 refuses every class variable read from a non-main Ractor, so `Jbuilder.new` raised `Ractor::IsolationError` on `@@key_formatter` in every view of an app that serves requests from Ractors (Rails main's experimental `Rails.application.ractorize!`). Keep the defaults in class instance variables of Jbuilder instead. They are read and written through `::Jbuilder`, so JbuilderTemplate and other subclasses still share them, as they shared the class variables. A non-main Ractor can read a class instance variable once its value is shareable, so the Railtie makes the default KeyFormatter and `JbuilderTemplate.template_lookup_options` shareable on Rails' `before_ractorize` hook, which older Rails never runs. KeyFormatter can now be frozen: it keeps serving cached keys and formats the others without storing them.
mhenrixon
force-pushed
the
ractor-shareable-defaults
branch
from
October 17, 2026 11:20
21a70c5 to
5b078de
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rails main has an experimental
Rails.application.ractorize!that serves requests from non-main Ractors on Ruby 4.0. Every jbuilder view fails there:Jbuilder#initializereads the class-level defaults from class variables, and Ruby 4.0 refuses every class variable read from a non-main Ractor.Ruby master (4.1) already lets non-main Ractors read a class variable that holds a shareable value (see rails/rails#58974). That covers the two booleans but not
@@key_formatter: aKeyFormatterholds a Mutex and a mutable cache, so a Ractor can't read it on any Ruby.Changes
Jbuilder. They are read and written through::Jbuilder, soJbuilderTemplateand other subclasses still share them, as they did with the class variables.Jbuilder.key_format,ignore_nilanddeep_format_keysbehave the same.KeyFormattercan be frozen. A frozen formatter still returns its cached keys and formats the others without storing them.freezedrops the Mutex, because a Mutex can't be shared between Ractors.JbuilderTemplate.template_lookup_optionsshareable on Rails'before_ractorizeload hook. Rails' own railties use the same hook. Older Rails never runs it, so nothing changes withoutractorize!.The process-wide cache from #597/#607 still works as before on the main Ractor. I measured no difference: 200k builds with 5 camelized keys took 0.216s/0.217s on main and 0.199s/0.212s on this branch (Ruby 4.0.7, best of 5). Under
ractorize!the default formatter is frozen, so it formats keys on each call, as it did before #597. A formatter built from a lambda makes the hook raise atractorize!, unless the lambda was made shareable (Ractor.shareable_lambda), so the failure shows up at boot and not on a request.Tests
Jbuilderapply to subclasses, and defaults set on a subclass apply toJbuilder. A template test checks the same forJbuilderTemplate.KeyFormatterstill formats keys, both cached and new.before_ractorizehook makes the default formatter and the lookup options shareable.Jbuilderbuilt in a non-main Ractor picks up the default key format andignore_nil. This test needsRactor#value, so it is skipped below Ruby 4.0.class_variable_set/class_variable_getnow use the setters and the new readers.CI=1 bundle exec rake test: Ruby 4.0.7 + Rails 8.1 gives 143 runs, 0 failures, 0 skips. Ruby 3.1.6 + 7.0, 3.2.8 + 7.1 and 3.4.11 + 8.0 give 0 failures, with the one Ruby 4.0-only skip. Onrails_head(allowed to fail), 6 collection-partial tests injbuilder_template_test.rbfail onmaintoo, against today's Rails main. This branch fails the same 6 and nothing else (checked over 5 seeds).I also checked it in a Rails main app under
ractorize!, withGET /posts.jsonrunning in a non-main Ractor. Before:Ractor::IsolationError … (@@key_formatter from Jbuilder) @ lib/jbuilder.rb:18. After:200both without a default format and withJbuilder.key_format camelize: :lowerplusJbuilder.ignore_nilin an initializer.Conformance case
test/jbuilder_test.rb:840: "defaults can be read from a non-main Ractor once shareable"f141f5e), Ruby 4.0.7: the test itself can't run there (it calls the newJbuilder.default_key_formatter); its Ractor block alone fails withRactor::IsolationError: can not access class variables from non-main Ractors (@@key_formatter from Jbuilder)It builds a Jbuilder inside the Ractor, so it also fails if any default
Jbuilder.newreads goes back into a class variable or isn't shareable. It skips below Ruby 4.0.