class Bootsnap::LoadPathCache::LoadedFeaturesIndex
returning false like ruby.
With bootsnap and with LoadedFeaturesIndex, this skips the second load,
‘a.rb`s.
With bootsnap but with no LoadedFeaturesIndex, this loads two different
Ruby returns false from step 3.
3. `require ’a’‘
2. Prepend a new $LOAD_PATH element containing an `a.rb`
1. `require ’a’‘
If you disable LoadedFeaturesIndex, you can see the problem this solves by:
’/path/to/bundler’.
realize that it has already loaded ‘bundler’, and not just
different absolute path. This class makes bootsnap smart enough to
a new instance of bundler is added to the $LOAD_PATH which resolves to a
has already required a file by its short name (e.g. require ‘bundler’) if
This works around an issue where, without bootsnap, ruby knows that it
we can’t easily obtain an interface to.
LoadedFeaturesIndex partially mirrors an internal structure in ruby that
def initialize
def initialize @lfi = {} @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped. # In theory the user could mutate $LOADED_FEATURES and invalidate our # cache. If this ever comes up in practice — or if you, the # enterprising reader, feels inclined to solve this problem — we could # parallel the work done with ChangeObserver on $LOAD_PATH to mirror # updates to our @lfi. $LOADED_FEATURES.each do |feat| hash = feat.hash $LOAD_PATH.each do |lpe| next unless feat.start_with?(lpe) # /a/b/lib/my/foo.rb # ^^^^^^^^^ short = feat[(lpe.length + 1)..-1] stripped = strip_extension(short) @lfi[short] = hash @lfi[stripped] = hash end end end
def key?(feature)
def key?(feature) @mutex.synchronize { @lfi.key?(feature) } end
def purge(feature)
If access patterns make this not-okay, we can lazy-invert the LFI on
We've optimized for initialize and register to be fast, and purge to be tolerable.
def purge(feature) @mutex.synchronize do feat_hash = feature.hash @lfi.reject! { |_, hash| hash == feat_hash } end end
def register(short, long = nil)
2. Inspect $LOADED_FEATURES upon return from yield to find the matching
not quite right; or
1. Just add `bundler.rb`, `bundler.so`, and so on, which is close but
We could either:
to confidently add the `bundler.rb` form to @lfi.
pass `long` (the full expanded absolute path), then we did are not able
`FallbackScan` pathway in `kernel_require.rb` and therefore did not
If the user asked for e.g. `require 'bundler'`, and we went through the
entry:
There is a relatively uncommon case where we could miss adding an
def register(short, long = nil) if long.nil? pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$} len = $LOADED_FEATURES.size ret = yield long = $LOADED_FEATURES[len..-1].detect { |feat| feat =~ pat } else ret = yield end hash = long.hash # do we have 'bundler' or 'bundler.rb'? altname = if File.extname(short) != '' # strip the path from 'bundler.rb' -> 'bundler' strip_extension(short) elsif long && (ext = File.extname(long)) # get the extension from the expanded path if given # 'bundler' + '.rb' short + ext end @mutex.synchronize do @lfi[short] = hash (@lfi[altname] = hash) if altname end ret end
def strip_extension(f)
def strip_extension(f) f.sub(STRIP_EXTENSION, '') end