Skip to content

Autoload broken by path canonicalization #2788

@headius

Description

@headius

We have broken autoload in recent weeks or months. I have tracked the problem down to autoload logic not handling RubyGems canonicalization of file paths and our LoadService's failure to recognize that the canonicalized path and the absolute path refer to the same file.

An example of how this breaks:

Main script:

module Foo
  autoload :Bar, 'autoloaded.rb'
end
require_relative 'autoloaded.rb'

autoloaded.rb:

module Foo
  class Bar
    undef_method :==
  end
end

The resulting stack trace:

[] ~/projects/jruby $ jruby script.rb
NameError: Undefined method == for class 'Foo::Bar'
  undef_method at org/jruby/RubyModule.java:2607
   <class:Bar> at /Users/headius/projects/jruby/autoloaded.rb:3
  <module:Foo> at /Users/headius/projects/jruby/autoloaded.rb:2
         <top> at /Users/headius/projects/jruby/autoloaded.rb:1
       require at org/jruby/RubyKernel.java:966
        (root) at /Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb:1
       require at /Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb:54
         <top> at script.rb:4

It looks like something's awry with undef_method, but what's actually happening is that the autoloaded.rb script is executing twice: once when required directly and once when the first access of Foo::Bar in autoloaded.rb (to open or reopen the Bar class) triggers the autoload.

Our autoload logic detects an in-progress autoload by looking for a circular require. Circular requires are detected by looking in a map of require locks to see if the current thread has already locked this file.

However, if the file is required via an absolute path, the entry in the table does not match and it proceeds to attempt to load the relative path. That execution of undef_method happens first and succeeds, it exits the script body, and then the first load of the autoloaded script proceeds to undef_method a method that isn't there anymore.

Very sneaky, because the error happens without any evidence of the second load in the stack...but I saw this in the context of a rails app with numerous other double-load entries in the backtrace.

This also breaks circular require detection in general, but the autoload scenario is more of an issue because it is so heavily used by Ruby frameworks.

Must be fixed for pre2. This may require another overhaul of LoadService and its management of required and found paths.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions