ActiveSupport dependencies/autoloading loads files declared with ruby's autoload mechanism (top level only) even though trigger constant is not referenced #8213

Closed
lenny opened this Issue Nov 13, 2012 · 9 comments

3 participants

@lenny

I ran into some trouble using while upgrading a Rails app to 3.2.9 that uses the ruby's-client gem ( https://github.com/rubycas/rubycas-client ). This particular application does not use ActiveRecord, but I was seeing uninitialized constant CASClient::Tickets::Storage::ActiveRecord errors. I pinned it to the fact that casclient.rb contains this:

autoload :ACTIVE_RECORD_TICKET_STORE, 'casclient/tickets/storage/active_record_ticket_store'

Even though there is never any reference to the ACTIVE_RECORD_TICKET_STORE constant aside from the autoload declaration, The ActiveSupport class reloading winds up loading casclient/tickets/storage/active_record_ticket_store.rb.

I was able to reproduce the behavior in a minimal Rails app.

[2012-11-13 17:51:19] ERROR RuntimeError: /Users/Shared/lenny/tmp/railsautoload/app/models/my_module/something_optional.rb is being loaded
    /Users/Shared/lenny/tmp/railsautoload/app/models/my_module/something_optional.rb:1:in `<top (required)>'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/core_ext/module/qualified_const.rb:42:in `const_get'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/core_ext/module/qualified_const.rb:42:in `block in qualified_const_defined?'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/core_ext/module/qualified_const.rb:40:in `each'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/core_ext/module/qualified_const.rb:40:in `inject'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/core_ext/module/qualified_const.rb:40:in `qualified_const_defined?'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/dependencies.rb:379:in `qualified_const_defined?'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/dependencies.rb:669:in `remove_constant'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/dependencies.rb:534:in `block in remove_unloadable_constants!'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/dependencies.rb:534:in `each'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/dependencies.rb:534:in `remove_unloadable_constants!'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/dependencies.rb:331:in `clear'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.9/lib/rails/application/finisher.rb:76:in `block (2 levels) in <module:Finisher>'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/callbacks.rb:407:in `_run__3479555800908422087__cleanup__1154112558922223398__callbacks'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/callbacks.rb:405:in `__run_callback'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/callbacks.rb:385:in `_run_cleanup_callbacks'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.2.9/lib/active_support/callbacks.rb:81:in `run_callbacks'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.9/lib/action_dispatch/middleware/reloader.rb:78:in `cleanup!'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.9/lib/action_dispatch/middleware/reloader.rb:66:in `block in call'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.9/lib/action_dispatch/middleware/body_proxy.rb:18:in `call'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.9/lib/action_dispatch/middleware/body_proxy.rb:18:in `close'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/rack-1.4.1/lib/rack/body_proxy.rb:15:in `close'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/rack-1.4.1/lib/rack/content_length.rb:25:in `call'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.9/lib/rails/rack/log_tailer.rb:17:in `call'
    /Users/Shared/lenny/.rvm/gems/ruby-1.9.3-p194/gems/rack-1.4.1/lib/rack/handler/webrick.rb:59:in `service'
    /Users/Shared/lenny/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/httpserver.rb:138:in `service'
    /Users/Shared/lenny/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/httpserver.rb:94:in `run'
    /Users/Shared/lenny/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/server.rb:191:in `block in start_thread'
@fxn
Ruby on Rails member

Can you upload the minimal app somewhere?

@lenny

Sorry. Was assuming I'd be uploading that once the issue was created but didn't see a spot. If you apply the patch from the Gist below to a freshly generated rails 3.2.9 app, it should get you there.

https://gist.github.com/4069112

$ rails new testapp
$ cd testapp
$ patch -p0 < patch.txt

@lenny

For anyone falling upon this after running into the same casclient error... I added the following hack after "require casclient" which seems to be working..

unless Object.const_defined?(:ActiveRecord)
  ACTIVE_RECORD_TICKET_STORE = <<-END
    This is a workaround for an issue as of rubycas-client-2.3.9
    and Rails-3.2.9.
    See: https://github.com/rails/rails/issues/8213
    ActiveSupport dependencies/autoloading loads files declared with ruby's
    autoload mechanism (top level only) even though trigger constant is not referenced.

    casclient.rb contains the following:
    autoload :ACTIVE_RECORD_TICKET_STORE, 'casclient/tickets/storage/active_record_ticket_store'
  END
end
@steveklabnik
Ruby on Rails member

but didn't see a spot.

Most people just push it to GitHub.

@lenny

but didn't see a spot.

Most people just push it to GitHub.

https://github.com/lenny/rails_3_9_autoload_issue

@fxn
Ruby on Rails member

@lenny excellent, I am going to have a look at this today.

@fxn
Ruby on Rails member

Alright, the core of the issue can be seen in this little pure Ruby script:

autoload :Foo, 'foo'
p Object.constants

You'll see Foo is returned among the constants of Object.

The file is not loaded at that point though, the problem is that AS relies on the constants method to detect which constants have been autoloaded after some autoloading has been triggered. That is, when MyModule is autoloaded AS knows that both MyModule and MyModuleFoo are constants newly defined in Object, and registers their name for later removal in development mode (that is how constant reloading works).

And the code that removes the autoloaded constants needs the actual object because it needs to check if it responds to :before_remove_const (technically it constantizes the name before reaching that point indeed). Thus, when it tries to access to the object the file that defines it is run.

I have been able today to understand the issue, need to think about how to address it. Will write back.

@lenny

Maybe consult the return value of :autoload?

rb(main):001:0> autoload :Foo, 'foo'
=> nil
irb(main):002:0> autoload?(:Foo)
=> "foo"
irb(main):003:0> Foo
=> Foo
irb(main):004:0> autoload?(:Foo)
=> nil
@fxn
Ruby on Rails member

Yeah, looks like the natural thing to base the solution on.

@fxn fxn closed this in bff4d8d Nov 15, 2012
@fxn fxn added a commit that referenced this issue Nov 15, 2012
@fxn fxn let remove_constant still delete Kernel#autoload constants [rounds #8213
]

The method #remove_const does not load the file, so we
can still remove the constant.
a8c3ea9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment