From 81f0dfac427a596907edf73ca863f496fd2c85e2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 10 Jan 2022 09:58:16 +0100 Subject: [PATCH] LoadedFeaturesIndex#register: Ignore absolute paths entirely Fix: https://github.com/Shopify/bootsnap/issues/383 Ref: https://github.com/fxn/zeitwerk/issues/198 The loaded feature index goal is to prevent requiring the same "feature" twice if the `$LOAD_PATH` was mutated. e.g: ```ruby require "bundler" # ~/gems/bundler/lib/bundler.rb $LOAD_PATH.unshift("some/path") require "bundler" # some/path/bundler.rb ``` In such scenario Ruby remember that you require "bundler" already and even though it now maps to a new path, it won't load it again. This was causing issues with Zeitwerk if it is loaded before bootsnap (see refs), because the cache wouldn't be cleared. But ultimately Zeitwerk always require absolute paths, and the concern described above simply doesn't apply to absolute paths. So we can simply bail out early in these cases. That fixes the bug, and also means less work and a smaller index, so win-win. --- CHANGELOG.md | 4 ++++ lib/bootsnap.rb | 10 ++++++++++ lib/bootsnap/load_path_cache/cache.rb | 12 +----------- .../load_path_cache/loaded_features_index.rb | 5 +++++ test/load_path_cache/loaded_features_index_test.rb | 14 +++++++++++++- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba35ccac..34876512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +* Ignore absolute paths in the loaded feature index. (#385) + This fixes a compatibility issue with Zeitwerk when Zeitwerk is loaded before bootsnap. It also should + Reduce the memory usage and improve load performance of Zeitwerk managed files. + * Automatically invalidate the load path cache whenever the Ruby version change. (#387) This is to avoid issues in case the same installation path is re-used for subsequent ruby patch releases. diff --git a/lib/bootsnap.rb b/lib/bootsnap.rb index 06a32e85..842973d9 100644 --- a/lib/bootsnap.rb +++ b/lib/bootsnap.rb @@ -123,4 +123,14 @@ def self.default_setup end end end + + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ + def self.absolute_path?(path) + path[1] == ':' + end + else + def self.absolute_path?(path) + path.start_with?('/') + end + end end diff --git a/lib/bootsnap/load_path_cache/cache.rb b/lib/bootsnap/load_path_cache/cache.rb index b1e82328..33ea1967 100644 --- a/lib/bootsnap/load_path_cache/cache.rb +++ b/lib/bootsnap/load_path_cache/cache.rb @@ -48,7 +48,7 @@ def find(feature, try_extensions: true) reinitialize if (@has_relative_paths && dir_changed?) || stale? feature = feature.to_s.freeze - return feature if absolute_path?(feature) + return feature if Bootsnap.absolute_path?(feature) if feature.start_with?('./', '../') return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze @@ -98,16 +98,6 @@ def find(feature, try_extensions: true) raise(LoadPathCache::FallbackScan, '', []) if @development_mode end - if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ - def absolute_path?(path) - path[1] == ':' - end - else - def absolute_path?(path) - path.start_with?(SLASH) - end - end - def unshift_paths(sender, *paths) return unless sender == @path_obj @mutex.synchronize { unshift_paths_locked(*paths) } diff --git a/lib/bootsnap/load_path_cache/loaded_features_index.rb b/lib/bootsnap/load_path_cache/loaded_features_index.rb index 769093fe..12cc0fb1 100644 --- a/lib/bootsnap/load_path_cache/loaded_features_index.rb +++ b/lib/bootsnap/load_path_cache/loaded_features_index.rb @@ -83,6 +83,11 @@ def key?(feature) # 2. Inspect $LOADED_FEATURES upon return from yield to find the matching # entry. def register(short, long = nil) + # Absolute paths are not a concern. + if Bootsnap.absolute_path?(short.to_s) + return yield + end + if long.nil? len = $LOADED_FEATURES.size ret = yield diff --git a/test/load_path_cache/loaded_features_index_test.rb b/test/load_path_cache/loaded_features_index_test.rb index 5e5f02d1..ea8da1c2 100644 --- a/test/load_path_cache/loaded_features_index_test.rb +++ b/test/load_path_cache/loaded_features_index_test.rb @@ -136,11 +136,23 @@ def test_derives_initial_state_from_loaded_features end def test_works_with_pathname - path = '/tmp/bundler.rb' + path = 'bundler.rb' pathname = Pathname.new(path) @index.register(pathname, path) { true } assert(@index.key?(pathname)) end + + def test_ignores_absolute_paths + path = "#{Dir.mktmpdir}/bundler.rb" + @index.register(path) { true } + refute(@index.key?(path)) + + path = "#{Dir.mktmpdir}/bundler.rb" + pathname = Pathname.new(path) + @index.register(pathname, path) { true } + refute(@index.key?(path)) + refute(@index.key?(pathname)) + end end end end