appsignal/appsignal-ruby

3.7.3 LocalJumpError during asset precompilation

brendonrapp opened this issue · 3 comments

Our Rails 7.0.8 app deploying to Heroku throws LocalJumpError from the AppSignal gem version 3.7.3 during asset precompilation, coming from lib/hooks/active_job.rb

Reverting to 3.7.2 gets rid of the error.

Have not yet tried to narrow down to a minimum reproducible test case, but since 3.7.3 rolled out just today, figured it was worth it to sound an early warning.

Error trace from Heroku deployment:

remote: -----> Detecting rake tasks
remote: -----> Preparing app for Rails asset pipeline
remote:        Running: rake assets:precompile
remote:        rake aborted!
remote:        LocalJumpError: unexpected return (LocalJumpError)
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/appsignal-3.7.3/lib/appsignal/hooks/active_job.rb:33:in `block in install'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/lazy_load_hooks.rb:95:in `class_eval'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/lazy_load_hooks.rb:95:in `block in execute_hook'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/lazy_load_hooks.rb:85:in `with_execution_control'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/lazy_load_hooks.rb:90:in `execute_hook'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/lazy_load_hooks.rb:76:in `block in run_load_hooks'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/lazy_load_hooks.rb:75:in `each'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/lazy_load_hooks.rb:75:in `run_load_hooks'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activejob-7.0.8/lib/active_job/base.rb:77:in `<class:Base>'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activejob-7.0.8/lib/active_job/base.rb:63:in `<module:ActiveJob>'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activejob-7.0.8/lib/active_job/base.rb:17:in `<main>'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/actionmailer-7.0.8/lib/action_mailer/mail_delivery_job.rb:11:in `<module:ActionMailer>'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/actionmailer-7.0.8/lib/action_mailer/mail_delivery_job.rb:5:in `<main>'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/actionmailer-7.0.8/lib/action_mailer/base.rb:489:in `<class:Base>'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/actionmailer-7.0.8/lib/action_mailer/base.rb:466:in `<module:ActionMailer>'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/actionmailer-7.0.8/lib/action_mailer/base.rb:12:in `<main>'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/inflector/methods.rb:278:in `const_get'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/inflector/methods.rb:278:in `constantize'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/core_ext/string/inflections.rb:74:in `constantize'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/devise-4.9.3/app/mailers/devise/mailer.rb:4:in `<main>'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:30:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/inflector/methods.rb:278:in `const_get'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/inflector/methods.rb:278:in `constantize'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/core_ext/string/inflections.rb:74:in `constantize'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/devise-4.9.3/lib/devise.rb:327:in `get'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/devise-4.9.3/lib/devise.rb:350:in `mailer'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/devise_invitable-2.0.9/lib/devise_invitable/rails.rb:12:in `block in <class:Engine>'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:445:in `instance_exec'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:445:in `block in make_lambda'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:199:in `block (2 levels) in halting'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:687:in `block (2 levels) in default_terminator'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:686:in `catch'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:686:in `block in default_terminator'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:200:in `block in halting'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:595:in `block in invoke_before'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:595:in `each'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:595:in `invoke_before'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/callbacks.rb:106:in `run_callbacks'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/activesupport-7.0.8/lib/active_support/reloader.rb:88:in `prepare!'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/application/finisher.rb:68:in `block in <module:Finisher>'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/initializable.rb:32:in `instance_exec'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/initializable.rb:32:in `run'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/initializable.rb:61:in `block in run_initializers'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/initializable.rb:60:in `run_initializers'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/application.rb:372:in `initialize!'
remote:        /tmp/build_5ec81295/config/environment.rb:5:in `<main>'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        <internal:/tmp/build_5ec81295/vendor/ruby-3.2.2/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:37:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:38:in `require'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/application.rb:348:in `require_environment!'
remote:        /tmp/build_5ec81295/vendor/bundle/ruby/3.2.0/gems/railties-7.0.8/lib/rails/application.rb:506:in `block in run_tasks_blocks'
remote:        Tasks: TOP => assets:precompile => environment
remote:        (See full trace by running task with --trace)
remote:        [2024-05-08T22:44:29 (process) #5574][INFO] appsignal: Starting AppSignal 3.7.3 (/tmp/build_5ec81295/bin/rake, Ruby 3.2.2, x86_64-linux)
remote:        [2024-05-08T22:44:29 (process) #5574][INFO] appsignal: Starting AppSignal 3.7.3 (/tmp/build_5ec81295/bin/rake, Ruby 3.2.2, x86_64-linux)
remote:
remote:  !
remote:  !     Precompiling assets failed.
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
remote:
remote:  !     Push failed

Hi @brendonrapp, thanks for the report! Interesting to see that's not happening in our test suite but it is in your app. I've made a quick fix in #1079.

Thanks for reporting this issue @brendonrapp!

We've just released AppSignal for Ruby 3.7.4, which contains @tombruijn's fix in #1079.

Did some research into why this happened in real apps and not in our test suite. See this test app in this gist for reference.

When the ActiveJob::Base class (the MyLib class in the example) is already loaded before the ActiveSupport.on_load(:my_lib) do callback gets registered, it will immediately run the on_load hook. The callback block will be run inside the context of the parent method that defines it: the return bubbles up to the Appsignal::Hooks::ActiveJobHook#install method and early exit from there.

If the ActiveJob (MyLib) constant isn't defined yet, and it gets loaded later (with require_relative or Rails's auto load module and/or zeitwerk), the on_load callback is excuted in another context, namely zeitwerk's Kernel.require monkeypatch. I think this acts like requiring a file (like require_relative in the example gist), and that context is not a valid context in which to call return, resulting in that LocalJumpError.

Previous revisions of that gist also has a zeitwerk version of the example, which is more closely what happens in Rails. It has the same behavior as the require_relative code base.

The problem is, in our test suite ActiveJob::Base constant is already loaded. When the return is called it only exits from the install method in our gem. But when it's called in a real app, it's going through the require, because ActiveJob::Base isn't loaded yet, which results in the LocalJumpError.