Dmitry was able to replicate the issue using vanilla Ruby:
mkdir a
ln -s a b
echo "require_relative 'b'" > a/a.rb
echo "p 'b loaded'" > a/b.rb
echo "$: << File.expand_path('../b', __FILE__); require 'a'; require 'b'" > c.rb
ruby c.rb
Notice how "b loaded" is printed twice but if you replace require_relative with require it's just loaded once.
Shouldn't Ruby always expand the loaded files before appending them to the $LOADED_FEATURES and avoid this kind of error? I don't think require_relative should behave differently than a regular require in such cases.
I can't change the title myself. Could someone with privileges please change it to something like: "require_relative and require should be compatible with each other when symlinks are used".
I think this would make it easier to be searchable if others are experiencing the same issue. The key change is to add the "symlinks" word to the title so that the connection is made clear.
Subject changed from require_relative and require should be compatible with each other to require_relative and require should be compatible with each other when symlinks are used
We looked at this issue in developer meeting today.
The ultimate reason why require and require_relative behaves differently is that while require_relative infers its argument's realpath every time, require doesn't.
This was by design; because require is called many times, we wanted to completely avoid disk access for 2nd and later calls to require with identical arguments.
But I believe the reported behaviour is a bug to be fixed. In order to do so a meeting attendee suggested to push both symlink-resolved and unresolved paths at once to $LOADED_FEATURES on the first call.
This could make it harder for auto-reloaders to unload a required file when require_relative is used... Doesn't seem like a great solution to this bug to me... Ruby could cache internally the real path when using "require" so that the second call would avoid any disk access...
In order to do so a meeting attendee suggested to push both symlink-resolved and unresolved paths at once to $LOADED_FEATURES on the first call.
I think this explanation differs from that we discussed accurately.
IIRC, it should be vm->expanded_load_path, not $LOADED_FEATURES. $LOADED_FEATURES won't be doubled, but will have realpaths only.
I see, what you mean, if we simply add the expanded filenames to LOADED_PATH then that file would be loaded just once. Do you have any use cases where someone would symlink Ruby code for good usage?
Here's an actual use case that we saw in Rails: https://github.com/rails/rails/pull/29638#issuecomment-321335175
The reporter says that it happened in Jenkins, but I guess the same situation may happen in any case where we put the .rb files under a symlinked directory, for instance Capistrano.
load.c (rb_construct_expanded_load_path): expand load paths to
real paths to get rid of duplicate loading from symbolic-linked
directories. [Feature #10222]
I recently encountered this issue on v2.7.1, when symlinked directories were included in the $RUBYLIB environment variable. I was not experiencing the issue on v2.5.1 with the exact same setup however. Has the bug regressed, or was $RUBYLIB never handled by this fix to begin with?