Skip to content
This repository

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 · 9 comments

3 participants

Lenny Marks Xavier Noria Steve Klabnik
Lenny Marks

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'
Xavier Noria
Owner

Can you upload the minimal app somewhere?

Lenny Marks

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 Marks

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

Steve Klabnik
Collaborator

but didn't see a spot.

Most people just push it to GitHub.

Lenny Marks

but didn't see a spot.

Most people just push it to GitHub.

https://github.com/lenny/rails_3_9_autoload_issue

Xavier Noria
Owner

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

Xavier Noria
Owner

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 Marks

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
Xavier Noria
Owner

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

Xavier Noria fxn closed this in bff4d8d
Xavier Noria fxn referenced this issue from a commit
Xavier Noria 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
Something went wrong with that request. Please try again.