Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only run load_subclasses after app is initialized #22904

Merged

Conversation

jrafanie
Copy link
Member

In rails 6.1, nothing was calling descendants or subclasses during the app initialization.

Rails changed in 7.0 to call subclasses from reload_schema_from_cache here: rails/rails@6f30cc0

It also changed to call descendants on the callback class (self) insead of the ActiveSupport::DescendantsTracker here:
rails/rails@ffae3bd

We are not expecting to be called from these locations.

We can make this rails 7 compatible by ensuring the descendant loader loading of subclasses until after the app is booted, which was the implicit behavior previously.

(Extracted from #22873)

In rails 6.1, nothing was calling descendants or subclasses during the
app initialization.

Rails changed in 7.0 to call subclasses from reload_schema_from_cache here:
rails/rails@6f30cc0

It also changed to call descendants on the callback class (self) insead of
the ActiveSupport::DescendantsTracker here:
rails/rails@ffae3bd

We are not expecting to be called from these locations.

We can make this rails 7 compatible by ensuring the descendant loader loading
of subclasses until after the app is booted, which was the implicit behavior
previously.
@Fryguy Fryguy added the rails7 label Feb 21, 2024
@jrafanie
Copy link
Member Author

jrafanie commented Feb 21, 2024

FYI, to demonstrate rails 6.1 didn't hit this code path during rails boot:

diff --git a/lib/extensions/descendant_loader.rb b/lib/extensions/descendant_loader.rb
index 488ab2500f..09455b37f3 100644
--- a/lib/extensions/descendant_loader.rb
+++ b/lib/extensions/descendant_loader.rb
@@ -263,6 +263,7 @@ class DescendantLoader

   module ArDescendantsWithLoader
     def descendants
+      raise if !defined? @loaded_descendants
       if Vmdb::Application.instance.initialized? && !defined? @loaded_descendants
         @loaded_descendants = true
         DescendantLoader.instance.load_subclasses(self)
@@ -275,6 +276,7 @@ class DescendantLoader
     # https://github.com/rails/rails/commit/8f8aa857e084b76b1120edaa9bb9ce03ba1e6a19
     # We need to get in front of it, like we do for descendants.
     def subclasses
+      raise if !defined? @loaded_descendants
       if Vmdb::Application.instance.initialized? && !defined? @loaded_descendants
         @loaded_descendants = true
         DescendantLoader.instance.load_subclasses(self)

It loads in console and rails runner:

joerafaniello@Joes-MBP manageiq % bin/rails c
** ManageIQ master, codename: Radjabov
Loading development environment (Rails 6.1.7.7)
irb(main):001:0> exit
joerafaniello@Joes-MBP manageiq % bin/rails r ''
** ManageIQ master, codename: Radjabov

As soon as I try to load descendants after app initialization, my hack to raise shows we hit this code path.

In rails 7, this is hit during application boot.

joerafaniello@Joes-MBP manageiq % bin/rails r 'Vm.descendants'
** ManageIQ master, codename: Radjabov
/Users/joerafaniello/Code/manageiq/lib/extensions/descendant_loader.rb:266:in `descendants': unhandled exception
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-6.1.7.7/lib/rails/commands/runner/runner_command.rb:45:in `<main>'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-6.1.7.7/lib/rails/commands/runner/runner_command.rb:45:in `eval'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-6.1.7.7/lib/rails/commands/runner/runner_command.rb:45:in `perform'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor/command.rb:28:in `run'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor/invocation.rb:127:in `invoke_command'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor.rb:527:in `dispatch'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-6.1.7.7/lib/rails/command/base.rb:69:in `perform'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-6.1.7.7/lib/rails/command.rb:48:in `invoke'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-6.1.7.7/lib/rails/commands.rb:18:in `<main>'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
	from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
	from bin/rails:4:in `<main>'

Here's what it looks like without even loading descendants on rails 7:

diff --git a/lib/extensions/descendant_loader.rb b/lib/extensions/descendant_loader.rb
index 488ab2500f..09455b37f3 100644
--- a/lib/extensions/descendant_loader.rb
+++ b/lib/extensions/descendant_loader.rb
@@ -263,6 +263,7 @@ class DescendantLoader

   module ArDescendantsWithLoader
     def descendants
+      raise if !defined? @loaded_descendants
       if Vmdb::Application.instance.initialized? && !defined? @loaded_descendants
         @loaded_descendants = true
         DescendantLoader.instance.load_subclasses(self)
@@ -275,6 +276,7 @@ class DescendantLoader
     # https://github.com/rails/rails/commit/8f8aa857e084b76b1120edaa9bb9ce03ba1e6a19
     # We need to get in front of it, like we do for descendants.
     def subclasses
+      raise if !defined? @loaded_descendants
       if Vmdb::Application.instance.initialized? && !defined? @loaded_descendants
         @loaded_descendants = true
         DescendantLoader.instance.load_subclasses(self)
joerafaniello@Joes-MBP manageiq % bin/rails c
/Users/joerafaniello/Code/manageiq/lib/extensions/descendant_loader.rb:279:in `subclasses': unhandled exception
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/descendants_tracker.rb:83:in `subclasses'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/descendants_tracker.rb:89:in `descendants'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/callbacks.rb:706:in `__update_callbacks'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/callbacks.rb:764:in `set_callback'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/reloader.rb:39:in `before_class_unload'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/railtie.rb:40:in `block in <class:Railtie>'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:32:in `instance_exec'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:32:in `run'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:61:in `block in run_initializers'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:228:in `block in tsort_each'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:431:in `each_strongly_connected_component_from'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:349:in `block in each_strongly_connected_component'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `each'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `call'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `each_strongly_connected_component'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:226:in `tsort_each'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:205:in `tsort_each'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:60:in `run_initializers'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/application.rb:372:in `initialize!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/railtie.rb:226:in `public_send'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/railtie.rb:226:in `method_missing'
        from /Users/joerafaniello/Code/manageiq/config/environment.rb:5:in `<main>'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/zeitwerk-2.6.13/lib/zeitwerk/kernel.rb:34:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/application.rb:348:in `require_environment!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command/actions.rb:28:in `require_environment!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command/actions.rb:15:in `require_application_and_environment!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/commands/console/console_command.rb:105:in `perform'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor/command.rb:28:in `run'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor/invocation.rb:127:in `invoke_command'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor.rb:527:in `dispatch'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command/base.rb:87:in `perform'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command.rb:48:in `invoke'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/commands.rb:18:in `<main>'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from bin/rails:4:in `<main>'
joerafaniello@Joes-MBP manageiq %
joerafaniello@Joes-MBP manageiq %
joerafaniello@Joes-MBP manageiq % bin/rails r ''
/Users/joerafaniello/Code/manageiq/lib/extensions/descendant_loader.rb:279:in `subclasses': unhandled exception
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/descendants_tracker.rb:83:in `subclasses'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/descendants_tracker.rb:89:in `descendants'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/callbacks.rb:706:in `__update_callbacks'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/callbacks.rb:764:in `set_callback'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/reloader.rb:39:in `before_class_unload'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/activesupport-7.0.8.1/lib/active_support/railtie.rb:40:in `block in <class:Railtie>'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:32:in `instance_exec'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:32:in `run'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:61:in `block in run_initializers'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:228:in `block in tsort_each'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:431:in `each_strongly_connected_component_from'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:349:in `block in each_strongly_connected_component'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `each'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `call'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:347:in `each_strongly_connected_component'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:226:in `tsort_each'
        from /Users/joerafaniello/.rubies/ruby-3.1.4/lib/ruby/3.1.0/tsort.rb:205:in `tsort_each'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/initializable.rb:60:in `run_initializers'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/application.rb:372:in `initialize!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/railtie.rb:226:in `public_send'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/railtie.rb:226:in `method_missing'
        from /Users/joerafaniello/Code/manageiq/config/environment.rb:5:in `<main>'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/zeitwerk-2.6.13/lib/zeitwerk/kernel.rb:34:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/application.rb:348:in `require_environment!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command/actions.rb:28:in `require_environment!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command/actions.rb:15:in `require_application_and_environment!'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/commands/runner/runner_command.rb:33:in `perform'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor/command.rb:28:in `run'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor/invocation.rb:127:in `invoke_command'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/thor-1.3.0/lib/thor.rb:527:in `dispatch'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command/base.rb:87:in `perform'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/command.rb:48:in `invoke'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/railties-7.0.8.1/lib/rails/commands.rb:18:in `<main>'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /Users/joerafaniello/.gem/ruby/3.1.4/gems/bootsnap-1.18.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from bin/rails:4:in `<main>'

@miq-bot
Copy link
Member

miq-bot commented Feb 21, 2024

Checked commit jrafanie@a7aa6b4 with ruby 2.7.8, rubocop 1.56.3, haml-lint 0.51.0, and yamllint
1 file checked, 0 offenses detected
Everything looks fine. ⭐

@Fryguy Fryguy merged commit f5e3575 into ManageIQ:master Feb 21, 2024
8 checks passed
@jrafanie jrafanie deleted the delay_descendant_loader_until_app_initialized branch February 27, 2024 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants