bootsnap 1.4.0 → 1.4.5
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/.github/CODEOWNERS +2 -0
- data/.github/probots.yml +2 -0
- data/.travis.yml +0 -3
- data/CHANGELOG.md +22 -0
- data/README.md +27 -3
- data/ext/bootsnap/bootsnap.c +37 -7
- data/lib/bootsnap/compile_cache/iseq.rb +2 -0
- data/lib/bootsnap/compile_cache/yaml.rb +9 -5
- data/lib/bootsnap/compile_cache.rb +12 -0
- data/lib/bootsnap/load_path_cache/cache.rb +19 -1
- data/lib/bootsnap/load_path_cache/change_observer.rb +1 -0
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +13 -7
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +10 -0
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +10 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +9 -0
- data/lib/bootsnap/load_path_cache/path_scanner.rb +3 -1
- data/lib/bootsnap/load_path_cache/store.rb +17 -12
- data/lib/bootsnap/load_path_cache.rb +7 -0
- data/lib/bootsnap/setup.rb +4 -1
- data/lib/bootsnap/version.rb +1 -1
- data/shipit.rubygems.yml +0 -4
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be54a60d54f32f824d5c2fdccf189cdcb5409d1848c55f6e654fe44ace1d3f21
|
|
4
|
+
data.tar.gz: c6239c30984a936b76e218c2b1292a7e72d817d3e4847a6c58f0121c1e3068dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c251990457406cd51726eec8d62e0e1028bbeade6e24f266cb743d6f00f53650841beed9ee8e1795c78febd18ea234691125920c16aa5a95031718a28619bb31
|
|
7
|
+
data.tar.gz: 554a885bf2264f088618c41864fb84069d0664b35af0ae4b619c8fbb1cf285c80fad564d36845161f8044c35c6b53a33e66f27eb75c6715f25869af2795c97f6
|
data/.github/CODEOWNERS
ADDED
data/.github/probots.yml
ADDED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
# 1.4.5
|
|
2
|
+
|
|
3
|
+
* MRI 2.7 support
|
|
4
|
+
* Fixed concurrency bugs
|
|
5
|
+
|
|
6
|
+
# 1.4.4
|
|
7
|
+
|
|
8
|
+
* Disable ISeq cache in `bootsnap/setup` by default in Ruby 2.5
|
|
9
|
+
|
|
10
|
+
# 1.4.3
|
|
11
|
+
|
|
12
|
+
* Fix some cache permissions and umask issues after switch to mkstemp
|
|
13
|
+
|
|
14
|
+
# 1.4.2
|
|
15
|
+
|
|
16
|
+
* Fix bug when removing features loaded by relative path from `$LOADED_FEATURES`
|
|
17
|
+
* Fix bug with propagation of `NameError` up from nested calls to `require`
|
|
18
|
+
|
|
19
|
+
# 1.4.1
|
|
20
|
+
|
|
21
|
+
* Don't register change observers to frozen objects.
|
|
22
|
+
|
|
1
23
|
# 1.4.0
|
|
2
24
|
|
|
3
25
|
* When running in development mode, always fall back to a full path scan on LoadError, making
|
data/README.md
CHANGED
|
@@ -4,9 +4,14 @@ Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSup
|
|
|
4
4
|
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
|
5
5
|
|
|
6
6
|
#### Performance
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
- [Discourse](https://github.com/discourse/discourse) reports a boot time reduction of approximately
|
|
9
|
+
50%, from roughly 6 to 3 seconds on one machine;
|
|
8
10
|
- One of our smaller internal apps also sees a reduction of 50%, from 3.6 to 1.8 seconds;
|
|
9
|
-
- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
|
|
11
|
+
- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
|
|
12
|
+
dropping from around 25s to 6.5s.
|
|
13
|
+
* In Shopify core (a large app), about 25% of this gain can be attributed to `compile_cache_*`
|
|
14
|
+
features; 75% to path caching, and ~1% to `disable_trace`. This is fairly representative.
|
|
10
15
|
|
|
11
16
|
## Usage
|
|
12
17
|
|
|
@@ -24,6 +29,14 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
|
|
|
24
29
|
require 'bootsnap/setup'
|
|
25
30
|
```
|
|
26
31
|
|
|
32
|
+
Note that bootsnap writes to `tmp/cache`, and that directory *must* be writable. Rails will fail to
|
|
33
|
+
boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
|
|
34
|
+
unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
|
|
35
|
+
|
|
36
|
+
**Note also that bootsnap will never clean up its own cache: this is left up to you. Depending on your
|
|
37
|
+
deployment strategy, you may need to periodically purge `tmp/cache/bootsnap*`. If you notice deploys
|
|
38
|
+
getting progressively slower, this is almost certainly the cause.**
|
|
39
|
+
|
|
27
40
|
It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
|
|
28
41
|
important to load Bootsnap as early as possible to get maximum performance improvement.
|
|
29
42
|
|
|
@@ -41,12 +54,14 @@ Bootsnap.setup(
|
|
|
41
54
|
development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
|
|
42
55
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
|
43
56
|
autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
|
|
44
|
-
disable_trace: true, #
|
|
57
|
+
disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
|
|
45
58
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
|
46
59
|
compile_cache_yaml: true # Compile YAML into a cache
|
|
47
60
|
)
|
|
48
61
|
```
|
|
49
62
|
|
|
63
|
+
**Note that `disable_trace` will break debuggers and tracing.**
|
|
64
|
+
|
|
50
65
|
**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
|
|
51
66
|
'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
|
|
52
67
|
will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
|
|
@@ -278,3 +293,12 @@ open /c/nope.bundle -> -1
|
|
|
278
293
|
```
|
|
279
294
|
# (nothing!)
|
|
280
295
|
```
|
|
296
|
+
|
|
297
|
+
## When not to use Bootsnap
|
|
298
|
+
|
|
299
|
+
*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
|
|
300
|
+
engines.
|
|
301
|
+
|
|
302
|
+
*Non-local filesystems*: Bootsnap depends on `tmp/cache` (or whatever you set its cache directory
|
|
303
|
+
to) being on a relatively fast filesystem. If you put it on a network mount, bootsnap is very likely
|
|
304
|
+
to slow your application down quite a lot.
|
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -74,6 +74,8 @@ static uint32_t current_ruby_platform;
|
|
|
74
74
|
static uint32_t current_ruby_revision;
|
|
75
75
|
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
|
76
76
|
static uint32_t current_compile_option_crc32 = 0;
|
|
77
|
+
/* Current umask */
|
|
78
|
+
static mode_t current_umask;
|
|
77
79
|
|
|
78
80
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
|
79
81
|
static VALUE rb_mBootsnap;
|
|
@@ -94,6 +96,7 @@ static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
|
|
94
96
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
|
95
97
|
static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
|
|
96
98
|
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
|
|
99
|
+
static uint32_t get_ruby_revision(void);
|
|
97
100
|
static uint32_t get_ruby_platform(void);
|
|
98
101
|
|
|
99
102
|
/*
|
|
@@ -134,7 +137,7 @@ Init_bootsnap(void)
|
|
|
134
137
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
|
135
138
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
|
136
139
|
|
|
137
|
-
current_ruby_revision =
|
|
140
|
+
current_ruby_revision = get_ruby_revision();
|
|
138
141
|
current_ruby_platform = get_ruby_platform();
|
|
139
142
|
|
|
140
143
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
|
@@ -142,6 +145,9 @@ Init_bootsnap(void)
|
|
|
142
145
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
|
143
146
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
|
|
144
147
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
|
148
|
+
|
|
149
|
+
current_umask = umask(0777);
|
|
150
|
+
umask(current_umask);
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
/*
|
|
@@ -191,6 +197,26 @@ fnv1a_64(const char *str)
|
|
|
191
197
|
return fnv1a_64_iter(h, str);
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
/*
|
|
201
|
+
* Ruby's revision may be Integer or String. CRuby 2.7 or later uses
|
|
202
|
+
* Git commit ID as revision. It's String.
|
|
203
|
+
*/
|
|
204
|
+
static uint32_t
|
|
205
|
+
get_ruby_revision(void)
|
|
206
|
+
{
|
|
207
|
+
VALUE ruby_revision;
|
|
208
|
+
|
|
209
|
+
ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
|
|
210
|
+
if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
|
|
211
|
+
return FIX2INT(ruby_revision);
|
|
212
|
+
} else {
|
|
213
|
+
uint64_t hash;
|
|
214
|
+
|
|
215
|
+
hash = fnv1a_64(StringValueCStr(ruby_revision));
|
|
216
|
+
return (uint32_t)(hash >> 32);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
194
220
|
/*
|
|
195
221
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
|
196
222
|
* (or OS version), we need to invalidate the cache.
|
|
@@ -468,18 +494,17 @@ static int
|
|
|
468
494
|
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
|
469
495
|
{
|
|
470
496
|
char template[MAX_CACHEPATH_SIZE + 20];
|
|
471
|
-
char * dest;
|
|
472
497
|
char * tmp_path;
|
|
473
498
|
int fd, ret;
|
|
474
499
|
ssize_t nwrite;
|
|
475
500
|
|
|
476
|
-
|
|
477
|
-
strcat(
|
|
501
|
+
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
|
502
|
+
strcat(tmp_path, ".tmp.XXXXXX");
|
|
478
503
|
|
|
479
|
-
|
|
480
|
-
fd =
|
|
504
|
+
// mkstemp modifies the template to be the actual created path
|
|
505
|
+
fd = mkstemp(tmp_path);
|
|
481
506
|
if (fd < 0) {
|
|
482
|
-
if (mkpath(
|
|
507
|
+
if (mkpath(tmp_path, 0775) < 0) {
|
|
483
508
|
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
|
|
484
509
|
return -1;
|
|
485
510
|
}
|
|
@@ -517,6 +542,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
|
517
542
|
ret = rename(tmp_path, path);
|
|
518
543
|
if (ret < 0) {
|
|
519
544
|
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
|
|
545
|
+
return -1;
|
|
546
|
+
}
|
|
547
|
+
ret = chmod(path, 0664 & ~current_umask);
|
|
548
|
+
if (ret < 0) {
|
|
549
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:chmod";
|
|
520
550
|
}
|
|
521
551
|
return ret;
|
|
522
552
|
}
|
|
@@ -46,11 +46,15 @@ module Bootsnap
|
|
|
46
46
|
|
|
47
47
|
klass = class << ::YAML; self; end
|
|
48
48
|
klass.send(:define_method, :load_file) do |path|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
begin
|
|
50
|
+
Bootsnap::CompileCache::Native.fetch(
|
|
51
|
+
cache_dir,
|
|
52
|
+
path,
|
|
53
|
+
Bootsnap::CompileCache::YAML
|
|
54
|
+
)
|
|
55
|
+
rescue Errno::EACCES
|
|
56
|
+
Bootsnap::CompileCache.permission_error(path)
|
|
57
|
+
end
|
|
54
58
|
end
|
|
55
59
|
end
|
|
56
60
|
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
module Bootsnap
|
|
2
2
|
module CompileCache
|
|
3
|
+
Error = Class.new(StandardError)
|
|
4
|
+
PermissionError = Class.new(Error)
|
|
5
|
+
|
|
3
6
|
def self.setup(cache_dir:, iseq:, yaml:)
|
|
4
7
|
if iseq
|
|
5
8
|
if supported?
|
|
@@ -20,6 +23,15 @@ module Bootsnap
|
|
|
20
23
|
end
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
def self.permission_error(path)
|
|
27
|
+
cpath = Bootsnap::CompileCache::ISeq.cache_dir
|
|
28
|
+
raise(
|
|
29
|
+
PermissionError,
|
|
30
|
+
"bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
|
|
31
|
+
"(or, less likely, doesn't have permission to read '#{path}')",
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
23
35
|
def self.supported?
|
|
24
36
|
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
|
25
37
|
RUBY_ENGINE == 'ruby' &&
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative('../explicit_require')
|
|
2
4
|
|
|
3
5
|
module Bootsnap
|
|
@@ -46,7 +48,7 @@ module Bootsnap
|
|
|
46
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
|
47
49
|
feature = feature.to_s
|
|
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
|
|
@@ -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,10 +180,18 @@ 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
197
|
def try_index(f)
|
|
@@ -185,6 +199,10 @@ module Bootsnap
|
|
|
185
199
|
p + '/' + f
|
|
186
200
|
end
|
|
187
201
|
end
|
|
202
|
+
|
|
203
|
+
def try_ext(f)
|
|
204
|
+
f if File.exist?(f)
|
|
205
|
+
end
|
|
188
206
|
end
|
|
189
207
|
end
|
|
190
208
|
end
|
|
@@ -60,19 +60,22 @@ module Bootsnap
|
|
|
60
60
|
super
|
|
61
61
|
end
|
|
62
62
|
rescue NameError => e
|
|
63
|
+
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
|
64
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
65
|
+
|
|
63
66
|
# This function can end up called recursively, we only want to
|
|
64
67
|
# retry at the top-level.
|
|
65
|
-
raise if Thread.current[:without_bootsnap_retry]
|
|
68
|
+
raise(e) if Thread.current[:without_bootsnap_retry]
|
|
66
69
|
# If we already had cache disabled, there's no use retrying
|
|
67
|
-
raise if Thread.current[:without_bootsnap_cache]
|
|
70
|
+
raise(e) if Thread.current[:without_bootsnap_cache]
|
|
68
71
|
# NoMethodError is a NameError, but we only want to handle actual
|
|
69
72
|
# NameError instances.
|
|
70
|
-
raise unless e.class == NameError
|
|
73
|
+
raise(e) unless e.class == NameError
|
|
71
74
|
# We can only confidently handle cases when *this* constant fails
|
|
72
75
|
# to load, not other constants referred to by it.
|
|
73
|
-
raise unless e.name == const_name
|
|
76
|
+
raise(e) unless e.name == const_name
|
|
74
77
|
# If the constant was actually loaded, something else went wrong?
|
|
75
|
-
raise if from_mod.const_defined?(const_name)
|
|
78
|
+
raise(e) if from_mod.const_defined?(const_name)
|
|
76
79
|
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
|
77
80
|
end
|
|
78
81
|
|
|
@@ -80,9 +83,12 @@ module Bootsnap
|
|
|
80
83
|
# reiterate it with version polymorphism here...
|
|
81
84
|
def depend_on(*)
|
|
82
85
|
super
|
|
83
|
-
rescue LoadError
|
|
86
|
+
rescue LoadError => e
|
|
87
|
+
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
|
88
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
89
|
+
|
|
84
90
|
# If we already had cache disabled, there's no use retrying
|
|
85
|
-
raise if Thread.current[:without_bootsnap_cache]
|
|
91
|
+
raise(e) if Thread.current[:without_bootsnap_cache]
|
|
86
92
|
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
|
87
93
|
end
|
|
88
94
|
end
|
|
@@ -3,6 +3,7 @@ module Bootsnap
|
|
|
3
3
|
module CoreExt
|
|
4
4
|
def self.make_load_error(path)
|
|
5
5
|
err = LoadError.new("cannot load such file -- #{path}")
|
|
6
|
+
err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
6
7
|
err.define_singleton_method(:path) { path }
|
|
7
8
|
err
|
|
8
9
|
end
|
|
@@ -30,6 +31,9 @@ module Kernel
|
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
|
34
|
+
rescue LoadError => e
|
|
35
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
36
|
+
raise(e)
|
|
33
37
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
34
38
|
false
|
|
35
39
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
@@ -56,6 +60,9 @@ module Kernel
|
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
|
63
|
+
rescue LoadError => e
|
|
64
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
65
|
+
raise(e)
|
|
59
66
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
60
67
|
false
|
|
61
68
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
@@ -74,6 +81,9 @@ class Module
|
|
|
74
81
|
# added to $LOADED_FEATURES and won't be able to hook that modification
|
|
75
82
|
# since it's done in C-land.
|
|
76
83
|
autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
|
|
84
|
+
rescue LoadError => e
|
|
85
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
86
|
+
raise(e)
|
|
77
87
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
78
88
|
false
|
|
79
89
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
@@ -4,4 +4,14 @@ class << $LOADED_FEATURES
|
|
|
4
4
|
Bootsnap::LoadPathCache.loaded_features_index.purge(key)
|
|
5
5
|
delete_without_bootsnap(key)
|
|
6
6
|
end
|
|
7
|
+
|
|
8
|
+
alias_method(:reject_without_bootsnap!, :reject!)
|
|
9
|
+
def reject!(&block)
|
|
10
|
+
backup = dup
|
|
11
|
+
|
|
12
|
+
# FIXME: if no block is passed we'd need to return a decorated iterator
|
|
13
|
+
reject_without_bootsnap!(&block)
|
|
14
|
+
|
|
15
|
+
Bootsnap::LoadPathCache.loaded_features_index.purge_multi(backup - self)
|
|
16
|
+
end
|
|
7
17
|
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
|
|
@@ -55,6 +57,13 @@ module Bootsnap
|
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
|
|
60
|
+
def purge_multi(features)
|
|
61
|
+
rejected_hashes = features.map(&:hash).to_set
|
|
62
|
+
@mutex.synchronize do
|
|
63
|
+
@lfi.reject! { |_, hash| rejected_hashes.include?(hash) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
58
67
|
def key?(feature)
|
|
59
68
|
@mutex.synchronize { @lfi.key?(feature) }
|
|
60
69
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative('../explicit_require')
|
|
2
4
|
|
|
3
5
|
module Bootsnap
|
|
@@ -11,7 +13,7 @@ module Bootsnap
|
|
|
11
13
|
BUNDLE_PATH = if Bootsnap.bundler?
|
|
12
14
|
(Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
|
|
13
15
|
else
|
|
14
|
-
''
|
|
16
|
+
''
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def self.call(path)
|
|
@@ -11,7 +11,8 @@ module Bootsnap
|
|
|
11
11
|
|
|
12
12
|
def initialize(store_path)
|
|
13
13
|
@store_path = store_path
|
|
14
|
-
|
|
14
|
+
# TODO: Remove conditional once Ruby 2.2 support is dropped.
|
|
15
|
+
@txn_mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::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
|
|
@@ -60,9 +63,11 @@ module Bootsnap
|
|
|
60
63
|
def load_data
|
|
61
64
|
@data = begin
|
|
62
65
|
MessagePack.load(File.binread(@store_path))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
# handle malformed data due to upgrade incompatability
|
|
67
|
+
rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
|
|
68
|
+
{}
|
|
69
|
+
rescue ArgumentError => e
|
|
70
|
+
e.message =~ /negative array size/ ? {} : raise
|
|
66
71
|
end
|
|
67
72
|
end
|
|
68
73
|
|
|
@@ -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? }
|
data/lib/bootsnap/setup.rb
CHANGED
|
@@ -24,12 +24,15 @@ unless cache_dir
|
|
|
24
24
|
cache_dir = File.join(app_root, 'tmp', 'cache')
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
|
28
|
+
iseq_cache_enabled = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
|
|
29
|
+
|
|
27
30
|
Bootsnap.setup(
|
|
28
31
|
cache_dir: cache_dir,
|
|
29
32
|
development_mode: development_mode,
|
|
30
33
|
load_path_cache: true,
|
|
31
34
|
autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
|
|
32
35
|
disable_trace: false,
|
|
33
|
-
compile_cache_iseq:
|
|
36
|
+
compile_cache_iseq: iseq_cache_enabled,
|
|
34
37
|
compile_cache_yaml: true,
|
|
35
38
|
)
|
data/lib/bootsnap/version.rb
CHANGED
data/shipit.rubygems.yml
CHANGED
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.
|
|
4
|
+
version: 1.4.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Burke Libbey
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-
|
|
11
|
+
date: 2019-08-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -102,6 +102,8 @@ extensions:
|
|
|
102
102
|
- ext/bootsnap/extconf.rb
|
|
103
103
|
extra_rdoc_files: []
|
|
104
104
|
files:
|
|
105
|
+
- ".github/CODEOWNERS"
|
|
106
|
+
- ".github/probots.yml"
|
|
105
107
|
- ".gitignore"
|
|
106
108
|
- ".rubocop.yml"
|
|
107
109
|
- ".travis.yml"
|