bootsnap 1.11.1 → 1.18.3
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 +76 -0
- data/README.md +19 -10
- data/ext/bootsnap/bootsnap.c +253 -112
- data/ext/bootsnap/extconf.rb +20 -13
- data/lib/bootsnap/bundler.rb +1 -1
- data/lib/bootsnap/cli.rb +18 -16
- data/lib/bootsnap/compile_cache/iseq.rb +14 -8
- data/lib/bootsnap/compile_cache/json.rb +18 -17
- data/lib/bootsnap/compile_cache/yaml.rb +46 -60
- data/lib/bootsnap/compile_cache.rb +11 -15
- data/lib/bootsnap/load_path_cache/cache.rb +20 -21
- data/lib/bootsnap/load_path_cache/change_observer.rb +19 -2
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +7 -35
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +2 -2
- data/lib/bootsnap/load_path_cache/path.rb +16 -18
- data/lib/bootsnap/load_path_cache/path_scanner.rb +7 -1
- data/lib/bootsnap/load_path_cache/store.rb +11 -14
- data/lib/bootsnap/load_path_cache.rb +36 -13
- data/lib/bootsnap/setup.rb +1 -1
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +36 -32
- metadata +4 -4
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kernel
|
4
|
-
|
4
|
+
alias_method :require_without_bootsnap, :require
|
5
5
|
|
6
|
-
alias_method
|
6
|
+
alias_method :require, :require # Avoid method redefinition warnings
|
7
|
+
|
8
|
+
def require(path) # rubocop:disable Lint/DuplicateMethods
|
9
|
+
return require_without_bootsnap(path) unless Bootsnap::LoadPathCache.enabled?
|
7
10
|
|
8
|
-
def require(path)
|
9
11
|
string_path = Bootsnap.rb_get_path(path)
|
10
12
|
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
|
11
13
|
|
@@ -22,9 +24,7 @@ module Kernel
|
|
22
24
|
elsif false == resolved
|
23
25
|
return false
|
24
26
|
elsif resolved.nil?
|
25
|
-
|
26
|
-
error.instance_variable_set(:@path, path)
|
27
|
-
raise error
|
27
|
+
return require_without_bootsnap(path)
|
28
28
|
else
|
29
29
|
# Note that require registers to $LOADED_FEATURES while load does not.
|
30
30
|
ret = require_without_bootsnap(resolved)
|
@@ -33,33 +33,5 @@ module Kernel
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
def load(path, wrap = false)
|
38
|
-
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path), try_extensions: false))
|
39
|
-
load_without_bootsnap(resolved, wrap)
|
40
|
-
else
|
41
|
-
load_without_bootsnap(path, wrap)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class Module
|
47
|
-
alias_method(:autoload_without_bootsnap, :autoload)
|
48
|
-
def autoload(const, path)
|
49
|
-
# NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately
|
50
|
-
# obvious how to make it work. This feels like a pretty niche case, unclear
|
51
|
-
# if it will ever burn anyone.
|
52
|
-
#
|
53
|
-
# The challenge is that we don't control the point at which the entry gets
|
54
|
-
# added to $LOADED_FEATURES and won't be able to hook that modification
|
55
|
-
# since it's done in C-land.
|
56
|
-
resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path))
|
57
|
-
if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
|
58
|
-
autoload_without_bootsnap(const, path)
|
59
|
-
elsif resolved == false
|
60
|
-
return false
|
61
|
-
else
|
62
|
-
autoload_without_bootsnap(const, resolved || path)
|
63
|
-
end
|
64
|
-
end
|
36
|
+
private :require
|
65
37
|
end
|
@@ -40,7 +40,7 @@ module Bootsnap
|
|
40
40
|
|
41
41
|
# /a/b/lib/my/foo.rb
|
42
42
|
# ^^^^^^^^^
|
43
|
-
short = feat[(lpe.length + 1)
|
43
|
+
short = feat[(lpe.length + 1)..]
|
44
44
|
stripped = strip_extension_if_elidable(short)
|
45
45
|
@lfi[short] = hash
|
46
46
|
@lfi[stripped] = hash
|
@@ -76,7 +76,7 @@ module Bootsnap
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def identify(short, cursor)
|
79
|
-
$LOADED_FEATURES[cursor
|
79
|
+
$LOADED_FEATURES[cursor..].detect do |feat|
|
80
80
|
offset = 0
|
81
81
|
while (offset = feat.index(short, offset))
|
82
82
|
if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "path_scanner"
|
4
4
|
|
5
5
|
module Bootsnap
|
6
6
|
module LoadPathCache
|
@@ -35,18 +35,18 @@ module Bootsnap
|
|
35
35
|
return self
|
36
36
|
end
|
37
37
|
|
38
|
-
if realpath
|
39
|
-
Path.new(realpath, real: true)
|
40
|
-
else
|
38
|
+
if realpath == path
|
41
39
|
@real = true
|
42
40
|
self
|
41
|
+
else
|
42
|
+
Path.new(realpath, real: true)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
# True if the path exists, but represents a non-directory object
|
47
47
|
def non_directory?
|
48
48
|
!File.stat(path).directory?
|
49
|
-
rescue Errno::ENOENT, Errno::ENOTDIR
|
49
|
+
rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
|
50
50
|
false
|
51
51
|
end
|
52
52
|
|
@@ -101,7 +101,7 @@ module Bootsnap
|
|
101
101
|
["", *dirs].each do |dir|
|
102
102
|
curr = begin
|
103
103
|
File.mtime("#{path}/#{dir}").to_i
|
104
|
-
rescue Errno::ENOENT, Errno::ENOTDIR
|
104
|
+
rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
|
105
105
|
-1
|
106
106
|
end
|
107
107
|
max = curr if curr > max
|
@@ -116,21 +116,19 @@ module Bootsnap
|
|
116
116
|
VOLATILE = :volatile
|
117
117
|
|
118
118
|
# Built-in ruby lib stuff doesn't change, but things can occasionally be
|
119
|
-
# installed into sitedir, which generally lives under
|
120
|
-
RUBY_LIBDIR = RbConfig::CONFIG["
|
119
|
+
# installed into sitedir, which generally lives under rubylibdir.
|
120
|
+
RUBY_LIBDIR = RbConfig::CONFIG["rubylibdir"]
|
121
121
|
RUBY_SITEDIR = RbConfig::CONFIG["sitedir"]
|
122
122
|
|
123
123
|
def stability
|
124
|
-
@stability ||=
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
VOLATILE
|
133
|
-
end
|
124
|
+
@stability ||= if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) }
|
125
|
+
STABLE
|
126
|
+
elsif Bootsnap.bundler? && expanded_path.start_with?(Bundler.bundle_path.to_s)
|
127
|
+
STABLE
|
128
|
+
elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR)
|
129
|
+
STABLE
|
130
|
+
else
|
131
|
+
VOLATILE
|
134
132
|
end
|
135
133
|
end
|
136
134
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "../explicit_require"
|
4
4
|
|
5
5
|
module Bootsnap
|
6
6
|
module LoadPathCache
|
@@ -15,7 +15,11 @@ module Bootsnap
|
|
15
15
|
""
|
16
16
|
end
|
17
17
|
|
18
|
+
@ignored_directories = %w(node_modules)
|
19
|
+
|
18
20
|
class << self
|
21
|
+
attr_accessor :ignored_directories
|
22
|
+
|
19
23
|
def call(path)
|
20
24
|
path = File.expand_path(path.to_s).freeze
|
21
25
|
return [[], []] unless File.directory?(path)
|
@@ -50,6 +54,8 @@ module Bootsnap
|
|
50
54
|
|
51
55
|
absolute_path = "#{absolute_dir_path}/#{name}"
|
52
56
|
if File.directory?(absolute_path)
|
57
|
+
next if ignored_directories.include?(name) || ignored_directories.include?(absolute_path)
|
58
|
+
|
53
59
|
if yield relative_path, absolute_path, true
|
54
60
|
walk(absolute_path, relative_path, &block)
|
55
61
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "../explicit_require"
|
4
4
|
|
5
|
-
Bootsnap::ExplicitRequire.with_gems("msgpack") { require
|
5
|
+
Bootsnap::ExplicitRequire.with_gems("msgpack") { require "msgpack" }
|
6
6
|
|
7
7
|
module Bootsnap
|
8
8
|
module LoadPathCache
|
@@ -13,10 +13,11 @@ module Bootsnap
|
|
13
13
|
NestedTransactionError = Class.new(StandardError)
|
14
14
|
SetOutsideTransactionNotAllowed = Class.new(StandardError)
|
15
15
|
|
16
|
-
def initialize(store_path)
|
16
|
+
def initialize(store_path, readonly: false)
|
17
17
|
@store_path = store_path
|
18
18
|
@txn_mutex = Mutex.new
|
19
19
|
@dirty = false
|
20
|
+
@readonly = readonly
|
20
21
|
load_data
|
21
22
|
end
|
22
23
|
|
@@ -49,11 +50,9 @@ module Bootsnap
|
|
49
50
|
raise(NestedTransactionError) if @txn_mutex.owned?
|
50
51
|
|
51
52
|
@txn_mutex.synchronize do
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
commit_transaction
|
56
|
-
end
|
53
|
+
yield
|
54
|
+
ensure
|
55
|
+
commit_transaction
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
@@ -65,7 +64,7 @@ module Bootsnap
|
|
65
64
|
end
|
66
65
|
|
67
66
|
def commit_transaction
|
68
|
-
if @dirty
|
67
|
+
if @dirty && !@readonly
|
69
68
|
dump_data
|
70
69
|
@dirty = false
|
71
70
|
end
|
@@ -121,11 +120,9 @@ module Bootsnap
|
|
121
120
|
path = File.dirname(path)
|
122
121
|
end
|
123
122
|
stack.reverse_each do |dir|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
raise unless File.directory?(dir)
|
128
|
-
end
|
123
|
+
Dir.mkdir(dir)
|
124
|
+
rescue SystemCallError
|
125
|
+
raise unless File.directory?(dir)
|
129
126
|
end
|
130
127
|
end
|
131
128
|
end
|
@@ -21,37 +21,60 @@ module Bootsnap
|
|
21
21
|
|
22
22
|
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
23
23
|
|
24
|
+
@enabled = false
|
25
|
+
|
24
26
|
class << self
|
25
|
-
attr_reader(:load_path_cache, :loaded_features_index)
|
27
|
+
attr_reader(:load_path_cache, :loaded_features_index, :enabled)
|
28
|
+
alias_method :enabled?, :enabled
|
29
|
+
remove_method(:enabled)
|
26
30
|
|
27
|
-
def setup(cache_path:, development_mode:)
|
31
|
+
def setup(cache_path:, development_mode:, ignore_directories:, readonly: false)
|
28
32
|
unless supported?
|
29
33
|
warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
|
30
34
|
return
|
31
35
|
end
|
32
36
|
|
33
|
-
store = Store.new(cache_path)
|
37
|
+
store = Store.new(cache_path, readonly: readonly)
|
34
38
|
|
35
39
|
@loaded_features_index = LoadedFeaturesIndex.new
|
36
40
|
|
41
|
+
PathScanner.ignored_directories = ignore_directories if ignore_directories
|
37
42
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
38
|
-
|
39
|
-
require_relative
|
43
|
+
@enabled = true
|
44
|
+
require_relative "load_path_cache/core_ext/kernel_require"
|
45
|
+
require_relative "load_path_cache/core_ext/loaded_features"
|
46
|
+
end
|
47
|
+
|
48
|
+
def unload!
|
49
|
+
@enabled = false
|
50
|
+
@loaded_features_index = nil
|
51
|
+
@realpath_cache = nil
|
52
|
+
@load_path_cache = nil
|
53
|
+
ChangeObserver.unregister($LOAD_PATH) if supported?
|
40
54
|
end
|
41
55
|
|
42
56
|
def supported?
|
43
|
-
|
44
|
-
|
57
|
+
if RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
|
58
|
+
case RUBY_ENGINE
|
59
|
+
when "truffleruby"
|
60
|
+
# https://github.com/oracle/truffleruby/issues/3131
|
61
|
+
RUBY_ENGINE_VERSION >= "23.1.0"
|
62
|
+
when "ruby"
|
63
|
+
true
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
45
68
|
end
|
46
69
|
end
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
50
73
|
if Bootsnap::LoadPathCache.supported?
|
51
|
-
require_relative
|
52
|
-
require_relative
|
53
|
-
require_relative
|
54
|
-
require_relative
|
55
|
-
require_relative
|
56
|
-
require_relative
|
74
|
+
require_relative "load_path_cache/path_scanner"
|
75
|
+
require_relative "load_path_cache/path"
|
76
|
+
require_relative "load_path_cache/cache"
|
77
|
+
require_relative "load_path_cache/store"
|
78
|
+
require_relative "load_path_cache/change_observer"
|
79
|
+
require_relative "load_path_cache/loaded_features_index"
|
57
80
|
end
|
data/lib/bootsnap/setup.rb
CHANGED
data/lib/bootsnap/version.rb
CHANGED
data/lib/bootsnap.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
3
|
+
require_relative "bootsnap/version"
|
4
|
+
require_relative "bootsnap/bundler"
|
5
|
+
require_relative "bootsnap/load_path_cache"
|
6
|
+
require_relative "bootsnap/compile_cache"
|
7
7
|
|
8
8
|
module Bootsnap
|
9
9
|
InvalidConfiguration = Class.new(StandardError)
|
@@ -11,6 +11,16 @@ module Bootsnap
|
|
11
11
|
class << self
|
12
12
|
attr_reader :logger
|
13
13
|
|
14
|
+
def log_stats!
|
15
|
+
stats = {hit: 0, revalidated: 0, miss: 0, stale: 0}
|
16
|
+
self.instrumentation = ->(event, _path) { stats[event] += 1 }
|
17
|
+
Kernel.at_exit do
|
18
|
+
stats.each do |event, count|
|
19
|
+
$stderr.puts "bootsnap #{event}: #{count}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
14
24
|
def log!
|
15
25
|
self.logger = $stderr.method(:puts)
|
16
26
|
end
|
@@ -18,9 +28,9 @@ module Bootsnap
|
|
18
28
|
def logger=(logger)
|
19
29
|
@logger = logger
|
20
30
|
self.instrumentation = if logger.respond_to?(:debug)
|
21
|
-
->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
|
31
|
+
->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") unless event == :hit }
|
22
32
|
else
|
23
|
-
->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
|
33
|
+
->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") unless event == :hit }
|
24
34
|
end
|
25
35
|
end
|
26
36
|
|
@@ -39,48 +49,34 @@ module Bootsnap
|
|
39
49
|
cache_dir:,
|
40
50
|
development_mode: true,
|
41
51
|
load_path_cache: true,
|
42
|
-
|
43
|
-
|
52
|
+
ignore_directories: nil,
|
53
|
+
readonly: false,
|
54
|
+
revalidation: false,
|
44
55
|
compile_cache_iseq: true,
|
45
56
|
compile_cache_yaml: true,
|
46
57
|
compile_cache_json: true
|
47
58
|
)
|
48
|
-
unless autoload_paths_cache.nil?
|
49
|
-
warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
|
50
|
-
"If you use Zeitwerk this option is useless, and if you are still using the classic autoloader " \
|
51
|
-
"upgrading is recommended."
|
52
|
-
end
|
53
|
-
|
54
|
-
unless disable_trace.nil?
|
55
|
-
warn "[DEPRECATED] Bootsnap's `disable_trace:` option is deprecated and will be removed. " \
|
56
|
-
"If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
|
57
|
-
end
|
58
|
-
|
59
|
-
if compile_cache_iseq && !iseq_cache_supported?
|
60
|
-
warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \
|
61
|
-
"to turn `compile_cache_iseq` off on Ruby 2.5"
|
62
|
-
end
|
63
|
-
|
64
59
|
if load_path_cache
|
65
60
|
Bootsnap::LoadPathCache.setup(
|
66
|
-
cache_path: cache_dir
|
61
|
+
cache_path: "#{cache_dir}/bootsnap/load-path-cache",
|
67
62
|
development_mode: development_mode,
|
63
|
+
ignore_directories: ignore_directories,
|
64
|
+
readonly: readonly,
|
68
65
|
)
|
69
66
|
end
|
70
67
|
|
71
68
|
Bootsnap::CompileCache.setup(
|
72
|
-
cache_dir: cache_dir
|
69
|
+
cache_dir: "#{cache_dir}/bootsnap/compile-cache",
|
73
70
|
iseq: compile_cache_iseq,
|
74
71
|
yaml: compile_cache_yaml,
|
75
72
|
json: compile_cache_json,
|
73
|
+
readonly: readonly,
|
74
|
+
revalidation: revalidation,
|
76
75
|
)
|
77
76
|
end
|
78
77
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
82
|
-
ruby_version = Gem::Version.new(RUBY_VERSION)
|
83
|
-
@iseq_cache_supported = ruby_version < Gem::Version.new("2.5.0") || ruby_version >= Gem::Version.new("2.6.0")
|
78
|
+
def unload_cache!
|
79
|
+
LoadPathCache.unload!
|
84
80
|
end
|
85
81
|
|
86
82
|
def default_setup
|
@@ -109,17 +105,25 @@ module Bootsnap
|
|
109
105
|
cache_dir = File.join(app_root, "tmp", "cache")
|
110
106
|
end
|
111
107
|
|
108
|
+
ignore_directories = if ENV.key?("BOOTSNAP_IGNORE_DIRECTORIES")
|
109
|
+
ENV["BOOTSNAP_IGNORE_DIRECTORIES"].split(",")
|
110
|
+
end
|
111
|
+
|
112
112
|
setup(
|
113
113
|
cache_dir: cache_dir,
|
114
114
|
development_mode: development_mode,
|
115
115
|
load_path_cache: !ENV["DISABLE_BOOTSNAP_LOAD_PATH_CACHE"],
|
116
|
-
compile_cache_iseq: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"]
|
116
|
+
compile_cache_iseq: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
|
117
117
|
compile_cache_yaml: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
|
118
118
|
compile_cache_json: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
|
119
|
+
readonly: !!ENV["BOOTSNAP_READONLY"],
|
120
|
+
ignore_directories: ignore_directories,
|
119
121
|
)
|
120
122
|
|
121
123
|
if ENV["BOOTSNAP_LOG"]
|
122
124
|
log!
|
125
|
+
elsif ENV["BOOTSNAP_STATS"]
|
126
|
+
log_stats!
|
123
127
|
end
|
124
128
|
end
|
125
129
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bootsnap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.18.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -76,14 +76,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
76
|
requirements:
|
77
77
|
- - ">="
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version: 2.
|
79
|
+
version: 2.6.0
|
80
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
82
|
- - ">="
|
83
83
|
- !ruby/object:Gem::Version
|
84
84
|
version: '0'
|
85
85
|
requirements: []
|
86
|
-
rubygems_version: 3.
|
86
|
+
rubygems_version: 3.5.5
|
87
87
|
signing_key:
|
88
88
|
specification_version: 4
|
89
89
|
summary: Boot large ruby/rails apps faster
|