bootsnap 1.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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