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