bootsnap 1.16.0 → 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 +39 -2
- data/README.md +9 -6
- data/ext/bootsnap/bootsnap.c +232 -58
- data/ext/bootsnap/extconf.rb +19 -12
- data/lib/bootsnap/bundler.rb +1 -1
- data/lib/bootsnap/cli.rb +7 -5
- data/lib/bootsnap/compile_cache/iseq.rb +11 -5
- data/lib/bootsnap/compile_cache/json.rb +9 -13
- data/lib/bootsnap/compile_cache/yaml.rb +28 -44
- data/lib/bootsnap/compile_cache.rb +8 -16
- data/lib/bootsnap/load_path_cache/cache.rb +9 -1
- data/lib/bootsnap/load_path_cache/change_observer.rb +6 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +6 -6
- data/lib/bootsnap/load_path_cache/path.rb +1 -1
- data/lib/bootsnap/load_path_cache/path_scanner.rb +2 -2
- data/lib/bootsnap/load_path_cache/store.rb +2 -2
- data/lib/bootsnap/load_path_cache.rb +21 -12
- data/lib/bootsnap/setup.rb +1 -1
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +20 -6
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fa4ab785277ee01a1c8ee75b43f0efb93db42bffcdacc1c8505a65efa03dede
|
4
|
+
data.tar.gz: 8aaaca48ae257b563580023c8fa36a59463f4c30f5463c14f6b8b94bf5fe27df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27b48d27d3330c8565952a2fbb979e71013b1e9585bcb3284656192808c304f2874c32a135b14895eec61a7ef038fa71fa111964a56e7aaedc9ff507ef307686
|
7
|
+
data.tar.gz: c3d83a0b068f2908a6298c7cd8e1a660f1228a7ddbfb9409cb3f6c174319f3974388ce73756c04062b17b97a37e199a07bccbb0b8dc1c6224998c58c51194b27
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,42 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 1.18.3
|
4
|
+
|
5
|
+
* Fix the cache corruption issue in the revalidation feature. See #474.
|
6
|
+
The cache revalidation feature remains opt-in for now, until it is more battle tested.
|
7
|
+
|
8
|
+
# 1.18.2
|
9
|
+
|
10
|
+
* Disable stale cache entries revalidation by default as it seems to cause cache corruption issues. See #471 and #474.
|
11
|
+
Will be re-enabled in a future version once the root cause is identified.
|
12
|
+
* Fix a potential compilation issue on some systems. See #470.
|
13
|
+
|
14
|
+
# 1.18.1
|
15
|
+
|
16
|
+
* Handle `EPERM` errors when opening files with `O_NOATIME`.
|
17
|
+
|
18
|
+
# 1.18.0
|
19
|
+
|
20
|
+
* `Bootsnap.instrumentation` now receive `:hit` events.
|
21
|
+
* Add `Bootsnap.log_stats!` to print hit rate statistics on process exit. Can also be enabled with `BOOTSNAP_STATS=1`.
|
22
|
+
* Revalidate stale cache entries by digesting the source content.
|
23
|
+
This should significantly improve performance in environments where `mtime` isn't preserved (e.g. CI systems doing a git clone, etc).
|
24
|
+
See #468.
|
25
|
+
* Open source files and cache entries with `O_NOATIME` when available to reduce disk accesses. See #469.
|
26
|
+
* `bootsnap precompile --gemfile` now look for `.rb` files in the whole gem and not just the `lib/` directory. See #466.
|
27
|
+
|
28
|
+
# 1.17.1
|
29
|
+
|
30
|
+
* Fix a compatibility issue with the `prism` library that ships with Ruby 3.3. See #463.
|
31
|
+
* Improved the `Kernel#require` decorator to not cause a method redefinition warning. See #461.
|
32
|
+
|
33
|
+
# 1.17.0
|
34
|
+
|
35
|
+
* Ensure `$LOAD_PATH.dup` is Ractor shareable to fix an conflict with `did_you_mean`.
|
36
|
+
* Allow to ignore directories using absolute paths.
|
37
|
+
* Support YAML and JSON CompileCache on TruffleRuby.
|
38
|
+
* Support LoadPathCache on TruffleRuby.
|
39
|
+
|
3
40
|
# 1.16.0
|
4
41
|
|
5
42
|
* Use `RbConfig::CONFIG["rubylibdir"]` instead of `RbConfig::CONFIG["libdir"]` to check for stdlib files. See #431.
|
@@ -17,7 +54,7 @@
|
|
17
54
|
* Add a way to skip directories during load path scanning.
|
18
55
|
If you have large non-ruby directories in the middle of your load path, it can severely slow down scanning.
|
19
56
|
Typically this is a problem with `node_modules`. See #277.
|
20
|
-
* Fix `Bootsnap.unload_cache!`, it simply wouldn't work at all
|
57
|
+
* Fix `Bootsnap.unload_cache!`, it simply wouldn't work at all because of a merge mistake. See #421.
|
21
58
|
|
22
59
|
# 1.13.0
|
23
60
|
|
@@ -36,7 +73,7 @@
|
|
36
73
|
|
37
74
|
* Stop decorating `Module#autoload` as it was only useful for supporting Ruby 2.2 and older.
|
38
75
|
|
39
|
-
* Remove `uname` and other
|
76
|
+
* Remove `uname` and other platform specific version from the cache keys. `RUBY_PLATFORM + RUBY_REVISION` should be
|
40
77
|
enough to ensure bytecode compatibility. This should improve caching for alpine based setups. See #409.
|
41
78
|
|
42
79
|
# 1.11.1
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Bootsnap [](https://github.com/Shopify/bootsnap/actions)
|
2
2
|
|
3
|
-
Bootsnap is a library that plugs into Ruby, with optional support for `YAML`,
|
3
|
+
Bootsnap is a library that plugs into Ruby, with optional support for `YAML` and `JSON`,
|
4
4
|
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
5
5
|
|
6
6
|
#### Performance
|
@@ -57,6 +57,7 @@ Bootsnap.setup(
|
|
57
57
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
58
58
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
59
59
|
compile_cache_yaml: true, # Compile YAML into a cache
|
60
|
+
compile_cache_json: true, # Compile JSON into a cache
|
60
61
|
readonly: true, # Use the caches but don't update them on miss or stale entries.
|
61
62
|
)
|
62
63
|
```
|
@@ -80,6 +81,7 @@ well together.
|
|
80
81
|
- `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
|
81
82
|
- `BOOTSNAP_READONLY` configure bootsnap to not update the cache on miss or stale entries.
|
82
83
|
- `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR.
|
84
|
+
- `BOOTSNAP_STATS` log hit rate statistics on exit. Can't be used if `BOOTSNAP_LOG` is enabled.
|
83
85
|
- `BOOTSNAP_IGNORE_DIRECTORIES` a comma separated list of directories that shouldn't be scanned.
|
84
86
|
Useful when you have large directories of non-ruby files inside `$LOAD_PATH`.
|
85
87
|
It defaults to ignore any directory named `node_modules`.
|
@@ -98,8 +100,8 @@ Bootsnap cache misses can be monitored though a callback:
|
|
98
100
|
Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
|
99
101
|
```
|
100
102
|
|
101
|
-
`event` is either `:miss` or `:
|
102
|
-
log all events to STDERR.
|
103
|
+
`event` is either `:hit`, `:miss`, `:stale` or `:revalidated`.
|
104
|
+
You can also call `Bootsnap.log!` as a shortcut to log all events to STDERR.
|
103
105
|
|
104
106
|
To turn instrumentation back off you can set it to nil:
|
105
107
|
|
@@ -119,6 +121,7 @@ into two broad categories:
|
|
119
121
|
compilation.
|
120
122
|
* `YAML.load_file` is modified to cache the result of loading a YAML object in MessagePack format
|
121
123
|
(or Marshal, if the message uses types unsupported by MessagePack).
|
124
|
+
* `JSON.load_file` is modified to cache the result of loading a JSON object in MessagePack format
|
122
125
|
|
123
126
|
### Path Pre-Scanning
|
124
127
|
|
@@ -189,9 +192,9 @@ translated ruby source to an internal bytecode format, which is then executed by
|
|
189
192
|
allows caching that bytecode. This allows us to bypass the relatively-expensive compilation step on
|
190
193
|
subsequent loads of the same file.
|
191
194
|
|
192
|
-
We also noticed that we spend a lot of time loading YAML documents during our application boot, and
|
193
|
-
that MessagePack and Marshal are *much* faster at deserialization than YAML, even with a fast
|
194
|
-
implementation. We use the same strategy of compilation caching for YAML documents, with the
|
195
|
+
We also noticed that we spend a lot of time loading YAML and JSON documents during our application boot, and
|
196
|
+
that MessagePack and Marshal are *much* faster at deserialization than YAML and JSON, even with a fast
|
197
|
+
implementation. We use the same strategy of compilation caching for YAML and JSON documents, with the
|
195
198
|
equivalent of Ruby's "bytecode" format being a MessagePack document (or, in the case of YAML
|
196
199
|
documents with types unsupported by MessagePack, a Marshal stream).
|
197
200
|
|
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -18,8 +18,19 @@
|
|
18
18
|
#include <sys/types.h>
|
19
19
|
#include <errno.h>
|
20
20
|
#include <fcntl.h>
|
21
|
+
#include <unistd.h>
|
21
22
|
#include <sys/stat.h>
|
22
23
|
|
24
|
+
#ifdef __APPLE__
|
25
|
+
// The symbol is present, however not in the headers
|
26
|
+
// See: https://github.com/Shopify/bootsnap/issues/470
|
27
|
+
extern int fdatasync(int);
|
28
|
+
#endif
|
29
|
+
|
30
|
+
#ifndef O_NOATIME
|
31
|
+
#define O_NOATIME 0
|
32
|
+
#endif
|
33
|
+
|
23
34
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
24
35
|
* 981 for the cache dir */
|
25
36
|
#define MAX_CACHEPATH_SIZE 1000
|
@@ -30,7 +41,7 @@
|
|
30
41
|
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
31
42
|
|
32
43
|
#ifndef RB_UNLIKELY
|
33
|
-
|
44
|
+
#define RB_UNLIKELY(x) (x)
|
34
45
|
#endif
|
35
46
|
|
36
47
|
/*
|
@@ -54,8 +65,10 @@ struct bs_cache_key {
|
|
54
65
|
uint32_t ruby_revision;
|
55
66
|
uint64_t size;
|
56
67
|
uint64_t mtime;
|
57
|
-
uint64_t data_size;
|
58
|
-
|
68
|
+
uint64_t data_size; //
|
69
|
+
uint64_t digest;
|
70
|
+
uint8_t digest_set;
|
71
|
+
uint8_t pad[15];
|
59
72
|
} __attribute__((packed));
|
60
73
|
|
61
74
|
/*
|
@@ -69,7 +82,7 @@ struct bs_cache_key {
|
|
69
82
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
70
83
|
|
71
84
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
72
|
-
static const uint32_t current_version =
|
85
|
+
static const uint32_t current_version = 5;
|
73
86
|
|
74
87
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
75
88
|
* new OS ABI, etc. */
|
@@ -87,25 +100,36 @@ static VALUE rb_mBootsnap_CompileCache;
|
|
87
100
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
88
101
|
static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
89
102
|
static ID instrumentation_method;
|
90
|
-
static VALUE sym_miss;
|
91
|
-
static VALUE sym_stale;
|
103
|
+
static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated;
|
92
104
|
static bool instrumentation_enabled = false;
|
93
105
|
static bool readonly = false;
|
106
|
+
static bool revalidation = false;
|
107
|
+
static bool perm_issue = false;
|
94
108
|
|
95
109
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
96
110
|
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
97
111
|
static VALUE bs_readonly_set(VALUE self, VALUE enabled);
|
112
|
+
static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
|
98
113
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
99
114
|
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
100
115
|
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
101
116
|
|
102
117
|
/* Helpers */
|
118
|
+
enum cache_status {
|
119
|
+
miss,
|
120
|
+
hit,
|
121
|
+
stale,
|
122
|
+
};
|
103
123
|
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
104
124
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
105
|
-
static
|
125
|
+
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
126
|
+
static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
|
127
|
+
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance);
|
128
|
+
|
129
|
+
static void bs_cache_key_digest(struct bs_cache_key * key, const VALUE input_data);
|
106
130
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
107
131
|
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
108
|
-
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
132
|
+
static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
109
133
|
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
110
134
|
static uint32_t get_ruby_revision(void);
|
111
135
|
static uint32_t get_ruby_platform(void);
|
@@ -161,14 +185,14 @@ Init_bootsnap(void)
|
|
161
185
|
|
162
186
|
instrumentation_method = rb_intern("_instrument");
|
163
187
|
|
188
|
+
sym_hit = ID2SYM(rb_intern("hit"));
|
164
189
|
sym_miss = ID2SYM(rb_intern("miss"));
|
165
|
-
rb_global_variable(&sym_miss);
|
166
|
-
|
167
190
|
sym_stale = ID2SYM(rb_intern("stale"));
|
168
|
-
|
191
|
+
sym_revalidated = ID2SYM(rb_intern("revalidated"));
|
169
192
|
|
170
193
|
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
171
194
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
195
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
|
172
196
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
173
197
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
174
198
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
@@ -185,6 +209,14 @@ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
|
185
209
|
return enabled;
|
186
210
|
}
|
187
211
|
|
212
|
+
static inline void
|
213
|
+
bs_instrumentation(VALUE event, VALUE path)
|
214
|
+
{
|
215
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
216
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
188
220
|
static VALUE
|
189
221
|
bs_readonly_set(VALUE self, VALUE enabled)
|
190
222
|
{
|
@@ -192,6 +224,13 @@ bs_readonly_set(VALUE self, VALUE enabled)
|
|
192
224
|
return enabled;
|
193
225
|
}
|
194
226
|
|
227
|
+
static VALUE
|
228
|
+
bs_revalidation_set(VALUE self, VALUE enabled)
|
229
|
+
{
|
230
|
+
revalidation = RTEST(enabled);
|
231
|
+
return enabled;
|
232
|
+
}
|
233
|
+
|
195
234
|
/*
|
196
235
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
197
236
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -290,17 +329,59 @@ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_C
|
|
290
329
|
* The data_size member is not compared, as it serves more of a "header"
|
291
330
|
* function.
|
292
331
|
*/
|
293
|
-
static
|
294
|
-
|
332
|
+
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
|
333
|
+
struct bs_cache_key *k2) {
|
334
|
+
if (k1->version == k2->version &&
|
335
|
+
k1->ruby_platform == k2->ruby_platform &&
|
336
|
+
k1->compile_option == k2->compile_option &&
|
337
|
+
k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) {
|
338
|
+
if (k1->mtime == k2->mtime) {
|
339
|
+
return hit;
|
340
|
+
}
|
341
|
+
if (revalidation) {
|
342
|
+
return stale;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
return miss;
|
346
|
+
}
|
347
|
+
|
348
|
+
static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
|
349
|
+
struct bs_cache_key *cached_key,
|
350
|
+
const VALUE input_data)
|
351
|
+
{
|
352
|
+
bs_cache_key_digest(current_key, input_data);
|
353
|
+
return current_key->digest == cached_key->digest;
|
354
|
+
}
|
355
|
+
|
356
|
+
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
|
295
357
|
{
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
358
|
+
old_key->mtime = current_key->mtime;
|
359
|
+
lseek(cache_fd, 0, SEEK_SET);
|
360
|
+
ssize_t nwrite = write(cache_fd, old_key, KEY_SIZE);
|
361
|
+
if (nwrite < 0) {
|
362
|
+
*errno_provenance = "update_cache_key:write";
|
363
|
+
return -1;
|
364
|
+
}
|
365
|
+
|
366
|
+
#ifdef HAVE_FDATASYNC
|
367
|
+
if (fdatasync(cache_fd) < 0) {
|
368
|
+
*errno_provenance = "update_cache_key:fdatasync";
|
369
|
+
return -1;
|
370
|
+
}
|
371
|
+
#endif
|
372
|
+
|
373
|
+
return 0;
|
374
|
+
}
|
375
|
+
|
376
|
+
/*
|
377
|
+
* Fills the cache key digest.
|
378
|
+
*/
|
379
|
+
static void bs_cache_key_digest(struct bs_cache_key *key,
|
380
|
+
const VALUE input_data) {
|
381
|
+
if (key->digest_set)
|
382
|
+
return;
|
383
|
+
key->digest = fnv1a_64(input_data);
|
384
|
+
key->digest_set = 1;
|
304
385
|
}
|
305
386
|
|
306
387
|
/*
|
@@ -356,17 +437,34 @@ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
356
437
|
|
357
438
|
return bs_precompile(path, path_v, cache_path, handler);
|
358
439
|
}
|
440
|
+
|
441
|
+
static int bs_open_noatime(const char *path, int flags) {
|
442
|
+
int fd = 1;
|
443
|
+
if (!perm_issue) {
|
444
|
+
fd = open(path, flags | O_NOATIME);
|
445
|
+
if (fd < 0 && errno == EPERM) {
|
446
|
+
errno = 0;
|
447
|
+
perm_issue = true;
|
448
|
+
}
|
449
|
+
}
|
450
|
+
|
451
|
+
if (perm_issue) {
|
452
|
+
fd = open(path, flags);
|
453
|
+
}
|
454
|
+
return fd;
|
455
|
+
}
|
456
|
+
|
359
457
|
/*
|
360
458
|
* Open the file we want to load/cache and generate a cache key for it if it
|
361
459
|
* was loaded.
|
362
460
|
*/
|
363
461
|
static int
|
364
|
-
open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
462
|
+
open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
365
463
|
{
|
366
464
|
struct stat statbuf;
|
367
465
|
int fd;
|
368
466
|
|
369
|
-
fd =
|
467
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
370
468
|
if (fd < 0) {
|
371
469
|
*errno_provenance = "bs_fetch:open_current_file:open";
|
372
470
|
return fd;
|
@@ -377,7 +475,9 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
377
475
|
|
378
476
|
if (fstat(fd, &statbuf) < 0) {
|
379
477
|
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
478
|
+
int previous_errno = errno;
|
380
479
|
close(fd);
|
480
|
+
errno = previous_errno;
|
381
481
|
return -1;
|
382
482
|
}
|
383
483
|
|
@@ -387,6 +487,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
387
487
|
key->ruby_revision = current_ruby_revision;
|
388
488
|
key->size = (uint64_t)statbuf.st_size;
|
389
489
|
key->mtime = (uint64_t)statbuf.st_mtime;
|
490
|
+
key->digest_set = false;
|
390
491
|
|
391
492
|
return fd;
|
392
493
|
}
|
@@ -430,7 +531,12 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
430
531
|
{
|
431
532
|
int fd, res;
|
432
533
|
|
433
|
-
|
534
|
+
if (readonly || !revalidation) {
|
535
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
536
|
+
} else {
|
537
|
+
fd = bs_open_noatime(path, O_RDWR);
|
538
|
+
}
|
539
|
+
|
434
540
|
if (fd < 0) {
|
435
541
|
*errno_provenance = "bs_fetch:open_cache_file:open";
|
436
542
|
return CACHE_MISS;
|
@@ -467,7 +573,6 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
467
573
|
static int
|
468
574
|
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
469
575
|
{
|
470
|
-
char * data = NULL;
|
471
576
|
ssize_t nread;
|
472
577
|
int ret;
|
473
578
|
|
@@ -479,8 +584,8 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
479
584
|
ret = ERROR_WITH_ERRNO;
|
480
585
|
goto done;
|
481
586
|
}
|
482
|
-
|
483
|
-
nread = read(fd,
|
587
|
+
storage_data = rb_str_buf_new(data_size);
|
588
|
+
nread = read(fd, RSTRING_PTR(storage_data), data_size);
|
484
589
|
if (nread < 0) {
|
485
590
|
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
486
591
|
ret = ERROR_WITH_ERRNO;
|
@@ -491,7 +596,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
491
596
|
goto done;
|
492
597
|
}
|
493
598
|
|
494
|
-
storage_data
|
599
|
+
rb_str_set_len(storage_data, nread);
|
495
600
|
|
496
601
|
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
497
602
|
if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
@@ -500,7 +605,6 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
500
605
|
}
|
501
606
|
ret = 0;
|
502
607
|
done:
|
503
|
-
if (data != NULL) xfree(data);
|
504
608
|
return ret;
|
505
609
|
}
|
506
610
|
|
@@ -607,17 +711,22 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
|
|
607
711
|
|
608
712
|
|
609
713
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
610
|
-
* long,
|
611
|
-
static
|
612
|
-
bs_read_contents(int fd, size_t size,
|
714
|
+
* long, returning a Ruby string on success and Qfalse on failure */
|
715
|
+
static VALUE
|
716
|
+
bs_read_contents(int fd, size_t size, const char ** errno_provenance)
|
613
717
|
{
|
718
|
+
VALUE contents;
|
614
719
|
ssize_t nread;
|
615
|
-
|
616
|
-
nread = read(fd,
|
720
|
+
contents = rb_str_buf_new(size);
|
721
|
+
nread = read(fd, RSTRING_PTR(contents), size);
|
722
|
+
|
617
723
|
if (nread < 0) {
|
618
724
|
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
725
|
+
return Qfalse;
|
726
|
+
} else {
|
727
|
+
rb_str_set_len(contents, nread);
|
728
|
+
return contents;
|
619
729
|
}
|
620
|
-
return nread;
|
621
730
|
}
|
622
731
|
|
623
732
|
/*
|
@@ -668,38 +777,67 @@ static VALUE
|
|
668
777
|
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
669
778
|
{
|
670
779
|
struct bs_cache_key cached_key, current_key;
|
671
|
-
char * contents = NULL;
|
672
780
|
int cache_fd = -1, current_fd = -1;
|
673
781
|
int res, valid_cache = 0, exception_tag = 0;
|
674
782
|
const char * errno_provenance = NULL;
|
675
783
|
|
676
|
-
VALUE
|
784
|
+
VALUE status = Qfalse;
|
785
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
677
786
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
678
787
|
VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
|
679
788
|
|
680
789
|
VALUE exception; /* ruby exception object to raise instead of returning */
|
790
|
+
VALUE exception_message; /* ruby exception string to use instead of errno_provenance */
|
681
791
|
|
682
792
|
/* Open the source file and generate a cache key for it */
|
683
793
|
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
684
|
-
if (current_fd < 0)
|
794
|
+
if (current_fd < 0) {
|
795
|
+
exception_message = path_v;
|
796
|
+
goto fail_errno;
|
797
|
+
}
|
685
798
|
|
686
799
|
/* Open the cache key if it exists, and read its cache key in */
|
687
800
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
688
801
|
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
689
802
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
690
|
-
|
691
|
-
rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
692
|
-
}
|
803
|
+
bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
693
804
|
} else if (cache_fd < 0) {
|
805
|
+
exception_message = rb_str_new_cstr(cache_path);
|
694
806
|
goto fail_errno;
|
695
807
|
} else {
|
696
808
|
/* True if the cache existed and no invalidating changes have occurred since
|
697
809
|
* it was generated. */
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
810
|
+
|
811
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
812
|
+
case hit:
|
813
|
+
status = sym_hit;
|
814
|
+
valid_cache = true;
|
815
|
+
break;
|
816
|
+
case miss:
|
817
|
+
valid_cache = false;
|
818
|
+
break;
|
819
|
+
case stale:
|
820
|
+
valid_cache = false;
|
821
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size,
|
822
|
+
&errno_provenance)) == Qfalse) {
|
823
|
+
exception_message = path_v;
|
824
|
+
goto fail_errno;
|
702
825
|
}
|
826
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
827
|
+
if (valid_cache) {
|
828
|
+
if (!readonly) {
|
829
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
830
|
+
exception_message = path_v;
|
831
|
+
goto fail_errno;
|
832
|
+
}
|
833
|
+
}
|
834
|
+
status = sym_revalidated;
|
835
|
+
}
|
836
|
+
break;
|
837
|
+
};
|
838
|
+
|
839
|
+
if (!valid_cache) {
|
840
|
+
status = sym_stale;
|
703
841
|
}
|
704
842
|
}
|
705
843
|
|
@@ -713,13 +851,18 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
713
851
|
else if (res == CACHE_UNCOMPILABLE) {
|
714
852
|
/* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
|
715
853
|
This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
|
716
|
-
if (bs_read_contents(current_fd, current_key.size, &
|
717
|
-
|
854
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
855
|
+
exception_message = path_v;
|
856
|
+
goto fail_errno;
|
857
|
+
}
|
718
858
|
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
719
859
|
if (exception_tag != 0) goto raise;
|
720
860
|
goto succeed;
|
721
861
|
} else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
722
|
-
else if (res == ERROR_WITH_ERRNO)
|
862
|
+
else if (res == ERROR_WITH_ERRNO){
|
863
|
+
exception_message = rb_str_new_cstr(cache_path);
|
864
|
+
goto fail_errno;
|
865
|
+
}
|
723
866
|
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
724
867
|
}
|
725
868
|
close(cache_fd);
|
@@ -727,8 +870,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
727
870
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
728
871
|
|
729
872
|
/* Read the contents of the source file into a buffer */
|
730
|
-
if (bs_read_contents(current_fd, current_key.size, &
|
731
|
-
|
873
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
874
|
+
exception_message = path_v;
|
875
|
+
goto fail_errno;
|
876
|
+
}
|
732
877
|
|
733
878
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
734
879
|
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
@@ -747,6 +892,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
747
892
|
* We do however ignore any failures to persist the cache, as it's better
|
748
893
|
* to move along, than to interrupt the process.
|
749
894
|
*/
|
895
|
+
bs_cache_key_digest(¤t_key, input_data);
|
750
896
|
atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
751
897
|
|
752
898
|
/* Having written the cache, now convert storage_data to output_data */
|
@@ -765,6 +911,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
765
911
|
* No point raising an error */
|
766
912
|
if (errno != ENOENT) {
|
767
913
|
errno_provenance = "bs_fetch:unlink";
|
914
|
+
exception_message = rb_str_new_cstr(cache_path);
|
768
915
|
goto fail_errno;
|
769
916
|
}
|
770
917
|
}
|
@@ -775,7 +922,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
775
922
|
goto succeed; /* output_data is now the correct return. */
|
776
923
|
|
777
924
|
#define CLEANUP \
|
778
|
-
if (
|
925
|
+
if (status != Qfalse) bs_instrumentation(status, path_v); \
|
779
926
|
if (current_fd >= 0) close(current_fd); \
|
780
927
|
if (cache_fd >= 0) close(cache_fd);
|
781
928
|
|
@@ -784,7 +931,13 @@ succeed:
|
|
784
931
|
return output_data;
|
785
932
|
fail_errno:
|
786
933
|
CLEANUP;
|
787
|
-
|
934
|
+
if (errno_provenance) {
|
935
|
+
exception_message = rb_str_concat(
|
936
|
+
rb_str_new_cstr(errno_provenance),
|
937
|
+
rb_str_concat(rb_str_new_cstr(": "), exception_message)
|
938
|
+
);
|
939
|
+
}
|
940
|
+
exception = rb_syserr_new_str(errno, exception_message);
|
788
941
|
rb_exc_raise(exception);
|
789
942
|
__builtin_unreachable();
|
790
943
|
raise:
|
@@ -802,13 +955,16 @@ invalid_type_storage_data:
|
|
802
955
|
static VALUE
|
803
956
|
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
804
957
|
{
|
958
|
+
if (readonly) {
|
959
|
+
return Qfalse;
|
960
|
+
}
|
961
|
+
|
805
962
|
struct bs_cache_key cached_key, current_key;
|
806
|
-
char * contents = NULL;
|
807
963
|
int cache_fd = -1, current_fd = -1;
|
808
964
|
int res, valid_cache = 0, exception_tag = 0;
|
809
965
|
const char * errno_provenance = NULL;
|
810
966
|
|
811
|
-
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
967
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
812
968
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
813
969
|
|
814
970
|
/* Open the source file and generate a cache key for it */
|
@@ -824,7 +980,26 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
824
980
|
} else {
|
825
981
|
/* True if the cache existed and no invalidating changes have occurred since
|
826
982
|
* it was generated. */
|
827
|
-
|
983
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
984
|
+
case hit:
|
985
|
+
valid_cache = true;
|
986
|
+
break;
|
987
|
+
case miss:
|
988
|
+
valid_cache = false;
|
989
|
+
break;
|
990
|
+
case stale:
|
991
|
+
valid_cache = false;
|
992
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
993
|
+
goto fail;
|
994
|
+
}
|
995
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
996
|
+
if (valid_cache) {
|
997
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
998
|
+
goto fail;
|
999
|
+
}
|
1000
|
+
}
|
1001
|
+
break;
|
1002
|
+
};
|
828
1003
|
}
|
829
1004
|
|
830
1005
|
if (valid_cache) {
|
@@ -836,8 +1011,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
836
1011
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
837
1012
|
|
838
1013
|
/* Read the contents of the source file into a buffer */
|
839
|
-
if (bs_read_contents(current_fd, current_key.size, &
|
840
|
-
input_data = rb_str_new(contents, current_key.size);
|
1014
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
|
841
1015
|
|
842
1016
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
843
1017
|
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
@@ -852,13 +1026,13 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
852
1026
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
853
1027
|
|
854
1028
|
/* Write the cache key and storage_data to the cache directory */
|
1029
|
+
bs_cache_key_digest(¤t_key, input_data);
|
855
1030
|
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
856
1031
|
if (res < 0) goto fail;
|
857
1032
|
|
858
1033
|
goto succeed;
|
859
1034
|
|
860
1035
|
#define CLEANUP \
|
861
|
-
if (contents != NULL) xfree(contents); \
|
862
1036
|
if (current_fd >= 0) close(current_fd); \
|
863
1037
|
if (cache_fd >= 0) close(cache_fd);
|
864
1038
|
|
data/ext/bootsnap/extconf.rb
CHANGED
@@ -1,23 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "mkmf"
|
4
4
|
|
5
|
-
if RUBY_ENGINE
|
6
|
-
|
7
|
-
|
5
|
+
if %w[ruby truffleruby].include?(RUBY_ENGINE)
|
6
|
+
have_func "fdatasync", "unistd.h"
|
7
|
+
|
8
|
+
unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
|
9
|
+
append_cppflags ["-D_GNU_SOURCE"] # Needed of O_NOATIME
|
10
|
+
end
|
11
|
+
|
12
|
+
append_cflags ["-O3", "-std=c99"]
|
8
13
|
|
9
14
|
# ruby.h has some -Wpedantic fails in some cases
|
10
15
|
# (e.g. https://github.com/Shopify/bootsnap/issues/15)
|
11
16
|
unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
append_cflags([
|
18
|
+
"-Wall",
|
19
|
+
"-Werror",
|
20
|
+
"-Wextra",
|
21
|
+
"-Wpedantic",
|
16
22
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
23
|
+
"-Wno-unused-parameter", # VALUE self has to be there but we don't care what it is.
|
24
|
+
"-Wno-keyword-macro", # hiding return
|
25
|
+
"-Wno-gcc-compat", # ruby.h 2.6.0 on macos 10.14, dunno
|
26
|
+
"-Wno-compound-token-split-by-macro",
|
27
|
+
])
|
21
28
|
end
|
22
29
|
|
23
30
|
create_makefile("bootsnap/bootsnap")
|
data/lib/bootsnap/bundler.rb
CHANGED
data/lib/bootsnap/cli.rb
CHANGED
@@ -60,14 +60,16 @@ module Bootsnap
|
|
60
60
|
precompile_json_files(main_sources)
|
61
61
|
|
62
62
|
if compile_gemfile
|
63
|
-
# Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
|
64
|
-
gem_exclude = Regexp.union([exclude, "/spec/", "/test/"].compact)
|
65
|
-
precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
|
66
|
-
|
67
63
|
# Gems that include JSON or YAML files usually don't put them in `lib/`.
|
68
64
|
# So we look at the gem root.
|
65
|
+
# Similarly, gems that include Rails engines generally file Ruby files in `app/`.
|
66
|
+
# However some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
|
67
|
+
gem_exclude = Regexp.union([exclude, "/spec/", "/test/", "/features/"].compact)
|
68
|
+
|
69
69
|
gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems/[^/]+}
|
70
|
-
gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.
|
70
|
+
gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] || p }.uniq
|
71
|
+
|
72
|
+
precompile_ruby_files(gem_paths, exclude: gem_exclude)
|
71
73
|
precompile_yaml_files(gem_paths, exclude: gem_exclude)
|
72
74
|
precompile_json_files(gem_paths, exclude: gem_exclude)
|
73
75
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "bootsnap/bootsnap"
|
4
|
+
require "zlib"
|
5
5
|
|
6
6
|
module Bootsnap
|
7
7
|
module CompileCache
|
@@ -12,6 +12,10 @@ module Bootsnap
|
|
12
12
|
def cache_dir=(cache_dir)
|
13
13
|
@cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
|
14
14
|
end
|
15
|
+
|
16
|
+
def supported?
|
17
|
+
CompileCache.supported? && defined?(RubyVM)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
|
@@ -83,8 +87,6 @@ module Bootsnap
|
|
83
87
|
return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
|
84
88
|
|
85
89
|
Bootsnap::CompileCache::ISeq.fetch(path.to_s)
|
86
|
-
rescue Errno::EACCES
|
87
|
-
Bootsnap::CompileCache.permission_error(path)
|
88
90
|
rescue RuntimeError => error
|
89
91
|
if error.message =~ /unmatched platform/
|
90
92
|
puts("unmatched platform for file #{path}")
|
@@ -103,11 +105,15 @@ module Bootsnap
|
|
103
105
|
crc = Zlib.crc32(option.inspect)
|
104
106
|
Bootsnap::CompileCache::Native.compile_option_crc32 = crc
|
105
107
|
end
|
106
|
-
compile_option_updated
|
108
|
+
compile_option_updated if supported?
|
107
109
|
|
108
110
|
def self.install!(cache_dir)
|
109
111
|
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
112
|
+
|
113
|
+
return unless supported?
|
114
|
+
|
110
115
|
Bootsnap::CompileCache::ISeq.compile_option_updated
|
116
|
+
|
111
117
|
class << RubyVM::InstructionSequence
|
112
118
|
prepend(InstructionSequenceMixin)
|
113
119
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "bootsnap/bootsnap"
|
4
4
|
|
5
5
|
module Bootsnap
|
6
6
|
module CompileCache
|
@@ -46,8 +46,8 @@ module Bootsnap
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def init!
|
49
|
-
require
|
50
|
-
require
|
49
|
+
require "json"
|
50
|
+
require "msgpack"
|
51
51
|
|
52
52
|
self.msgpack_factory = MessagePack::Factory.new
|
53
53
|
self.supported_options = [:symbolize_names]
|
@@ -74,16 +74,12 @@ module Bootsnap
|
|
74
74
|
return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
|
75
75
|
end
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
)
|
84
|
-
rescue Errno::EACCES
|
85
|
-
::Bootsnap::CompileCache.permission_error(path)
|
86
|
-
end
|
77
|
+
::Bootsnap::CompileCache::Native.fetch(
|
78
|
+
Bootsnap::CompileCache::JSON.cache_dir,
|
79
|
+
File.realpath(path),
|
80
|
+
::Bootsnap::CompileCache::JSON,
|
81
|
+
kwargs,
|
82
|
+
)
|
87
83
|
end
|
88
84
|
|
89
85
|
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "bootsnap/bootsnap"
|
4
4
|
|
5
5
|
module Bootsnap
|
6
6
|
module CompileCache
|
@@ -55,9 +55,9 @@ module Bootsnap
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def init!
|
58
|
-
require
|
59
|
-
require
|
60
|
-
require
|
58
|
+
require "yaml"
|
59
|
+
require "msgpack"
|
60
|
+
require "date"
|
61
61
|
|
62
62
|
@implementation = ::YAML::VERSION >= "4" ? Psych4 : Psych3
|
63
63
|
if @implementation::Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
|
@@ -229,16 +229,12 @@ module Bootsnap
|
|
229
229
|
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
230
230
|
end
|
231
231
|
|
232
|
-
|
233
|
-
CompileCache::
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
)
|
239
|
-
rescue Errno::EACCES
|
240
|
-
CompileCache.permission_error(path)
|
241
|
-
end
|
232
|
+
CompileCache::Native.fetch(
|
233
|
+
CompileCache::YAML.cache_dir,
|
234
|
+
File.realpath(path),
|
235
|
+
CompileCache::YAML::Psych4::SafeLoad,
|
236
|
+
kwargs,
|
237
|
+
)
|
242
238
|
end
|
243
239
|
|
244
240
|
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
@@ -253,16 +249,12 @@ module Bootsnap
|
|
253
249
|
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
254
250
|
end
|
255
251
|
|
256
|
-
|
257
|
-
CompileCache::
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
)
|
263
|
-
rescue Errno::EACCES
|
264
|
-
CompileCache.permission_error(path)
|
265
|
-
end
|
252
|
+
CompileCache::Native.fetch(
|
253
|
+
CompileCache::YAML.cache_dir,
|
254
|
+
File.realpath(path),
|
255
|
+
CompileCache::YAML::Psych4::UnsafeLoad,
|
256
|
+
kwargs,
|
257
|
+
)
|
266
258
|
end
|
267
259
|
|
268
260
|
ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
|
@@ -309,16 +301,12 @@ module Bootsnap
|
|
309
301
|
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
310
302
|
end
|
311
303
|
|
312
|
-
|
313
|
-
CompileCache::
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
)
|
319
|
-
rescue Errno::EACCES
|
320
|
-
CompileCache.permission_error(path)
|
321
|
-
end
|
304
|
+
CompileCache::Native.fetch(
|
305
|
+
CompileCache::YAML.cache_dir,
|
306
|
+
File.realpath(path),
|
307
|
+
CompileCache::YAML::Psych3,
|
308
|
+
kwargs,
|
309
|
+
)
|
322
310
|
end
|
323
311
|
|
324
312
|
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
@@ -333,16 +321,12 @@ module Bootsnap
|
|
333
321
|
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
334
322
|
end
|
335
323
|
|
336
|
-
|
337
|
-
CompileCache::
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
)
|
343
|
-
rescue Errno::EACCES
|
344
|
-
CompileCache.permission_error(path)
|
345
|
-
end
|
324
|
+
CompileCache::Native.fetch(
|
325
|
+
CompileCache::YAML.cache_dir,
|
326
|
+
File.realpath(path),
|
327
|
+
CompileCache::YAML::Psych3,
|
328
|
+
kwargs,
|
329
|
+
)
|
346
330
|
end
|
347
331
|
|
348
332
|
ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
|
@@ -8,12 +8,11 @@ module Bootsnap
|
|
8
8
|
end
|
9
9
|
|
10
10
|
Error = Class.new(StandardError)
|
11
|
-
PermissionError = Class.new(Error)
|
12
11
|
|
13
|
-
def self.setup(cache_dir:, iseq:, yaml:, json:, readonly: false)
|
12
|
+
def self.setup(cache_dir:, iseq:, yaml:, json:, readonly: false, revalidation: false)
|
14
13
|
if iseq
|
15
14
|
if supported?
|
16
|
-
require_relative
|
15
|
+
require_relative "compile_cache/iseq"
|
17
16
|
Bootsnap::CompileCache::ISeq.install!(cache_dir)
|
18
17
|
elsif $VERBOSE
|
19
18
|
warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
|
@@ -22,7 +21,7 @@ module Bootsnap
|
|
22
21
|
|
23
22
|
if yaml
|
24
23
|
if supported?
|
25
|
-
require_relative
|
24
|
+
require_relative "compile_cache/yaml"
|
26
25
|
Bootsnap::CompileCache::YAML.install!(cache_dir)
|
27
26
|
elsif $VERBOSE
|
28
27
|
warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
|
@@ -31,7 +30,7 @@ module Bootsnap
|
|
31
30
|
|
32
31
|
if json
|
33
32
|
if supported?
|
34
|
-
require_relative
|
33
|
+
require_relative "compile_cache/json"
|
35
34
|
Bootsnap::CompileCache::JSON.install!(cache_dir)
|
36
35
|
elsif $VERBOSE
|
37
36
|
warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
|
@@ -40,21 +39,14 @@ module Bootsnap
|
|
40
39
|
|
41
40
|
if supported? && defined?(Bootsnap::CompileCache::Native)
|
42
41
|
Bootsnap::CompileCache::Native.readonly = readonly
|
42
|
+
Bootsnap::CompileCache::Native.revalidation = revalidation
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
def self.permission_error(path)
|
47
|
-
cpath = Bootsnap::CompileCache::ISeq.cache_dir
|
48
|
-
raise(
|
49
|
-
PermissionError,
|
50
|
-
"bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
|
51
|
-
"(or, less likely, doesn't have permission to read '#{path}')",
|
52
|
-
)
|
53
|
-
end
|
54
|
-
|
55
46
|
def self.supported?
|
56
|
-
# only enable on 'ruby' (MRI)
|
57
|
-
|
47
|
+
# only enable on 'ruby' (MRI) and TruffleRuby for POSIX (darwin, linux, *bsd), Windows (RubyInstaller2)
|
48
|
+
%w[ruby truffleruby].include?(RUBY_ENGINE) &&
|
49
|
+
RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
|
58
50
|
end
|
59
51
|
end
|
60
52
|
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
|
@@ -24,8 +24,16 @@ module Bootsnap
|
|
24
24
|
@mutex.synchronize { @dirs[dir] }
|
25
25
|
end
|
26
26
|
|
27
|
+
TRUFFLERUBY_LIB_DIR_PREFIX = if RUBY_ENGINE == "truffleruby"
|
28
|
+
"#{File.join(RbConfig::CONFIG['libdir'], 'truffle')}#{File::SEPARATOR}"
|
29
|
+
end
|
30
|
+
|
27
31
|
# { 'enumerator' => nil, 'enumerator.so' => nil, ... }
|
28
32
|
BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
|
33
|
+
if TRUFFLERUBY_LIB_DIR_PREFIX && feat.start_with?(TRUFFLERUBY_LIB_DIR_PREFIX)
|
34
|
+
feat = feat.byteslice(TRUFFLERUBY_LIB_DIR_PREFIX.bytesize..-1)
|
35
|
+
end
|
36
|
+
|
29
37
|
# Builtin features are of the form 'enumerator.so'.
|
30
38
|
# All others include paths.
|
31
39
|
next unless feat.size < 20 && !feat.include?("/")
|
@@ -1,11 +1,11 @@
|
|
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
7
|
|
8
|
-
def require(path)
|
8
|
+
def require(path) # rubocop:disable Lint/DuplicateMethods
|
9
9
|
return require_without_bootsnap(path) unless Bootsnap::LoadPathCache.enabled?
|
10
10
|
|
11
11
|
string_path = Bootsnap.rb_get_path(path)
|
@@ -24,9 +24,7 @@ module Kernel
|
|
24
24
|
elsif false == resolved
|
25
25
|
return false
|
26
26
|
elsif resolved.nil?
|
27
|
-
|
28
|
-
error.instance_variable_set(:@path, path)
|
29
|
-
raise error
|
27
|
+
return require_without_bootsnap(path)
|
30
28
|
else
|
31
29
|
# Note that require registers to $LOADED_FEATURES while load does not.
|
32
30
|
ret = require_without_bootsnap(resolved)
|
@@ -34,4 +32,6 @@ module Kernel
|
|
34
32
|
return ret
|
35
33
|
end
|
36
34
|
end
|
35
|
+
|
36
|
+
private :require
|
37
37
|
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
|
@@ -54,7 +54,7 @@ module Bootsnap
|
|
54
54
|
|
55
55
|
absolute_path = "#{absolute_dir_path}/#{name}"
|
56
56
|
if File.directory?(absolute_path)
|
57
|
-
next if ignored_directories.include?(name)
|
57
|
+
next if ignored_directories.include?(name) || ignored_directories.include?(absolute_path)
|
58
58
|
|
59
59
|
if yield relative_path, absolute_path, true
|
60
60
|
walk(absolute_path, relative_path, &block)
|
@@ -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
|
@@ -38,11 +38,11 @@ module Bootsnap
|
|
38
38
|
|
39
39
|
@loaded_features_index = LoadedFeaturesIndex.new
|
40
40
|
|
41
|
-
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
42
41
|
PathScanner.ignored_directories = ignore_directories if ignore_directories
|
42
|
+
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
43
43
|
@enabled = true
|
44
|
-
require_relative
|
45
|
-
require_relative
|
44
|
+
require_relative "load_path_cache/core_ext/kernel_require"
|
45
|
+
require_relative "load_path_cache/core_ext/loaded_features"
|
46
46
|
end
|
47
47
|
|
48
48
|
def unload!
|
@@ -50,22 +50,31 @@ module Bootsnap
|
|
50
50
|
@loaded_features_index = nil
|
51
51
|
@realpath_cache = nil
|
52
52
|
@load_path_cache = nil
|
53
|
-
ChangeObserver.unregister($LOAD_PATH)
|
53
|
+
ChangeObserver.unregister($LOAD_PATH) if supported?
|
54
54
|
end
|
55
55
|
|
56
56
|
def supported?
|
57
|
-
|
58
|
-
|
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
|
59
68
|
end
|
60
69
|
end
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
64
73
|
if Bootsnap::LoadPathCache.supported?
|
65
|
-
require_relative
|
66
|
-
require_relative
|
67
|
-
require_relative
|
68
|
-
require_relative
|
69
|
-
require_relative
|
70
|
-
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"
|
71
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
|
|
@@ -41,6 +51,7 @@ module Bootsnap
|
|
41
51
|
load_path_cache: true,
|
42
52
|
ignore_directories: nil,
|
43
53
|
readonly: false,
|
54
|
+
revalidation: false,
|
44
55
|
compile_cache_iseq: true,
|
45
56
|
compile_cache_yaml: true,
|
46
57
|
compile_cache_json: true
|
@@ -60,6 +71,7 @@ module Bootsnap
|
|
60
71
|
yaml: compile_cache_yaml,
|
61
72
|
json: compile_cache_json,
|
62
73
|
readonly: readonly,
|
74
|
+
revalidation: revalidation,
|
63
75
|
)
|
64
76
|
end
|
65
77
|
|
@@ -110,6 +122,8 @@ module Bootsnap
|
|
110
122
|
|
111
123
|
if ENV["BOOTSNAP_LOG"]
|
112
124
|
log!
|
125
|
+
elsif ENV["BOOTSNAP_STATS"]
|
126
|
+
log_stats!
|
113
127
|
end
|
114
128
|
end
|
115
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
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
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
|