Class: Bootsnap::LoadPathCache::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/bootsnap/load_path_cache/cache.rb

Constant Summary collapse

AGE_THRESHOLD =

seconds

30
BUILTIN_FEATURES =

{ ‘enumerator’ => nil, ‘enumerator.so’ => nil, … }

$LOADED_FEATURES.reduce({}) do |acc, feat|
  # Builtin features are of the form 'enumerator.so'.
  # All others include paths.
  next acc unless feat.size < 20 && !feat.include?('/')

  base = File.basename(feat, '.*') # enumerator.so -> enumerator
  ext  = File.extname(feat) # .so

  acc[feat] = nil # enumerator.so
  acc[base] = nil # enumerator

  if [DOT_SO, *DL_EXTENSIONS].include?(ext)
    DL_EXTENSIONS.each do |dl_ext|
      acc["#{base}#{dl_ext}"] = nil # enumerator.bundle
    end
  end

  acc
end.freeze

Instance Method Summary collapse

Constructor Details

#initialize(store, path_obj, development_mode: false) ⇒ Cache

Returns a new instance of Cache.



8
9
10
11
12
13
14
15
# File 'lib/bootsnap/load_path_cache/cache.rb', line 8

def initialize(store, path_obj, development_mode: false)
  @development_mode = development_mode
  @store = store
  @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
  @path_obj = path_obj
  @has_relative_paths = nil
  reinitialize
end

Instance Method Details

#absolute_path?(path) ⇒ Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/bootsnap/load_path_cache/cache.rb', line 89

def absolute_path?(path)
  path[1] == ':'
end

#each_requirableObject



108
109
110
111
112
113
114
# File 'lib/bootsnap/load_path_cache/cache.rb', line 108

def each_requirable
  @mutex.synchronize do
    @index.each do |rel, entry|
      yield "#{entry}/#{rel}"
    end
  end
end

#find(feature) ⇒ Object

Try to resolve this feature to an absolute path without traversing the loadpath.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/bootsnap/load_path_cache/cache.rb', line 48

def find(feature)
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
  feature = feature.to_s
  return feature if absolute_path?(feature)
  return File.expand_path(feature) if feature.start_with?('./')
  @mutex.synchronize do
    x = search_index(feature)
    return x if x

    # Ruby has some built-in features that require lies about.
    # For example, 'enumerator' is built in. If you require it, ruby
    # returns false as if it were already loaded; however, there is no
    # file to find on disk. We've pre-built a list of these, and we
    # return false if any of them is loaded.
    raise LoadPathCache::ReturnFalse if BUILTIN_FEATURES.key?(feature)

    # The feature wasn't found on our preliminary search through the index.
    # We resolve this differently depending on what the extension was.
    case File.extname(feature)
    # If the extension was one of the ones we explicitly cache (.rb and the
    # native dynamic extension, e.g. .bundle or .so), we know it was a
    # failure and there's nothing more we can do to find the file.
    when '', *CACHED_EXTENSIONS # no extension, .rb, (.bundle or .so)
      nil
    # Ruby allows specifying native extensions as '.so' even when DLEXT
    # is '.bundle'. This is where we handle that case.
    when DOT_SO
      x = search_index(feature[0..-4] + DLEXT)
      return x if x
      if DLEXT2
        search_index(feature[0..-4] + DLEXT2)
      end
    else
      # other, unknown extension. For example, `.rake`. Since we haven't
      # cached these, we legitimately need to run the load path search.
      raise LoadPathCache::FallbackScan
    end
  end
end

#has_dir?(dir) ⇒ Boolean

Does this directory exist as a child of one of the path items? e.g. given “/a/b/c/d” exists, and the path is [“/a/b”], has_dir?(“c/d”) is true.

Returns:

  • (Boolean)


20
21
22
23
# File 'lib/bootsnap/load_path_cache/cache.rb', line 20

def has_dir?(dir)
  reinitialize if stale?
  @mutex.synchronize { @dirs[dir] }
end

#push_paths(sender, *paths) ⇒ Object



103
104
105
106
# File 'lib/bootsnap/load_path_cache/cache.rb', line 103

def push_paths(sender, *paths)
  return unless sender == @path_obj
  @mutex.synchronize { push_paths_locked(*paths) }
end

#reinitialize(path_obj = @path_obj) ⇒ Object



116
117
118
119
120
121
122
123
124
125
# File 'lib/bootsnap/load_path_cache/cache.rb', line 116

def reinitialize(path_obj = @path_obj)
  @mutex.synchronize do
    @path_obj = path_obj
    ChangeObserver.register(self, @path_obj)
    @index = {}
    @dirs = Hash.new(false)
    @generated_at = now
    push_paths_locked(*@path_obj)
  end
end

#unshift_paths(sender, *paths) ⇒ Object



98
99
100
101
# File 'lib/bootsnap/load_path_cache/cache.rb', line 98

def unshift_paths(sender, *paths)
  return unless sender == @path_obj
  @mutex.synchronize { unshift_paths_locked(*paths) }
end