bootsnap 1.4.0 → 1.7.7
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +110 -0
- data/README.md +68 -13
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +285 -87
- data/ext/bootsnap/extconf.rb +20 -14
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +135 -0
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/compile_cache/iseq.rb +24 -7
- data/lib/bootsnap/compile_cache/yaml.rb +113 -39
- data/lib/bootsnap/compile_cache.rb +15 -2
- data/lib/bootsnap/explicit_require.rb +1 -0
- data/lib/bootsnap/load_path_cache/cache.rb +44 -9
- data/lib/bootsnap/load_path_cache/change_observer.rb +5 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +30 -6
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +11 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +43 -11
- data/lib/bootsnap/load_path_cache/path.rb +3 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +53 -27
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +28 -14
- data/lib/bootsnap/load_path_cache.rb +10 -16
- data/lib/bootsnap/setup.rb +2 -33
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +91 -15
- metadata +17 -29
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -24
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/README.jp.md +0 -231
- data/Rakefile +0 -12
- data/bin/ci +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -45
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -100
- data/shipit.rubygems.yml +0 -4
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative('../explicit_require')
|
2
4
|
|
3
5
|
module Bootsnap
|
@@ -8,8 +10,8 @@ module Bootsnap
|
|
8
10
|
def initialize(store, path_obj, development_mode: false)
|
9
11
|
@development_mode = development_mode
|
10
12
|
@store = store
|
11
|
-
@mutex =
|
12
|
-
@path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
|
13
15
|
@has_relative_paths = nil
|
14
16
|
reinitialize
|
15
17
|
end
|
@@ -44,9 +46,9 @@ module Bootsnap
|
|
44
46
|
# loadpath.
|
45
47
|
def find(feature)
|
46
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
47
|
-
feature = feature.to_s
|
49
|
+
feature = feature.to_s.freeze
|
48
50
|
return feature if absolute_path?(feature)
|
49
|
-
return
|
51
|
+
return expand_path(feature) if feature.start_with?('./')
|
50
52
|
@mutex.synchronize do
|
51
53
|
x = search_index(feature)
|
52
54
|
return x if x
|
@@ -65,7 +67,7 @@ module Bootsnap
|
|
65
67
|
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
66
68
|
# failure and there's nothing more we can do to find the file.
|
67
69
|
# no extension, .rb, (.bundle or .so)
|
68
|
-
when '', *CACHED_EXTENSIONS
|
70
|
+
when '', *CACHED_EXTENSIONS
|
69
71
|
nil
|
70
72
|
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
71
73
|
# is '.bundle'. This is where we handle that case.
|
@@ -142,7 +144,7 @@ module Bootsnap
|
|
142
144
|
expanded_path = p.expanded_path
|
143
145
|
entries, dirs = p.entries_and_dirs(@store)
|
144
146
|
# push -> low precedence -> set only if unset
|
145
|
-
dirs.each { |dir| @dirs[dir]
|
147
|
+
dirs.each { |dir| @dirs[dir] ||= path }
|
146
148
|
entries.each { |rel| @index[rel] ||= expanded_path }
|
147
149
|
end
|
148
150
|
end
|
@@ -162,6 +164,10 @@ module Bootsnap
|
|
162
164
|
end
|
163
165
|
end
|
164
166
|
|
167
|
+
def expand_path(feature)
|
168
|
+
maybe_append_extension(File.expand_path(feature))
|
169
|
+
end
|
170
|
+
|
165
171
|
def stale?
|
166
172
|
@development_mode && @generated_at + AGE_THRESHOLD < now
|
167
173
|
end
|
@@ -174,17 +180,46 @@ module Bootsnap
|
|
174
180
|
def search_index(f)
|
175
181
|
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
|
176
182
|
end
|
183
|
+
|
184
|
+
def maybe_append_extension(f)
|
185
|
+
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
|
186
|
+
end
|
177
187
|
else
|
178
188
|
def search_index(f)
|
179
189
|
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
|
180
190
|
end
|
191
|
+
|
192
|
+
def maybe_append_extension(f)
|
193
|
+
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
|
194
|
+
end
|
181
195
|
end
|
182
196
|
|
183
|
-
|
184
|
-
|
185
|
-
|
197
|
+
s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
|
198
|
+
if s.respond_to?(:-@)
|
199
|
+
if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= '2.7'
|
200
|
+
def try_index(f)
|
201
|
+
if (p = @index[f])
|
202
|
+
-(File.join(p, f).freeze)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
else
|
206
|
+
def try_index(f)
|
207
|
+
if (p = @index[f])
|
208
|
+
-File.join(p, f).untaint
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
else
|
213
|
+
def try_index(f)
|
214
|
+
if (p = @index[f])
|
215
|
+
File.join(p, f)
|
216
|
+
end
|
186
217
|
end
|
187
218
|
end
|
219
|
+
|
220
|
+
def try_ext(f)
|
221
|
+
f if File.exist?(f)
|
222
|
+
end
|
188
223
|
end
|
189
224
|
end
|
190
225
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
module ChangeObserver
|
@@ -14,18 +15,20 @@ module Bootsnap
|
|
14
15
|
@lpc_observer.push_paths(self, *entries.map(&:to_s))
|
15
16
|
super
|
16
17
|
end
|
18
|
+
alias_method :append, :push
|
17
19
|
|
18
20
|
def unshift(*entries)
|
19
21
|
@lpc_observer.unshift_paths(self, *entries.map(&:to_s))
|
20
22
|
super
|
21
23
|
end
|
24
|
+
alias_method :prepend, :unshift
|
22
25
|
|
23
26
|
def concat(entries)
|
24
27
|
@lpc_observer.push_paths(self, *entries.map(&:to_s))
|
25
28
|
super
|
26
29
|
end
|
27
30
|
|
28
|
-
# uniq! keeps the first
|
31
|
+
# uniq! keeps the first occurrence of each path, otherwise preserving
|
29
32
|
# order, preserving the effective load path
|
30
33
|
def uniq!(*args)
|
31
34
|
ret = super
|
@@ -53,6 +56,7 @@ module Bootsnap
|
|
53
56
|
end
|
54
57
|
|
55
58
|
def self.register(observer, arr)
|
59
|
+
return if arr.frozen? # can't register observer, but no need to.
|
56
60
|
arr.instance_variable_set(:@lpc_observer, observer)
|
57
61
|
arr.extend(ArrayMixin)
|
58
62
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
module CoreExt
|
4
5
|
def self.make_load_error(path)
|
5
|
-
err = LoadError.new("cannot load such file -- #{path}")
|
6
|
+
err = LoadError.new(+"cannot load such file -- #{path}")
|
7
|
+
err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
6
8
|
err.define_singleton_method(:path) { path }
|
7
9
|
err
|
8
10
|
end
|
@@ -30,16 +32,24 @@ module Kernel
|
|
30
32
|
end
|
31
33
|
|
32
34
|
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
35
|
+
rescue LoadError => e
|
36
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
37
|
+
raise(e)
|
33
38
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
34
39
|
false
|
35
40
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
36
|
-
|
41
|
+
fallback = true
|
42
|
+
ensure
|
43
|
+
if fallback
|
44
|
+
require_with_bootsnap_lfi(path)
|
45
|
+
end
|
37
46
|
end
|
38
47
|
|
39
48
|
alias_method(:require_relative_without_bootsnap, :require_relative)
|
40
49
|
def require_relative(path)
|
50
|
+
location = caller_locations(1..1).first
|
41
51
|
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
42
|
-
|
52
|
+
location.absolute_path || location.path, path
|
43
53
|
)
|
44
54
|
require(realpath)
|
45
55
|
end
|
@@ -51,15 +61,22 @@ module Kernel
|
|
51
61
|
end
|
52
62
|
|
53
63
|
# load also allows relative paths from pwd even when not in $:
|
54
|
-
if File.exist?(relative = File.expand_path(path))
|
64
|
+
if File.exist?(relative = File.expand_path(path).freeze)
|
55
65
|
return load_without_bootsnap(relative, wrap)
|
56
66
|
end
|
57
67
|
|
58
68
|
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
69
|
+
rescue LoadError => e
|
70
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
71
|
+
raise(e)
|
59
72
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
60
73
|
false
|
61
74
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
62
|
-
|
75
|
+
fallback = true
|
76
|
+
ensure
|
77
|
+
if fallback
|
78
|
+
load_without_bootsnap(path, wrap)
|
79
|
+
end
|
63
80
|
end
|
64
81
|
end
|
65
82
|
|
@@ -74,9 +91,16 @@ class Module
|
|
74
91
|
# added to $LOADED_FEATURES and won't be able to hook that modification
|
75
92
|
# since it's done in C-land.
|
76
93
|
autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
|
94
|
+
rescue LoadError => e
|
95
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
96
|
+
raise(e)
|
77
97
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
78
98
|
false
|
79
99
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
80
|
-
|
100
|
+
fallback = true
|
101
|
+
ensure
|
102
|
+
if fallback
|
103
|
+
autoload_without_bootsnap(const, path)
|
104
|
+
end
|
81
105
|
end
|
82
106
|
end
|
@@ -1,7 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class << $LOADED_FEATURES
|
2
3
|
alias_method(:delete_without_bootsnap, :delete)
|
3
4
|
def delete(key)
|
4
5
|
Bootsnap::LoadPathCache.loaded_features_index.purge(key)
|
5
6
|
delete_without_bootsnap(key)
|
6
7
|
end
|
8
|
+
|
9
|
+
alias_method(:reject_without_bootsnap!, :reject!)
|
10
|
+
def reject!(&block)
|
11
|
+
backup = dup
|
12
|
+
|
13
|
+
# FIXME: if no block is passed we'd need to return a decorated iterator
|
14
|
+
reject_without_bootsnap!(&block)
|
15
|
+
|
16
|
+
Bootsnap::LoadPathCache.loaded_features_index.purge_multi(backup - self)
|
17
|
+
end
|
7
18
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bootsnap
|
2
4
|
module LoadPathCache
|
3
5
|
# LoadedFeaturesIndex partially mirrors an internal structure in ruby that
|
@@ -24,7 +26,7 @@ module Bootsnap
|
|
24
26
|
class LoadedFeaturesIndex
|
25
27
|
def initialize
|
26
28
|
@lfi = {}
|
27
|
-
@mutex =
|
29
|
+
@mutex = Mutex.new
|
28
30
|
|
29
31
|
# In theory the user could mutate $LOADED_FEATURES and invalidate our
|
30
32
|
# cache. If this ever comes up in practice — or if you, the
|
@@ -38,7 +40,7 @@ module Bootsnap
|
|
38
40
|
# /a/b/lib/my/foo.rb
|
39
41
|
# ^^^^^^^^^
|
40
42
|
short = feat[(lpe.length + 1)..-1]
|
41
|
-
stripped =
|
43
|
+
stripped = strip_extension_if_elidable(short)
|
42
44
|
@lfi[short] = hash
|
43
45
|
@lfi[stripped] = hash
|
44
46
|
end
|
@@ -55,6 +57,13 @@ module Bootsnap
|
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
60
|
+
def purge_multi(features)
|
61
|
+
rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
|
62
|
+
@mutex.synchronize do
|
63
|
+
@lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
58
67
|
def key?(feature)
|
59
68
|
@mutex.synchronize { @lfi.key?(feature) }
|
60
69
|
end
|
@@ -85,13 +94,14 @@ module Bootsnap
|
|
85
94
|
|
86
95
|
hash = long.hash
|
87
96
|
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
#
|
97
|
+
# Do we have a filename with an elidable extension, e.g.,
|
98
|
+
# 'bundler.rb', or 'libgit2.so'?
|
99
|
+
altname = if extension_elidable?(short)
|
100
|
+
# Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
|
101
|
+
strip_extension_if_elidable(short)
|
102
|
+
elsif long && (ext = File.extname(long.freeze))
|
103
|
+
# We already know the extension of the actual file this
|
104
|
+
# resolves to, so put that back on.
|
95
105
|
short + ext
|
96
106
|
end
|
97
107
|
|
@@ -108,8 +118,30 @@ module Bootsnap
|
|
108
118
|
STRIP_EXTENSION = /\.[^.]*?$/
|
109
119
|
private_constant(:STRIP_EXTENSION)
|
110
120
|
|
111
|
-
|
112
|
-
|
121
|
+
# Might Ruby automatically search for this extension if
|
122
|
+
# someone tries to 'require' the file without it? E.g. Ruby
|
123
|
+
# will implicitly try 'x.rb' if you ask for 'x'.
|
124
|
+
#
|
125
|
+
# This is complex and platform-dependent, and the Ruby docs are a little
|
126
|
+
# handwavy about what will be tried when and in what order.
|
127
|
+
# So optimistically pretend that all known elidable extensions
|
128
|
+
# will be tried on all platforms, and that people are unlikely
|
129
|
+
# to name files in a way that assumes otherwise.
|
130
|
+
# (E.g. It's unlikely that someone will know that their code
|
131
|
+
# will _never_ run on MacOS, and therefore think they can get away
|
132
|
+
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
133
|
+
#
|
134
|
+
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
135
|
+
def extension_elidable?(f)
|
136
|
+
f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
|
137
|
+
end
|
138
|
+
|
139
|
+
def strip_extension_if_elidable(f)
|
140
|
+
if extension_elidable?(f)
|
141
|
+
f.sub(STRIP_EXTENSION, '')
|
142
|
+
else
|
143
|
+
f
|
144
|
+
end
|
113
145
|
end
|
114
146
|
end
|
115
147
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative('path_scanner')
|
2
3
|
|
3
4
|
module Bootsnap
|
@@ -20,7 +21,7 @@ module Bootsnap
|
|
20
21
|
attr_reader(:path)
|
21
22
|
|
22
23
|
def initialize(path)
|
23
|
-
@path = path.to_s
|
24
|
+
@path = path.to_s.freeze
|
24
25
|
end
|
25
26
|
|
26
27
|
# True if the path exists, but represents a non-directory object
|
@@ -59,7 +60,7 @@ module Bootsnap
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def expanded_path
|
62
|
-
File.expand_path(path)
|
63
|
+
File.expand_path(path).freeze
|
63
64
|
end
|
64
65
|
|
65
66
|
private
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative('../explicit_require')
|
2
4
|
|
3
5
|
module Bootsnap
|
4
6
|
module LoadPathCache
|
5
7
|
module PathScanner
|
6
|
-
ALL_FILES = "/{,**/*/**/}*"
|
7
8
|
REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
|
8
9
|
NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
|
9
10
|
ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
|
@@ -11,37 +12,62 @@ module Bootsnap
|
|
11
12
|
BUNDLE_PATH = if Bootsnap.bundler?
|
12
13
|
(Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
|
13
14
|
else
|
14
|
-
''
|
15
|
+
''
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
18
|
+
class << self
|
19
|
+
def call(path)
|
20
|
+
path = File.expand_path(path.to_s).freeze
|
21
|
+
return [[], []] unless File.directory?(path)
|
22
|
+
|
23
|
+
# If the bundle path is a descendent of this path, we do additional
|
24
|
+
# checks to prevent recursing into the bundle path as we recurse
|
25
|
+
# through this path. We don't want to scan the bundle path because
|
26
|
+
# anything useful in it will be present on other load path items.
|
27
|
+
#
|
28
|
+
# This can happen if, for example, the user adds '.' to the load path,
|
29
|
+
# and the bundle path is '.bundle'.
|
30
|
+
contains_bundle_path = BUNDLE_PATH.start_with?(path)
|
31
|
+
|
32
|
+
dirs = []
|
33
|
+
requirables = []
|
34
|
+
walk(path, nil) do |relative_path, absolute_path, is_directory|
|
35
|
+
if is_directory
|
36
|
+
dirs << os_path(relative_path)
|
37
|
+
!contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
|
38
|
+
elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
|
39
|
+
requirables << os_path(relative_path)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
[requirables, dirs]
|
43
|
+
end
|
44
|
+
|
45
|
+
def walk(absolute_dir_path, relative_dir_path, &block)
|
46
|
+
Dir.foreach(absolute_dir_path) do |name|
|
47
|
+
next if name.start_with?('.')
|
48
|
+
relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
|
49
|
+
|
50
|
+
absolute_path = "#{absolute_dir_path}/#{name}"
|
51
|
+
if File.directory?(absolute_path)
|
52
|
+
if yield relative_path, absolute_path, true
|
53
|
+
walk(absolute_path, relative_path, &block)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
yield relative_path, absolute_path, false
|
57
|
+
end
|
41
58
|
end
|
42
59
|
end
|
43
60
|
|
44
|
-
|
61
|
+
if RUBY_VERSION >= '3.1'
|
62
|
+
def os_path(path)
|
63
|
+
path.freeze
|
64
|
+
end
|
65
|
+
else
|
66
|
+
def os_path(path)
|
67
|
+
path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
|
68
|
+
path.freeze
|
69
|
+
end
|
70
|
+
end
|
45
71
|
end
|
46
72
|
end
|
47
73
|
end
|
@@ -15,15 +15,15 @@ module Bootsnap
|
|
15
15
|
|
16
16
|
def realpath(caller_location, path)
|
17
17
|
base = File.dirname(caller_location)
|
18
|
-
|
19
|
-
|
20
|
-
File.join(dir, File.basename(file))
|
18
|
+
abspath = File.expand_path(path, base).freeze
|
19
|
+
find_file(abspath)
|
21
20
|
end
|
22
21
|
|
23
22
|
def find_file(name)
|
24
|
-
|
23
|
+
return File.realpath(name).freeze if File.exist?(name)
|
24
|
+
CACHED_EXTENSIONS.each do |ext|
|
25
25
|
filename = "#{name}#{ext}"
|
26
|
-
return File.realpath(filename) if File.exist?(filename)
|
26
|
+
return File.realpath(filename).freeze if File.exist?(filename)
|
27
27
|
end
|
28
28
|
name
|
29
29
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative('../explicit_require')
|
2
3
|
|
3
4
|
Bootsnap::ExplicitRequire.with_gems('msgpack') { require('msgpack') }
|
@@ -11,7 +12,7 @@ module Bootsnap
|
|
11
12
|
|
12
13
|
def initialize(store_path)
|
13
14
|
@store_path = store_path
|
14
|
-
@
|
15
|
+
@txn_mutex = Mutex.new
|
15
16
|
@dirty = false
|
16
17
|
load_data
|
17
18
|
end
|
@@ -21,7 +22,7 @@ module Bootsnap
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def fetch(key)
|
24
|
-
raise(SetOutsideTransactionNotAllowed) unless @
|
25
|
+
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
25
26
|
v = get(key)
|
26
27
|
unless v
|
27
28
|
@dirty = true
|
@@ -32,7 +33,7 @@ module Bootsnap
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def set(key, value)
|
35
|
-
raise(SetOutsideTransactionNotAllowed) unless @
|
36
|
+
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
36
37
|
if value != @data[key]
|
37
38
|
@dirty = true
|
38
39
|
@data[key] = value
|
@@ -40,12 +41,14 @@ module Bootsnap
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def transaction
|
43
|
-
raise(NestedTransactionError) if @
|
44
|
-
@
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
raise(NestedTransactionError) if @txn_mutex.owned?
|
45
|
+
@txn_mutex.synchronize do
|
46
|
+
begin
|
47
|
+
yield
|
48
|
+
ensure
|
49
|
+
commit_transaction
|
50
|
+
end
|
51
|
+
end
|
49
52
|
end
|
50
53
|
|
51
54
|
private
|
@@ -59,10 +62,18 @@ module Bootsnap
|
|
59
62
|
|
60
63
|
def load_data
|
61
64
|
@data = begin
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
File.open(@store_path, encoding: Encoding::BINARY) do |io|
|
66
|
+
MessagePack.load(io)
|
67
|
+
end
|
68
|
+
# handle malformed data due to upgrade incompatibility
|
69
|
+
rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
|
70
|
+
{}
|
71
|
+
rescue ArgumentError => error
|
72
|
+
if error.message =~ /negative array size/
|
73
|
+
{}
|
74
|
+
else
|
75
|
+
raise
|
76
|
+
end
|
66
77
|
end
|
67
78
|
end
|
68
79
|
|
@@ -74,10 +85,13 @@ module Bootsnap
|
|
74
85
|
exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
|
75
86
|
# `encoding:` looks redundant wrt `binwrite`, but necessary on windows
|
76
87
|
# because binary is part of mode.
|
77
|
-
File.
|
88
|
+
File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
|
89
|
+
MessagePack.dump(@data, io, freeze: true)
|
90
|
+
end
|
78
91
|
FileUtils.mv(tmp, @store_path)
|
79
92
|
rescue Errno::EEXIST
|
80
93
|
retry
|
94
|
+
rescue SystemCallError
|
81
95
|
end
|
82
96
|
end
|
83
97
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bootsnap
|
2
4
|
module LoadPathCache
|
3
5
|
ReturnFalse = Class.new(StandardError)
|
@@ -7,6 +9,11 @@ module Bootsnap
|
|
7
9
|
DOT_SO = '.so'
|
8
10
|
SLASH = '/'
|
9
11
|
|
12
|
+
# If a NameError happens several levels deep, don't re-handle it
|
13
|
+
# all the way up the chain: mark it once and bubble it up without
|
14
|
+
# more retries.
|
15
|
+
ERROR_TAG_IVAR = :@__bootsnap_rescued
|
16
|
+
|
10
17
|
DL_EXTENSIONS = ::RbConfig::CONFIG
|
11
18
|
.values_at('DLEXT', 'DLEXT2')
|
12
19
|
.reject { |ext| !ext || ext.empty? }
|
@@ -21,10 +28,9 @@ module Bootsnap
|
|
21
28
|
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
22
29
|
|
23
30
|
class << self
|
24
|
-
attr_reader(:load_path_cache, :
|
25
|
-
:loaded_features_index, :realpath_cache)
|
31
|
+
attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
|
26
32
|
|
27
|
-
def setup(cache_path:, development_mode
|
33
|
+
def setup(cache_path:, development_mode:)
|
28
34
|
unless supported?
|
29
35
|
warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
|
30
36
|
return
|
@@ -38,23 +44,11 @@ module Bootsnap
|
|
38
44
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
39
45
|
require_relative('load_path_cache/core_ext/kernel_require')
|
40
46
|
require_relative('load_path_cache/core_ext/loaded_features')
|
41
|
-
|
42
|
-
if active_support
|
43
|
-
# this should happen after setting up the initial cache because it
|
44
|
-
# loads a lot of code. It's better to do after +require+ is optimized.
|
45
|
-
require('active_support/dependencies')
|
46
|
-
@autoload_paths_cache = Cache.new(
|
47
|
-
store,
|
48
|
-
::ActiveSupport::Dependencies.autoload_paths,
|
49
|
-
development_mode: development_mode
|
50
|
-
)
|
51
|
-
require_relative('load_path_cache/core_ext/active_support')
|
52
|
-
end
|
53
47
|
end
|
54
48
|
|
55
49
|
def supported?
|
56
50
|
RUBY_ENGINE == 'ruby' &&
|
57
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/
|
51
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
|
58
52
|
end
|
59
53
|
end
|
60
54
|
end
|
data/lib/bootsnap/setup.rb
CHANGED
@@ -1,35 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative('../bootsnap')
|
2
3
|
|
3
|
-
|
4
|
-
development_mode = ['', nil, 'development'].include?(env)
|
5
|
-
|
6
|
-
cache_dir = ENV['BOOTSNAP_CACHE_DIR']
|
7
|
-
unless cache_dir
|
8
|
-
config_dir_frame = caller.detect do |line|
|
9
|
-
line.include?('/config/')
|
10
|
-
end
|
11
|
-
|
12
|
-
unless config_dir_frame
|
13
|
-
$stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
|
14
|
-
$stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
|
15
|
-
$stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
|
16
|
-
|
17
|
-
raise("couldn't infer bootsnap cache directory")
|
18
|
-
end
|
19
|
-
|
20
|
-
path = config_dir_frame.split(/:\d+:/).first
|
21
|
-
path = File.dirname(path) until File.basename(path) == 'config'
|
22
|
-
app_root = File.dirname(path)
|
23
|
-
|
24
|
-
cache_dir = File.join(app_root, 'tmp', 'cache')
|
25
|
-
end
|
26
|
-
|
27
|
-
Bootsnap.setup(
|
28
|
-
cache_dir: cache_dir,
|
29
|
-
development_mode: development_mode,
|
30
|
-
load_path_cache: true,
|
31
|
-
autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
|
32
|
-
disable_trace: false,
|
33
|
-
compile_cache_iseq: true,
|
34
|
-
compile_cache_yaml: true,
|
35
|
-
)
|
4
|
+
Bootsnap.default_setup
|
data/lib/bootsnap/version.rb
CHANGED