bootsnap 1.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ module Bootsnap
2
+ module LoadPathCache
3
+ module ChangeObserver
4
+ def self.register(observer, arr)
5
+ # Re-overriding these methods on an array that already has them would
6
+ # cause StackOverflowErrors
7
+ return if arr.respond_to?(:push_without_lpc)
8
+
9
+ # For each method that adds items to one end or another of the array
10
+ # (<<, push, unshift, concat), override that method to also notify the
11
+ # observer of the change.
12
+ sc = arr.singleton_class
13
+ sc.send(:alias_method, :shovel_without_lpc, :<<)
14
+ arr.define_singleton_method(:<<) do |entry|
15
+ observer.push_paths(self, entry.to_s)
16
+ shovel_without_lpc(entry)
17
+ end
18
+
19
+ sc.send(:alias_method, :push_without_lpc, :push)
20
+ arr.define_singleton_method(:push) do |*entries|
21
+ observer.push_paths(self, *entries.map(&:to_s))
22
+ push_without_lpc(*entries)
23
+ end
24
+
25
+ sc.send(:alias_method, :unshift_without_lpc, :unshift)
26
+ arr.define_singleton_method(:unshift) do |*entries|
27
+ observer.unshift_paths(self, *entries.map(&:to_s))
28
+ unshift_without_lpc(*entries)
29
+ end
30
+
31
+ sc.send(:alias_method, :concat_without_lpc, :concat)
32
+ arr.define_singleton_method(:concat) do |entries|
33
+ observer.push_paths(self, *entries.map(&:to_s))
34
+ concat_without_lpc(entries)
35
+ end
36
+
37
+ # For each method that modifies the array more aggressively, override
38
+ # the method to also have the observer completely reconstruct its state
39
+ # after the modification. Many of these could be made to modify the
40
+ # internal state of the LoadPathCache::Cache more efficiently, but the
41
+ # accounting cost would be greater than the hit from these, since we
42
+ # actively discourage calling them.
43
+ %i(
44
+ collect! compact! delete delete_at delete_if fill flatten! insert map!
45
+ reject! reverse! select! shuffle! shift slice! sort! sort_by!
46
+ ).each do |meth|
47
+ sc.send(:alias_method, :"#{meth}_without_lpc", meth)
48
+ arr.define_singleton_method(meth) do |*a|
49
+ send(:"#{meth}_without_lpc", *a)
50
+ observer.reinitialize
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,73 @@
1
+ module Bootsnap
2
+ module LoadPathCache
3
+ module CoreExt
4
+ module ActiveSupport
5
+ def self.with_bootsnap_fallback(error)
6
+ yield
7
+ rescue error => e
8
+ # NoMethodError is a NameError, but we only want to handle actual
9
+ # NameError instances.
10
+ raise unless e.class == error
11
+ without_bootsnap_cache { yield }
12
+ end
13
+
14
+ def self.without_bootsnap_cache
15
+ prev = Thread.current[:without_bootsnap_cache] || false
16
+ Thread.current[:without_bootsnap_cache] = true
17
+ yield
18
+ ensure
19
+ Thread.current[:without_bootsnap_cache] = prev
20
+ end
21
+
22
+ module ClassMethods
23
+ def autoload_paths=(o)
24
+ r = super
25
+ Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o)
26
+ r
27
+ end
28
+
29
+ def search_for_file(path)
30
+ return super if Thread.current[:without_bootsnap_cache]
31
+ begin
32
+ Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
33
+ rescue Bootsnap::LoadPathCache::ReturnFalse
34
+ nil # doesn't really apply here
35
+ end
36
+ end
37
+
38
+ def autoloadable_module?(path_suffix)
39
+ Bootsnap::LoadPathCache.autoload_paths_cache.has_dir?(path_suffix)
40
+ end
41
+
42
+ def remove_constant(const)
43
+ CoreExt::ActiveSupport.without_bootsnap_cache { super }
44
+ end
45
+
46
+ # If we can't find a constant using the patched implementation of
47
+ # search_for_file, try again with the default implementation.
48
+ #
49
+ # These methods call search_for_file, and we want to modify its
50
+ # behaviour. The gymnastics here are a bit awkward, but it prevents
51
+ # 200+ lines of monkeypatches.
52
+ def load_missing_constant(from_mod, const_name)
53
+ CoreExt::ActiveSupport.with_bootsnap_fallback(NameError) { super }
54
+ end
55
+
56
+ # Signature has changed a few times over the years; easiest to not
57
+ # reiterate it with version polymorphism here...
58
+ def depend_on(*)
59
+ CoreExt::ActiveSupport.with_bootsnap_fallback(LoadError) { super }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ module ActiveSupport
68
+ module Dependencies
69
+ class << self
70
+ prepend Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,88 @@
1
+ module Bootsnap
2
+ module LoadPathCache
3
+ module CoreExt
4
+ def self.make_load_error(path)
5
+ err = LoadError.new("cannot load such file -- #{path}")
6
+ err.define_singleton_method(:path) { path }
7
+ err
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ module Kernel
14
+ alias_method :require_without_cache, :require
15
+ def require(path)
16
+ if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
17
+ require_without_cache(resolved)
18
+ else
19
+ raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
20
+ end
21
+ rescue Bootsnap::LoadPathCache::ReturnFalse
22
+ return false
23
+ rescue Bootsnap::LoadPathCache::FallbackScan
24
+ require_without_cache(path)
25
+ end
26
+
27
+ alias_method :load_without_cache, :load
28
+ def load(path, wrap = false)
29
+ if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
30
+ load_without_cache(resolved, wrap)
31
+ else
32
+ # load also allows relative paths from pwd even when not in $:
33
+ relative = File.expand_path(path)
34
+ if File.exist?(File.expand_path(path))
35
+ return load_without_cache(relative, wrap)
36
+ end
37
+ raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
38
+ end
39
+ rescue Bootsnap::LoadPathCache::ReturnFalse
40
+ return false
41
+ rescue Bootsnap::LoadPathCache::FallbackScan
42
+ load_without_cache(path, wrap)
43
+ end
44
+ end
45
+
46
+ class << Kernel
47
+ alias_method :require_without_cache, :require
48
+ def require(path)
49
+ if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
50
+ require_without_cache(resolved)
51
+ else
52
+ raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
53
+ end
54
+ rescue Bootsnap::LoadPathCache::ReturnFalse
55
+ return false
56
+ rescue Bootsnap::LoadPathCache::FallbackScan
57
+ require_without_cache(path)
58
+ end
59
+
60
+ alias_method :load_without_cache, :load
61
+ def load(path, wrap = false)
62
+ if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
63
+ load_without_cache(resolved, wrap)
64
+ else
65
+ # load also allows relative paths from pwd even when not in $:
66
+ relative = File.expand_path(path)
67
+ if File.exist?(relative)
68
+ return load_without_cache(relative, wrap)
69
+ end
70
+ raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
71
+ end
72
+ rescue Bootsnap::LoadPathCache::ReturnFalse
73
+ return false
74
+ rescue Bootsnap::LoadPathCache::FallbackScan
75
+ load_without_cache(path, wrap)
76
+ end
77
+ end
78
+
79
+ class Module
80
+ alias_method :autoload_without_cache, :autoload
81
+ def autoload(const, path)
82
+ autoload_without_cache(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
83
+ rescue Bootsnap::LoadPathCache::ReturnFalse
84
+ return false
85
+ rescue Bootsnap::LoadPathCache::FallbackScan
86
+ autoload_without_cache(const, path)
87
+ end
88
+ end
@@ -0,0 +1,113 @@
1
+ require_relative 'path_scanner'
2
+
3
+ module Bootsnap
4
+ module LoadPathCache
5
+ class Path
6
+ # A path is considered 'stable' if it is part of a Gem.path or the ruby
7
+ # distribution. When adding or removing files in these paths, the cache
8
+ # must be cleared before the change will be noticed.
9
+ def stable?
10
+ stability == STABLE
11
+ end
12
+
13
+ # A path is considered volatile if it doesn't live under a Gem.path or
14
+ # the ruby distribution root. These paths are scanned for new additions
15
+ # more frequently.
16
+ def volatile?
17
+ stability == VOLATILE
18
+ end
19
+
20
+ attr_reader :path
21
+
22
+ def initialize(path)
23
+ @path = path.to_s
24
+ end
25
+
26
+ # True if the path exists, but represents a non-directory object
27
+ def non_directory?
28
+ !File.stat(path).directory?
29
+ rescue Errno::ENOENT
30
+ false
31
+ end
32
+
33
+ def relative?
34
+ !path.start_with?(SLASH)
35
+ end
36
+
37
+ # Return a list of all the requirable files and all of the subdirectories
38
+ # of this +Path+.
39
+ def entries_and_dirs(store)
40
+ if stable?
41
+ # the cached_mtime field is unused for 'stable' paths, but is
42
+ # set to zero anyway, just in case we change the stability heuristics.
43
+ _, entries, dirs = store.get(expanded_path)
44
+ return [entries, dirs] if entries # cache hit
45
+ entries, dirs = scan!
46
+ store.set(expanded_path, [0, entries, dirs])
47
+ return [entries, dirs]
48
+ end
49
+
50
+ cached_mtime, entries, dirs = store.get(expanded_path)
51
+
52
+ current_mtime = latest_mtime(expanded_path, dirs || [])
53
+ return [[], []] if current_mtime == -1 # path does not exist
54
+ return [entries, dirs] if cached_mtime == current_mtime
55
+
56
+ entries, dirs = scan!
57
+ store.set(expanded_path, [current_mtime, entries, dirs])
58
+ [entries, dirs]
59
+ end
60
+
61
+ def expanded_path
62
+ File.expand_path(path)
63
+ end
64
+
65
+ private
66
+
67
+ def scan! # (expensive) returns [entries, dirs]
68
+ PathScanner.call(expanded_path)
69
+ end
70
+
71
+ # last time a directory was modified in this subtree. +dirs+ should be a
72
+ # list of relative paths to directories under +path+. e.g. for /a/b and
73
+ # /a/b/c, pass ('/a/b', ['c'])
74
+ def latest_mtime(path, dirs)
75
+ max = -1
76
+ ["", *dirs].each do |dir|
77
+ curr = begin
78
+ File.mtime("#{path}/#{dir}").to_i
79
+ rescue Errno::ENOENT
80
+ -1
81
+ end
82
+ max = curr if curr > max
83
+ end
84
+ max
85
+ end
86
+
87
+ # a Path can be either stable of volatile, depending on how frequently we
88
+ # expect its contents may change. Stable paths aren't rescanned nearly as
89
+ # often.
90
+ STABLE = :stable
91
+ VOLATILE = :volatile
92
+
93
+ # Built-in ruby lib stuff doesn't change, but things can occasionally be
94
+ # installed into sitedir, which generally lives under libdir.
95
+ RUBY_LIBDIR = RbConfig::CONFIG['libdir']
96
+ RUBY_SITEDIR = RbConfig::CONFIG['sitedir']
97
+
98
+ def stability
99
+ @stability ||= begin
100
+ if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) }
101
+ STABLE
102
+ elsif expanded_path.start_with?(Bundler.bundle_path.to_s)
103
+ STABLE
104
+ elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR)
105
+ STABLE
106
+ else
107
+ VOLATILE
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../load_path_cache'
2
+
3
+ module Bootsnap
4
+ module LoadPathCache
5
+ module PathScanner
6
+ REQUIRABLES_AND_DIRS = "/**/*{#{DOT_RB},#{DL_EXTENSIONS.join(',')},/}"
7
+ IS_DIR = %r{(.*)/\z}
8
+ NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
9
+ ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
10
+ BUNDLE_PATH = (Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
11
+
12
+ def self.call(path)
13
+ path = path.to_s
14
+
15
+ relative_slice = (path.size + 1)..-1
16
+ # If the bundle path is a descendent of this path, we do additional
17
+ # checks to prevent recursing into the bundle path as we recurse
18
+ # through this path. We don't want to scan the bundle path because
19
+ # anything useful in it will be present on other load path items.
20
+ #
21
+ # This can happen if, for example, the user adds '.' to the load path,
22
+ # and the bundle path is '.bundle'.
23
+ contains_bundle_path = BUNDLE_PATH.start_with?(path)
24
+
25
+ dirs = []
26
+ requirables = []
27
+
28
+ Dir.glob(path + REQUIRABLES_AND_DIRS).each do |absolute_path|
29
+ next if contains_bundle_path && absolute_path.start_with?(BUNDLE_PATH)
30
+ relative_path = absolute_path.slice!(relative_slice)
31
+
32
+ if md = relative_path.match(IS_DIR)
33
+ dirs << md[1]
34
+ else
35
+ requirables << relative_path
36
+ end
37
+ end
38
+ [requirables, dirs]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,77 @@
1
+ require_relative '../explicit_require'
2
+
3
+ Bootsnap::ExplicitRequire.with_gems('msgpack') { require 'msgpack' }
4
+ Bootsnap::ExplicitRequire.from_rubylibdir('fileutils')
5
+
6
+ module Bootsnap
7
+ module LoadPathCache
8
+ class Store
9
+ NestedTransactionError = Class.new(StandardError)
10
+ SetOutsideTransactionNotAllowed = Class.new(StandardError)
11
+
12
+ def initialize(store_path)
13
+ @store_path = store_path
14
+ load_data
15
+ end
16
+
17
+ def get(key)
18
+ @data[key]
19
+ end
20
+
21
+ def fetch(key)
22
+ raise SetOutsideTransactionNotAllowed unless @in_txn
23
+ v = get(key)
24
+ unless v
25
+ @dirty = true
26
+ v = yield
27
+ @data[key] = v
28
+ end
29
+ v
30
+ end
31
+
32
+ def set(key, value)
33
+ raise SetOutsideTransactionNotAllowed unless @in_txn
34
+ if value != @data[key]
35
+ @dirty = true
36
+ @data[key] = value
37
+ end
38
+ end
39
+
40
+ def transaction
41
+ raise NestedTransactionError if @in_txn
42
+ @in_txn = true
43
+ yield
44
+ ensure
45
+ commit_transaction
46
+ @in_txn = false
47
+ end
48
+
49
+ private
50
+
51
+ def commit_transaction
52
+ if @dirty
53
+ dump_data
54
+ @dirty = false
55
+ end
56
+ end
57
+
58
+ def load_data
59
+ @data = begin
60
+ MessagePack.load(File.binread(@store_path))
61
+ # handle malformed data due to upgrade incompatability
62
+ rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
63
+ {}
64
+ end
65
+ end
66
+
67
+ def dump_data
68
+ # Change contents atomically so other processes can't get invalid
69
+ # caches if they read at an inopportune time.
70
+ tmp = "#{@store_path}.#{(rand * 100000).to_i}.tmp"
71
+ FileUtils.mkpath(File.dirname(tmp))
72
+ File.binwrite(tmp, MessagePack.dump(@data))
73
+ FileUtils.mv(tmp, @store_path)
74
+ end
75
+ end
76
+ end
77
+ end