spring icon indicating copy to clipboard operation
spring copied to clipboard

Problem with zeitwerk autoloader

Open tycooon opened this issue 6 years ago • 8 comments

I have a Rails 6 app and when using zeitwerk autoloader mode, it looks like Spring is adding all the rb-files to the watch list. So every change in any file triggers full spring restart. In classic mode, there is no such issue. I just add config.autoloader = :classic to application.rb and it stops restarting spring server on every change.

Here are the logs in zeitwerk mode:

started on /var/folders/qk/rfns1ml107b19r47z45l1dch0000gn/T/spring-501/92640e3e802293c883d0acfcd709d0d4
[2019-09-14 13:10:03 +0300] [6675] [server] accepted client
[2019-09-14 13:10:03 +0300] [6675] [server] running command rspec
[2019-09-14 13:10:03 +0300] [6675] [application_manager:test] child not running; starting
[2019-09-14 13:10:03 +0300] [6662] [client] sending command
[2019-09-14 13:10:03 +0300] [6678] [application:test] initialized -> running
[2019-09-14 13:10:04 +0300] [6678] [application:test] got client
[2019-09-14 13:10:04 +0300] [6678] [application:test] preloading app
[2019-09-14 13:10:07 +0300] [6678] [watcher:test] watcher: add: <here goes the list of all rb-files in the project>

I wonder, why is it adding all that files.

tycooon avatar Sep 14 '19 10:09 tycooon

@fxn any idea?

rafaelfranca avatar Sep 19 '19 14:09 rafaelfranca

Not familiar with the way Spring builds this list. I'll have a look.

fxn avatar Sep 19 '19 14:09 fxn

I have created a new Rails 6 application with files app/models/foo.rb and app/models/foo/bar.rb and I don't see them in the logs of Spring.

I tried development (bin/rails runner 1) and test (bin/rails test).

Could you please provide a minimal way to reproduce?

fxn avatar Sep 19 '19 15:09 fxn

It looks like you have to set config.eager_load = true for development and test envs. Here is the example app just in case https://github.com/tycooon/spring-test-app

tycooon avatar Sep 19 '19 15:09 tycooon

I could reproduce.

In principle this seems to match the docs, no?

Spring will automatically detect file changes to any file loaded when the server boots. Changes will cause the affected environments to be restarted.

Eager loading happens while the application boots, so that code lives in the master process if I remember correctly how Spring works (a bit rusty, so perhaps I am wrong).

I'll see what explains the difference.

fxn avatar Sep 19 '19 19:09 fxn

Hi, any updates on this? I turned off eager loading but this is not great for test env.

tycooon avatar Sep 26 '19 08:09 tycooon

Why not? Normally you do not want to eager load the whole app to run one test.

Indeed, in the history of Spring there is a commit that skips eager loading unconditionally, later reverted to let the user decide in the config file.

Rails sets eager loading to false in the generated config/environments/test.rb, and my intuition is that Spring in zeitwerk mode is working as expected. The suspicious behavior for me is the one in classic mode.

Not eager loading is definitely the norm in the test environment.

However, this is a difference I’d like to understand anyway, but I need to find a moment to dig into Spring source code.

fxn avatar Sep 26 '19 10:09 fxn

I have a slightly different problem, when spring is running and any change is made to a watched file, the subsequent RSpec run yields this until I either reload or stop Spring.

$ bin/rspec spec/models/project_spec.rb:416 --format documentation
.../activesupport-6.0.0/lib/active_support/dependencies/zeitwerk_integration.rb:14:in `rescue in block in clear': reloading is disabled because config.cache_classes is true (RuntimeError)
  from .../activesupport-6.0.0/lib/active_support/dependencies/zeitwerk_integration.rb:12:in `block in clear'
  from .../activesupport-6.0.0/lib/active_support/dependencies.rb:47:in `block in unload_interlock'
  from .../activesupport-6.0.0/lib/active_support/dependencies/interlock.rb:20:in `block in unloading'
  from .../activesupport-6.0.0/lib/active_support/concurrency/share_lock.rb:151:in `exclusive'
  from .../activesupport-6.0.0/lib/active_support/dependencies/interlock.rb:19:in `unloading'
  from .../activesupport-6.0.0/lib/active_support/dependencies.rb:47:in `unload_interlock'
  from .../activesupport-6.0.0/lib/active_support/dependencies/zeitwerk_integration.rb:11:in `clear'
  from .../railties-6.0.0/lib/rails/application/finisher.rb:206:in `block (2 levels) in <module:Finisher>'
  from .../activesupport-6.0.0/lib/active_support/file_update_checker.rb:83:in `execute'
  from .../railties-6.0.0/lib/rails/application/finisher.rb:232:in `block (3 levels) in <module:Finisher>'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:135:in `run_callbacks'
  from .../activesupport-6.0.0/lib/active_support/reloader.rb:120:in `class_unload!'
  from .../railties-6.0.0/lib/rails/application/finisher.rb:231:in `block (2 levels) in <module:Finisher>'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:429:in `instance_exec'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:429:in `block in make_lambda'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:201:in `block (2 levels) in halting'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:607:in `block (2 levels) in default_terminator'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:606:in `catch'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:606:in `block in default_terminator'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:202:in `block in halting'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:514:in `block in invoke_before'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:514:in `each'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:514:in `invoke_before'
  from .../activesupport-6.0.0/lib/active_support/callbacks.rb:134:in `run_callbacks'
  from .../activesupport-6.0.0/lib/active_support/execution_wrapper.rb:111:in `run!'
  from .../activesupport-6.0.0/lib/active_support/reloader.rb:114:in `run!'
  from .../activesupport-6.0.0/lib/active_support/reloader.rb:53:in `block (2 levels) in reload!'
  from .../activesupport-6.0.0/lib/active_support/reloader.rb:52:in `tap'
  from .../activesupport-6.0.0/lib/active_support/reloader.rb:52:in `block in reload!'
  from .../activesupport-6.0.0/lib/active_support/execution_wrapper.rb:88:in `wrap'
  from .../activesupport-6.0.0/lib/active_support/reloader.rb:51:in `reload!'
  from .../spring-2.1.0/lib/spring/application.rb:168:in `serve'
  from .../spring-2.1.0/lib/spring/application.rb:145:in `block in run'
  from .../spring-2.1.0/lib/spring/application.rb:139:in `loop'
  from .../spring-2.1.0/lib/spring/application.rb:139:in `run'
  from .../spring-2.1.0/lib/spring/application/boot.rb:19:in `<top (required)>'
  from /Users/olivierlacan/.rbenv/versions/2.5.5/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:54:in `require'
  from /Users/olivierlacan/.rbenv/versions/2.5.5/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:54:in `require'
  from -e:1:in `<main>'

I'm using https://github.com/jonleighton/spring-commands-rspec with RSpec which modifies bin/rspec to add:

begin
  load File.expand_path('../spring', __FILE__)
rescue LoadError => e
  raise unless e.message.include?('spring')
end

Despite the error message from Zeitwerk, config.cache_classes is set to false in my development.rb environment config.

olivierlacan avatar Oct 07 '19 22:10 olivierlacan