bootsnap 1.16.0 → 1.18.3
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](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
|