Merge pull request #43080 from rails/del-as-clear

Final touches deleting the classic autoloader
This commit is contained in:
Xavier Noria 2021-08-23 18:04:16 +02:00 committed by GitHub
commit 891189d9b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 34 additions and 114 deletions

View File

@ -8,11 +8,6 @@ module ActiveSupport # :nodoc:
module Dependencies # :nodoc:
require_relative "dependencies/require_dependency"
extend self
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
private_constant :UNBOUND_METHOD_MODULE_NAME
mattr_accessor :interlock, default: Interlock.new
# :doc:
@ -20,29 +15,25 @@ module ActiveSupport # :nodoc:
# Execute the supplied block without interference from any
# concurrent loads.
def self.run_interlock
Dependencies.interlock.running { yield }
interlock.running { yield }
end
# Execute the supplied block while holding an exclusive lock,
# preventing any other thread from being inside a #run_interlock
# block at the same time.
def self.load_interlock
Dependencies.interlock.loading { yield }
interlock.loading { yield }
end
# Execute the supplied block while holding an exclusive lock,
# preventing any other thread from being inside a #run_interlock
# block at the same time.
def self.unload_interlock
Dependencies.interlock.unloading { yield }
interlock.unloading { yield }
end
# :nodoc:
def eager_load?(path)
Dependencies._eager_load_paths.member?(path)
end
# The set of directories from which we may automatically load files. Files
# under these directories will be reloaded on each request in development mode,
# unless the directory also appears in autoload_once_paths.
@ -62,23 +53,33 @@ module ActiveSupport # :nodoc:
# main autoloader. Used to clear state.
mattr_accessor :_autoloaded_tracked_classes, default: Set.new
# An array of qualified constant names that have been loaded. Adding a name
# to this array will cause it to be unloaded the next time Dependencies are
# cleared.
mattr_accessor :autoloaded_constants, default: []
def clear
# Private method that reloads constants autoloaded by the main autoloader.
#
# Rails.application.reloader.reload! is the public interface for application
# reload. That involves more things, like deleting unloaded classes from the
# internal state of the descendants tracker, or reloading routes.
def self.clear
unload_interlock do
_autoloaded_tracked_classes.clear
Rails.autoloaders.main.reload
rescue Zeitwerk::ReloadingDisabledError
raise "reloading is disabled because config.cache_classes is true"
end
end
# Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
path_suffix += ".rb" unless path_suffix.end_with?(".rb")
autoload_paths.each do |root|
path = File.join(root, path_suffix)
return path if File.file? path
# Private method used by require_dependency.
def self.search_for_file(relpath)
relpath += ".rb" unless relpath.end_with?(".rb")
autoload_paths.each do |autoload_path|
abspath = File.join(autoload_path, relpath)
return abspath if File.file?(abspath)
end
nil # Gee, I sure wish we had first_match ;-)
nil
end
# Private method that helps configuring the autoloaders.
def self.eager_load?(path)
_eager_load_paths.member?(path)
end
end
end

View File

@ -6,26 +6,6 @@ require "zeitwerk"
module ActiveSupport
module Dependencies
module ZeitwerkIntegration # :nodoc: all
module Decorations
def clear
Dependencies.unload_interlock do
_autoloaded_tracked_classes.clear
Rails.autoloaders.main.reload
rescue Zeitwerk::ReloadingDisabledError
raise "reloading is disabled because config.cache_classes is true"
end
end
def autoloaded_constants
Rails.autoloaders.main.unloadable_cpaths
end
def verbose=(verbose)
l = verbose ? logger || Rails.logger : nil
Rails.autoloaders.each { |autoloader| autoloader.logger = l }
end
end
module Inflector
# Concurrent::Map is not needed. This is a private class, and overrides
# must be defined while the application boots.
@ -39,10 +19,6 @@ module ActiveSupport
@overrides.merge!(overrides)
end
end
def self.take_over
Dependencies.singleton_class.prepend(Decorations)
end
end
end
end

View File

@ -16,26 +16,6 @@ module ModuleWithConstant
InheritedConstant = "Hello"
end
class DependenciesTest < ActiveSupport::TestCase
setup do
@loaded_features_copy = $LOADED_FEATURES.dup
$LOAD_PATH << "test"
end
teardown do
ActiveSupport::Dependencies.clear
$LOADED_FEATURES.replace(@loaded_features_copy)
$LOAD_PATH.pop
end
def test_smart_name_error_strings
e = assert_raise NameError do
Object.module_eval "ImaginaryObject"
end
assert_includes "uninitialized constant ImaginaryObject", e.message
end
end
class RequireDependencyTest < ActiveSupport::TestCase
setup do
@loaded_features_copy = $LOADED_FEATURES.dup

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true
require "zeitwerk"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/array/conversions"
require "active_support/descendants_tracker"
require "active_support/dependencies"
require "zeitwerk"
module Rails
class Application
@ -44,11 +44,6 @@ module Rails
autoloader.setup
end
initializer :let_zeitwerk_take_over do
require "active_support/dependencies/zeitwerk_integration"
ActiveSupport::Dependencies::ZeitwerkIntegration.take_over
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?
@ -179,6 +174,7 @@ module Rails
# added in the hook are taken into account.
initializer :set_clear_dependencies_hook, group: :all do |app|
callback = lambda do
# Order matters.
ActiveSupport::DescendantsTracker.clear(
only: ActiveSupport::Dependencies._autoloaded_tracked_classes
)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/dependencies/zeitwerk_integration"
require "zeitwerk"
require "active_support/dependencies/zeitwerk_integration"
module Rails
module Autoloaders # :nodoc:

View File

@ -440,6 +440,7 @@ module ApplicationTests
end
test "db:migrate set back connection to its original state" do
require "#{app_path}/config/environment"
Dir.chdir(app_path) do
dummy_task = <<~RUBY
task foo: :environment do
@ -457,6 +458,7 @@ module ApplicationTests
end
test "db:migrate:name sets the connection back to its original state" do
require "#{app_path}/config/environment"
Dir.chdir(app_path) do
dummy_task = <<~RUBY
task foo: :environment do
@ -1088,6 +1090,7 @@ module ApplicationTests
end
test "when database_tasks is false, then do not run the database tasks on that db" do
require "#{app_path}/config/environment"
app_file "config/database.yml", <<-YAML
development:
primary:

View File

@ -23,14 +23,9 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
ActiveSupport::Dependencies
end
def decorated?
deps.singleton_class < deps::ZeitwerkIntegration::Decorations
end
test "ActiveSupport::Dependencies is decorated" do
test "The integration is minimally looking good" do
boot
assert decorated?
assert Rails.autoloaders.zeitwerk_enabled?
assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main
assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once
@ -111,37 +106,6 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
assert Object.const_defined?(:MoneySerializer)
end
test "unloadable constants (main)" do
app_file "app/models/user.rb", "class User; end"
app_file "app/models/post.rb", "class Post; end"
boot
assert Post
assert_equal ["Post"], deps.autoloaded_constants
end
test "unloadable constants (once)" do
add_to_config 'config.autoload_once_paths << "#{Rails.root}/extras"'
app_file "extras/foo.rb", "class Foo; end"
app_file "extras/bar.rb", "class Bar; end"
boot
assert Foo
assert_empty deps.autoloaded_constants
end
test "unloadable constants (reloading disabled)" do
app_file "app/models/user.rb", "class User; end"
app_file "app/models/post.rb", "class Post; end"
boot("production")
assert Post
assert_empty deps.autoloaded_constants
end
test "eager loading loads the application code" do
$zeitwerk_integration_test_user = false
$zeitwerk_integration_test_post = false