mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
f8cbd3d5bd
Previously, if cache_classes was true we would build a file_watcher and add it to reloaders though it would never be used. This wasted some memory for FileUpdateChecker to maintain an array of all watched files, and added slightly to boot time. Instead we should avoid building a file watcher it when cache_classes is true.
252 lines
9 KiB
Ruby
252 lines
9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "active_support/core_ext/string/inflections"
|
|
require "active_support/core_ext/array/conversions"
|
|
|
|
module Rails
|
|
class Application
|
|
module Finisher
|
|
include Initializable
|
|
|
|
initializer :add_generator_templates do
|
|
config.generators.templates.unshift(*paths["lib/templates"].existent)
|
|
end
|
|
|
|
initializer :ensure_autoload_once_paths_as_subset do
|
|
extra = ActiveSupport::Dependencies.autoload_once_paths -
|
|
ActiveSupport::Dependencies.autoload_paths
|
|
|
|
unless extra.empty?
|
|
abort <<-end_error
|
|
autoload_once_paths must be a subset of the autoload_paths.
|
|
Extra items in autoload_once_paths: #{extra * ','}
|
|
end_error
|
|
end
|
|
end
|
|
|
|
# This will become an error if/when we remove classic mode. The plan is
|
|
# autoloaders won't be configured up to this point in the finisher, so
|
|
# constants just won't be found, raising regular NameError exceptions.
|
|
initializer :warn_if_autoloaded, before: :let_zeitwerk_take_over do
|
|
next if config.cache_classes
|
|
next if ActiveSupport::Dependencies.autoloaded_constants.empty?
|
|
|
|
autoloaded = ActiveSupport::Dependencies.autoloaded_constants
|
|
constants = "constant".pluralize(autoloaded.size)
|
|
enum = autoloaded.to_sentence
|
|
have = autoloaded.size == 1 ? "has" : "have"
|
|
these = autoloaded.size == 1 ? "This" : "These"
|
|
example = autoloaded.first
|
|
example_klass = example.constantize.class
|
|
|
|
if config.autoloader == :zeitwerk
|
|
ActiveSupport::DescendantsTracker.clear
|
|
ActiveSupport::Dependencies.clear
|
|
|
|
unload_message = "#{these} autoloaded #{constants} #{have} been unloaded."
|
|
else
|
|
unload_message = "`config.autoloader` is set to `#{config.autoloader}`. #{these} autoloaded #{constants} would have been unloaded if `config.autoloader` had been set to `:zeitwerk`."
|
|
end
|
|
|
|
ActiveSupport::Deprecation.warn(<<~WARNING)
|
|
Initialization autoloaded the #{constants} #{enum}.
|
|
|
|
Being able to do this is deprecated. Autoloading during initialization is going
|
|
to be an error condition in future versions of Rails.
|
|
|
|
Reloading does not reboot the application, and therefore code executed during
|
|
initialization does not run again. So, if you reload #{example}, for example,
|
|
the expected changes won't be reflected in that stale #{example_klass} object.
|
|
|
|
#{unload_message}
|
|
|
|
Please, check the "Autoloading and Reloading Constants" guide for solutions.
|
|
WARNING
|
|
end
|
|
|
|
initializer :let_zeitwerk_take_over do
|
|
if config.autoloader == :zeitwerk
|
|
require "active_support/dependencies/zeitwerk_integration"
|
|
ActiveSupport::Dependencies::ZeitwerkIntegration.take_over(enable_reloading: !config.cache_classes)
|
|
end
|
|
end
|
|
|
|
initializer :add_builtin_route do |app|
|
|
if Rails.env.development?
|
|
app.routes.prepend do
|
|
get "/rails/info/properties" => "rails/info#properties", internal: true
|
|
get "/rails/info/routes" => "rails/info#routes", internal: true
|
|
get "/rails/info" => "rails/info#index", internal: true
|
|
end
|
|
|
|
app.routes.append do
|
|
get "/" => "rails/welcome#index", internal: true
|
|
end
|
|
end
|
|
end
|
|
|
|
# Setup default session store if not already set in config/application.rb
|
|
initializer :setup_default_session_store, before: :build_middleware_stack do |app|
|
|
unless app.config.session_store?
|
|
app_name = app.class.name ? app.railtie_name.chomp("_application") : ""
|
|
app.config.session_store :cookie_store, key: "_#{app_name}_session"
|
|
end
|
|
end
|
|
|
|
initializer :build_middleware_stack do
|
|
build_middleware_stack
|
|
end
|
|
|
|
initializer :define_main_app_helper do |app|
|
|
app.routes.define_mounted_helper(:main_app)
|
|
end
|
|
|
|
initializer :add_to_prepare_blocks do |app|
|
|
config.to_prepare_blocks.each do |block|
|
|
app.reloader.to_prepare(&block)
|
|
end
|
|
end
|
|
|
|
# This needs to happen before eager load so it happens
|
|
# in exactly the same point regardless of config.eager_load
|
|
initializer :run_prepare_callbacks do |app|
|
|
app.reloader.prepare!
|
|
end
|
|
|
|
initializer :eager_load! do
|
|
if config.eager_load
|
|
ActiveSupport.run_load_hooks(:before_eager_load, self)
|
|
# Checks defined?(Zeitwerk) instead of zeitwerk_enabled? because we
|
|
# want to eager load any dependency managed by Zeitwerk regardless of
|
|
# the autoloading mode of the application.
|
|
Zeitwerk::Loader.eager_load_all if defined?(Zeitwerk)
|
|
config.eager_load_namespaces.each(&:eager_load!)
|
|
end
|
|
end
|
|
|
|
# All initialization is done, including eager loading in production
|
|
initializer :finisher_hook do
|
|
ActiveSupport.run_load_hooks(:after_initialize, self)
|
|
end
|
|
|
|
class MutexHook
|
|
def initialize(mutex = Mutex.new)
|
|
@mutex = mutex
|
|
end
|
|
|
|
def run
|
|
@mutex.lock
|
|
end
|
|
|
|
def complete(_state)
|
|
@mutex.unlock
|
|
end
|
|
end
|
|
|
|
module InterlockHook
|
|
def self.run
|
|
ActiveSupport::Dependencies.interlock.start_running
|
|
end
|
|
|
|
def self.complete(_state)
|
|
ActiveSupport::Dependencies.interlock.done_running
|
|
end
|
|
end
|
|
|
|
initializer :configure_executor_for_concurrency do |app|
|
|
if config.allow_concurrency == false
|
|
# User has explicitly opted out of concurrent request
|
|
# handling: presumably their code is not threadsafe
|
|
|
|
app.executor.register_hook(MutexHook.new, outer: true)
|
|
|
|
elsif config.allow_concurrency == :unsafe
|
|
# Do nothing, even if we know this is dangerous. This is the
|
|
# historical behavior for true.
|
|
|
|
else
|
|
# Default concurrency setting: enabled, but safe
|
|
|
|
unless config.cache_classes && config.eager_load
|
|
# Without cache_classes + eager_load, the load interlock
|
|
# is required for proper operation
|
|
|
|
app.executor.register_hook(InterlockHook, outer: true)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Set routes reload after the finisher hook to ensure routes added in
|
|
# the hook are taken into account.
|
|
initializer :set_routes_reloader_hook do |app|
|
|
reloader = routes_reloader
|
|
reloader.eager_load = app.config.eager_load
|
|
reloader.execute
|
|
reloaders << reloader
|
|
app.reloader.to_run do
|
|
# We configure #execute rather than #execute_if_updated because if
|
|
# autoloaded constants are cleared we need to reload routes also in
|
|
# case any was used there, as in
|
|
#
|
|
# mount MailPreview => 'mail_view'
|
|
#
|
|
# This means routes are also reloaded if i18n is updated, which
|
|
# might not be necessary, but in order to be more precise we need
|
|
# some sort of reloaders dependency support, to be added.
|
|
require_unload_lock!
|
|
reloader.execute
|
|
end
|
|
end
|
|
|
|
# Set clearing dependencies after the finisher hook to ensure paths
|
|
# added in the hook are taken into account.
|
|
initializer :set_clear_dependencies_hook, group: :all do |app|
|
|
callback = lambda do
|
|
ActiveSupport::DescendantsTracker.clear
|
|
ActiveSupport::Dependencies.clear
|
|
end
|
|
|
|
if config.cache_classes
|
|
app.reloader.check = lambda { false }
|
|
elsif config.reload_classes_only_on_change
|
|
app.reloader.check = lambda do
|
|
app.reloaders.map(&:updated?).any?
|
|
end
|
|
else
|
|
app.reloader.check = lambda { true }
|
|
end
|
|
|
|
if config.cache_classes
|
|
# No reloader
|
|
elsif config.reload_classes_only_on_change
|
|
reloader = config.file_watcher.new(*watchable_args, &callback)
|
|
reloaders << reloader
|
|
|
|
# Prepend this callback to have autoloaded constants cleared before
|
|
# any other possible reloading, in case they need to autoload fresh
|
|
# constants.
|
|
app.reloader.to_run(prepend: true) do
|
|
# In addition to changes detected by the file watcher, if routes
|
|
# or i18n have been updated we also need to clear constants,
|
|
# that's why we run #execute rather than #execute_if_updated, this
|
|
# callback has to clear autoloaded constants after any update.
|
|
class_unload! do
|
|
reloader.execute
|
|
end
|
|
end
|
|
else
|
|
app.reloader.to_complete do
|
|
class_unload!(&callback)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Disable dependency loading during request cycle
|
|
initializer :disable_dependency_loading do
|
|
if config.eager_load && config.cache_classes && !config.enable_dependency_loading
|
|
ActiveSupport::Dependencies.unhook!
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|