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

initialize! raises FrozenError when called more than once from an app using zeitwerk #42319

Closed
modosc opened this issue May 29, 2021 · 10 comments · Fixed by #42961
Closed

initialize! raises FrozenError when called more than once from an app using zeitwerk #42319

modosc opened this issue May 29, 2021 · 10 comments · Fixed by #42961

Comments

@modosc
Copy link
Contributor

modosc commented May 29, 2021

Steps to reproduce

initialize a rails app twice:

  require "action_controller/railtie"
  class App < Rails::Application; end

  a = App.new
  a.initialize!
  
  b = App.new
  b.initialize!

Expected behavior

no errors should be raised - this is the case on 6.0.3.7 and 6.1.3.2 with default configs

Actual behavior

on main i get this error:

Traceback (most recent call last):
	22: from ./bug.rb:8:in `<main>'
	21: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/application.rb:389:in `initialize!'
	20: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/initializable.rb:60:in `run_initializers'
	19: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:205:in `tsort_each'
	18: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:226:in `tsort_each'
	17: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:347:in `each_strongly_connected_component'
	16: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:347:in `call'
	15: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:347:in `each'
	14: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:349:in `block in each_strongly_connected_component'
	13: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:415:in `each_strongly_connected_component_from'
	12: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:415:in `call'
	11: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/initializable.rb:50:in `tsort_each_child'
	10: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/initializable.rb:50:in `each'
	 9: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:421:in `block in each_strongly_connected_component_from'
	 8: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:431:in `each_strongly_connected_component_from'
	 7: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:422:in `block (2 levels) in each_strongly_connected_component_from'
	 6: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
	 5: from /Users/jon/.rvm/rubies/ruby-2.7.3/lib/ruby/2.7.0/tsort.rb:228:in `block in tsort_each'
	 4: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/initializable.rb:61:in `block in run_initializers'
	 3: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/initializable.rb:32:in `run'
	 2: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/initializable.rb:32:in `instance_exec'
	 1: from /Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/engine.rb:588:in `block in <class:Engine>'
/Users/jon/.rvm/gems/ruby-2.7.3/bundler/gems/rails-b678667853b4/railties/lib/rails/engine.rb:588:in `unshift'
FrozenError (can't modify frozen Array: [])

i also found the same behavior when instantiating and initializing instances of two different classes.

a bisect gave me 0d523d8 as the first bad commit which is where zeitwerk becomes the default autoloader.

i found that in 6.1.3.2 i could get a similar error by enabling zeitwerk

  require "action_controller/railtie"
  class App < Rails::Application; end
  App.config.autoloader = :zeitwerk

  a = App.new
  a.config.autoloader = :zeitwerk
  a.initialize!
  
  b = App.new
  b.config.autoloader = :zeitwerk
  b.initialize!

System configuration

Rails version:
b678667
6.1.3.2
6.0.3.7

Ruby version:
ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [x86_64-darwin20]

@modosc modosc changed the title initialize! raises FrozenError when called more than once from an app using zeitwerk initialize! raises FrozenError when called more than once from an app using zeitwerk May 29, 2021
@modosc modosc changed the title initialize! raises FrozenError when called more than once from an app using zeitwerk initialize! raises FrozenError when called more than once from an app using zeitwerk May 29, 2021
@zzak
Copy link
Member

zzak commented May 29, 2021

@modosc I think this is a question to ask zeitwerk 🤔 but I will leave this open for a while if anyone else has ideas.

@modosc
Copy link
Contributor Author

modosc commented May 29, 2021

@zzak i opened this here because multiple app support is in the api docs - that example will fail if you call initialize! on both apps.

that being said, if this is specific to zeitwerk i'm happy to move it. @fxn what are your thoughts here?

also fwiw my usage case is not hypothetical, it's for testing a rails/rack middleware. if there's another way to do so (or to reset an existing loader instance) without hitting this bug i'd be satisfied but so far all my attempts have failed.

@zzak
Copy link
Member

zzak commented May 30, 2021

I just spent some time looking into this afaict there is no way to "reload" once the app is initialized -- but you can create a new app using the same config and that should work. I'm not entirely sure though, so let's wait to see what Xavier says. 🙏

@modosc
Copy link
Contributor Author

modosc commented May 30, 2021

but you can create a new app using the same config and that should work.

if i'm understanding you correctly, i tried that also and it didn't work:

require "action_controller/railtie"

class App < Rails::Application; end
class OtherApp < Rails::Application; end

a = App.new.tap(&:initialize!)

# no config, fails
b = OtherApp.new.tap(&:initialize!)

# or with the same config, also fails
b = OtherApp.new(config: a.config).tap(&:initialize!)

@fxn
Copy link
Member

fxn commented Jun 7, 2021

I remember we discussed this in core when doing the integration, and the consensus was that right now we do not support several application instances. Rails.application is a singleton.

@rafaelfranca do you remember? Should we correct that documentation?

@modosc
Copy link
Contributor Author

modosc commented Jun 7, 2021

any idea why this spec works even with this change? this is exactly what i'm trying to do.

ah, i guess it's ActiveSupport::Testing::Isolation which kept this issue from coming up.

@rafaelfranca
Copy link
Member

@rafaelfranca do you remember? Should we correct that documentation?

There was some work in the past to make multiple applications possible, but the place we are right now it is not possible since the work was never finished. Since we have no plans to finish I think we should remove the documentation.

ghiculescu added a commit to ghiculescu/rails that referenced this issue Aug 6, 2021
Fixes rails#42319

Per rails#42319 (comment) this is not meant to be supported.
@ghiculescu
Copy link
Member

Since we have no plans to finish I think we should remove the documentation.

made a PR to remove the relevant section from the docs: #42961

also fwiw my usage case is not hypothetical, it's for testing a rails/rack middleware. if there's another way to do so (or to reset an existing loader instance) without hitting this bug i'd be satisfied but so far all my attempts have failed.

can you use ActiveSupport::Testing::Isolation in your gem?

st0012 added a commit to getsentry/sentry-ruby that referenced this issue Sep 25, 2021
st0012 added a commit to getsentry/sentry-ruby that referenced this issue Sep 25, 2021
* Run CI against Rails 7

* Add workaround to avoid FrozenError when initializing testing Rails apps

See: rails/rails#42319

* Update changelog
@modosc
Copy link
Contributor Author

modosc commented Oct 31, 2021

ah, a much easier solution was linked. this works for my usage case, thanks @st0012

getsentry/sentry-ruby@e21fac9#diff-18f3f95d0126b5b677a9472bd8f57b4123fdfbb3412f5762ebf1d5667ad67b7bR91-R95

@st0012
Copy link
Contributor

st0012 commented Oct 31, 2021

@modosc haha, thanks for creating the issue too. great cooperation I guess 🤝

jonathanhefner added a commit to jonathanhefner/fly-ruby that referenced this issue Aug 12, 2022
Rails does not support multiple application instances (per process),
and, since transitioning to Zeitwerk, calling `initialize!` on
additional application instances raises a "FrozenError: can't modify
frozen Array" error.  (See [rails/rails#42319][] for more information.)

This commit makes each `TestFlyRails` and `TestBadEnv` test run in its
own process so that there is only one application instance per process.

This also prevents the environment variables set in `TestFlyRails#setup`
from affecting other (randomly ordered) tests, such as
`TestBadEnv#test_middleware_skipped_without_required_env_vars`.

[rails/rails#42319]: rails/rails#42319
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants