From 964e1a7f76f08339b3d127430ef91baae1434735 Mon Sep 17 00:00:00 2001 From: Hugo Vacher Date: Wed, 29 Apr 2026 16:52:51 -0400 Subject: [PATCH] Eagerly preload framework base classes After `config/environment` is loaded, eagerly autoload framework base classes (`ActionMailer::Base`, `ActionController::Base`, `ActionController::API`) so their `ActiveSupport.on_load` hooks fire in the parent process. Those classes shouldn't change and sometimes have side effects when loaded, although the biggest one is getting fixed in Rails (see https://github.com/rails/rails/pull/57269), it won't hurt to still preload those classes so they don't get loaded on each fork again. --- CHANGELOG.md | 11 ++++++++++- lib/spring/application.rb | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b2e7d49..bf3f3c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ -## Next Release +## Unreleased * Fixed crashes when a client disconnects mid-handshake (e.g. on connect timeout). Previously, `Errno::EPIPE` raised in `Spring::Server#serve` or `Spring::Application#serve` would propagate up through the accept loop and kill the process, leaving a stale socket that broke every subsequent client. Both crash sites are now rescued, including writes that happen inside the `rescue Exception` handler in `Application#serve` while reporting an earlier failure to the gone client. +* Eagerly autoload framework base classes (`ActionMailer::Base`, + `ActionController::Base`, `ActionController::API`) at the end of preload + so their `ActiveSupport.on_load` hooks fire in the parent process. + Without this, the reloader probe in `#serve` materializes Rails + internals (notably ActionView's `CacheExpiry::ViewReloader`) in a + half-initialized state and triggers an expensive `FileUpdateChecker` + rebuild on every `prepend_view_path` inside each fork. See + rails/rails#51308 for the lazy-init contract this aligns with. + ## 4.4.0 * Revert the removal of UTF-8 force encoding in JSON loading. diff --git a/lib/spring/application.rb b/lib/spring/application.rb index 3bb25631..2e0c7f46 100644 --- a/lib/spring/application.rb +++ b/lib/spring/application.rb @@ -117,6 +117,7 @@ def preload require Spring.application_root_path.join("config", "environment") invoke_after_environment_load_callbacks + preload_framework_base_classes disconnect_database @@ -144,6 +145,19 @@ def preload end end + # Eagerly autoload framework base classes + FRAMEWORK_BASE_CLASSES = %w[ + ActionMailer::Base + ActionController::Base + ActionController::API + ].freeze + + def preload_framework_base_classes + FRAMEWORK_BASE_CLASSES.each do |const| + Object.const_get(const) if Object.const_defined?(const) + end + end + def eager_preload with_pty do # we can't see stderr and there could be issues when it's overflown