Skip to content

Only register the compile cache executor hook without template caching - #2728

Open
mhenrixon wants to merge 1 commit into
ViewComponent:mainfrom
mhenrixon:register-compile-cache-hook-only-without-template-caching
Open

mhenrixon wants to merge 1 commit into
ViewComponent:mainfrom
mhenrixon:register-compile-cache-hook-only-without-template-caching

Conversation

@mhenrixon

@mhenrixon mhenrixon commented Oct 17, 2026 •

Copy link
Copy Markdown

What are you trying to accomplish?

Rails main has an experimental Rails.application.ractorize! that serves requests from non-main Ractors on Ruby 4.0. Once view_component is loaded under it, every request fails, including requests that don't render a component:

Ractor::IsolationError: can not access class variables from non-main Ractors (@@caching from ActionView::Resolver)
  lib/view_component/engine.rb:135:in 'block (2 levels) in <class:Engine>'   # 4.15.0; line 136 on main

The executor hook in engine.rb runs before every request and reads ActionView::Base.cache_template_loading, which is ActionView::Resolver's @@caching class variable. In production that flag is true, so the hook does nothing, but it still reads the flag.

What approach did you choose and why?

The hook is now registered only if template caching is off when Action View loads. Inside ActiveSupport.on_load(:action_view), Rails' own action_view.caching hook has already set the flag, so this sees the app's real setting. Rails' Action View railtie makes the same decision for its view reloader: it registers it only when templates aren't cached.

Development and test don't change. The hook is registered there, and it still checks the flag on each run, so with_template_caching/with_new_cache toggling it at runtime still work. I checked this: if the hook is never registered, the two ..._when_cache_template_loading_is_false integration tests fail.

I considered recomputing the flag from config.action_view.cache_template_loading and reloading_enabled? in after_initialize, the way Rails' railtie does. I didn't, because it copies Rails' defaulting logic, misses apps that set ActionView::Base.cache_template_loading themselves, and leaves an else line the sandbox never runs.

Anything you want to highlight for special attention from reviewers?

  • The new test in test/sandbox/test/initializers/engine_test.rb boots a minimal production app in a subprocess, like the Rakefile's eager_load_check, because the sandbox boots with template caching off. It counts ActionView::Resolver.caching? calls during executor.wrap {}. That count is 1 on main and 0 with this change. Using a subprocess means it doesn't need Ractors and runs on every Ruby in the matrix.
  • Scope: this only stops the per-request failure. Rendering a component in a Ractor still fails. The next error in my app is @__vc_compiler (base.rb:707, a Compiler holding a Mutex), and after that comes CompileCache.cache (compile_cache.rb:7, a mattr_accessor, so a class variable holding a Set). I'm happy to look at those separately.
  • On Rails' side, ActionView::Resolver.caching is a cattr_accessor. On Ruby master (4.1), non-main Ractors can read class variables that hold shareable values (see Make I18n locale switching work in non-main Ractors rails/rails#58974), and on 4.0 Rails may move it off a class variable. This change doesn't depend on either.

Tests:

  • bundle exec rake test: Ruby 4.0.7 with Rails 8.1 and with Rails main gives 653 runs, 0 failures, 0 skips. Ruby 3.2.8 with Rails 7.1 gives 0 failures and 7 skips.
  • rake eager_load_check engine_test spec (4.0.7/8.1) passes.
  • audition . --static-only --plain: ok, 74 baselined, nothing new.

I also checked it in a Rails main app under ractorize!, with requests running in a non-main Ractor. Before: GET /posts failed with the error above. After: GET /posts -> 200 and GET /stack/plain-helpers -> 200.

The executor hook that clears the compile cache before each request did
nothing when Action View caches templates, as in production, but it
still read `ActionView::Base.cache_template_loading` on every request.
That reads `ActionView::Resolver`'s `@@caching` class variable, which
non-main Ractors can't read on Ruby 4.0, so under Rails' experimental
`Rails.application.ractorize!` every request failed once view_component
was loaded.

Decide once, when Action View loads, whether to register the hook. Rails'
Action View railtie likewise registers its view reloader only when
template caching is off. The hook still checks the flag on each run, so
toggling it at runtime, with caching off at boot, works as before.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant