bootsnap 1.1.0 → 1.2.0
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 +5 -5
- data/.rubocop.yml +13 -0
- data/.travis.yml +6 -3
- data/CHANGELOG.md +39 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.jp.md +218 -0
- data/README.md +20 -24
- data/bin/testunit +3 -3
- data/bootsnap.gemspec +2 -2
- data/dev.yml +3 -1
- data/ext/bootsnap/bootsnap.c +131 -80
- data/lib/bootsnap/bundler.rb +12 -0
- data/lib/bootsnap/compile_cache/iseq.rb +0 -1
- data/lib/bootsnap/compile_cache/yaml.rb +1 -0
- data/lib/bootsnap/explicit_require.rb +6 -1
- data/lib/bootsnap/load_path_cache/cache.rb +12 -14
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +15 -13
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +59 -33
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +95 -0
- data/lib/bootsnap/load_path_cache/path.rb +1 -1
- data/lib/bootsnap/load_path_cache/path_scanner.rb +16 -6
- data/lib/bootsnap/load_path_cache/store.rb +9 -2
- data/lib/bootsnap/load_path_cache.rb +4 -1
- data/lib/bootsnap/setup.rb +4 -10
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +1 -0
- metadata +10 -6
- data/LICENSE +0 -20
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
/*
|
|
33
33
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
|
34
34
|
* The mtime and size members track whether the file contents have changed, and
|
|
35
|
-
* the version,
|
|
35
|
+
* the version, ruby_platform, compile_option, and ruby_revision members track
|
|
36
36
|
* changes to the environment that could invalidate compile results without
|
|
37
37
|
* file contents having changed. The data_size member is not truly part of the
|
|
38
38
|
* "key". Really, this could be called a "header" with the first six members
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
struct bs_cache_key {
|
|
47
47
|
uint32_t version;
|
|
48
|
-
uint32_t
|
|
48
|
+
uint32_t ruby_platform;
|
|
49
49
|
uint32_t compile_option;
|
|
50
50
|
uint32_t ruby_revision;
|
|
51
51
|
uint64_t size;
|
|
@@ -67,9 +67,9 @@ STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
|
|
67
67
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
|
68
68
|
static const uint32_t current_version = 2;
|
|
69
69
|
|
|
70
|
-
/*
|
|
71
|
-
*
|
|
72
|
-
static uint32_t
|
|
70
|
+
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
|
71
|
+
* new OS ABI, etc. */
|
|
72
|
+
static uint32_t current_ruby_platform;
|
|
73
73
|
/* Invalidates cache when switching ruby versions */
|
|
74
74
|
static uint32_t current_ruby_revision;
|
|
75
75
|
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
|
@@ -92,10 +92,9 @@ static void bs_cache_path(const char * cachedir, const char * path, char ** cach
|
|
|
92
92
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
|
93
93
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
|
94
94
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
|
95
|
-
static int open_current_file(char * path, struct bs_cache_key * key);
|
|
96
|
-
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag);
|
|
97
|
-
static
|
|
98
|
-
static uint32_t get_os_version(void);
|
|
95
|
+
static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
|
|
96
|
+
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
|
|
97
|
+
static uint32_t get_ruby_platform(void);
|
|
99
98
|
|
|
100
99
|
/*
|
|
101
100
|
* Helper functions to call ruby methods on handler object without crashing on
|
|
@@ -136,7 +135,7 @@ Init_bootsnap(void)
|
|
|
136
135
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
|
137
136
|
|
|
138
137
|
current_ruby_revision = FIX2INT(rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")));
|
|
139
|
-
|
|
138
|
+
current_ruby_platform = get_ruby_platform();
|
|
140
139
|
|
|
141
140
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
|
142
141
|
|
|
@@ -148,12 +147,17 @@ Init_bootsnap(void)
|
|
|
148
147
|
/*
|
|
149
148
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
|
150
149
|
* when compile_option changes. These changes invalidate all existing caches.
|
|
150
|
+
*
|
|
151
|
+
* Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
|
|
152
|
+
* can be represented by a uint.
|
|
151
153
|
*/
|
|
152
154
|
static VALUE
|
|
153
155
|
bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
154
156
|
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) {
|
|
158
|
+
Check_Type(crc32_v, T_FIXNUM);
|
|
159
|
+
}
|
|
160
|
+
current_compile_option_crc32 = NUM2UINT(crc32_v);
|
|
157
161
|
return Qnil;
|
|
158
162
|
}
|
|
159
163
|
|
|
@@ -168,10 +172,9 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
|
168
172
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
|
169
173
|
*/
|
|
170
174
|
static uint64_t
|
|
171
|
-
|
|
175
|
+
fnv1a_64_iter(uint64_t h, const char *str)
|
|
172
176
|
{
|
|
173
177
|
unsigned char *s = (unsigned char *)str;
|
|
174
|
-
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
|
175
178
|
|
|
176
179
|
while (*s) {
|
|
177
180
|
h ^= (uint64_t)*s++;
|
|
@@ -181,26 +184,42 @@ fnv1a_64(const char *str)
|
|
|
181
184
|
return h;
|
|
182
185
|
}
|
|
183
186
|
|
|
187
|
+
static uint64_t
|
|
188
|
+
fnv1a_64(const char *str)
|
|
189
|
+
{
|
|
190
|
+
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
|
191
|
+
return fnv1a_64_iter(h, str);
|
|
192
|
+
}
|
|
193
|
+
|
|
184
194
|
/*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
195
|
+
* When ruby's version doesn't change, but it's recompiled on a different OS
|
|
196
|
+
* (or OS version), we need to invalidate the cache.
|
|
197
|
+
*
|
|
198
|
+
* We actually factor in some extra information here, to be extra confident
|
|
199
|
+
* that we don't try to re-use caches that will not be compatible, by factoring
|
|
200
|
+
* in utsname.version.
|
|
187
201
|
*/
|
|
188
202
|
static uint32_t
|
|
189
|
-
|
|
203
|
+
get_ruby_platform(void)
|
|
190
204
|
{
|
|
191
|
-
#ifdef _WIN32
|
|
192
|
-
return (uint32_t)GetVersion();
|
|
193
|
-
#else
|
|
194
205
|
uint64_t hash;
|
|
195
|
-
|
|
206
|
+
VALUE ruby_platform;
|
|
196
207
|
|
|
197
|
-
|
|
198
|
-
|
|
208
|
+
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
|
209
|
+
hash = fnv1a_64(RSTRING_PTR(ruby_platform));
|
|
199
210
|
|
|
200
|
-
|
|
211
|
+
#ifdef _WIN32
|
|
212
|
+
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
|
213
|
+
#else
|
|
214
|
+
struct utsname utsname;
|
|
215
|
+
|
|
216
|
+
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
|
217
|
+
if (uname(&utsname) >= 0) {
|
|
218
|
+
hash = fnv1a_64_iter(hash, utsname.version);
|
|
219
|
+
}
|
|
201
220
|
|
|
202
221
|
return (uint32_t)(hash >> 32);
|
|
203
|
-
|
|
222
|
+
#endif
|
|
204
223
|
}
|
|
205
224
|
|
|
206
225
|
/*
|
|
@@ -234,7 +253,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
|
234
253
|
{
|
|
235
254
|
return (
|
|
236
255
|
k1->version == k2->version &&
|
|
237
|
-
k1->
|
|
256
|
+
k1->ruby_platform == k2->ruby_platform &&
|
|
238
257
|
k1->compile_option == k2->compile_option &&
|
|
239
258
|
k1->ruby_revision == k2->ruby_revision &&
|
|
240
259
|
k1->size == k2->size &&
|
|
@@ -274,24 +293,28 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
|
274
293
|
* was loaded.
|
|
275
294
|
*/
|
|
276
295
|
static int
|
|
277
|
-
open_current_file(char * path, struct bs_cache_key * key)
|
|
296
|
+
open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
|
|
278
297
|
{
|
|
279
298
|
struct stat statbuf;
|
|
280
299
|
int fd;
|
|
281
300
|
|
|
282
301
|
fd = open(path, O_RDONLY);
|
|
283
|
-
if (fd < 0)
|
|
302
|
+
if (fd < 0) {
|
|
303
|
+
*errno_provenance = (char *)"bs_fetch:open_current_file:open";
|
|
304
|
+
return fd;
|
|
305
|
+
}
|
|
284
306
|
#ifdef _WIN32
|
|
285
307
|
setmode(fd, O_BINARY);
|
|
286
308
|
#endif
|
|
287
309
|
|
|
288
310
|
if (fstat(fd, &statbuf) < 0) {
|
|
311
|
+
*errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
|
|
289
312
|
close(fd);
|
|
290
313
|
return -1;
|
|
291
314
|
}
|
|
292
315
|
|
|
293
316
|
key->version = current_version;
|
|
294
|
-
key->
|
|
317
|
+
key->ruby_platform = current_ruby_platform;
|
|
295
318
|
key->compile_option = current_compile_option_crc32;
|
|
296
319
|
key->ruby_revision = current_ruby_revision;
|
|
297
320
|
key->size = (uint64_t)statbuf.st_size;
|
|
@@ -331,12 +354,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
|
331
354
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
|
332
355
|
*/
|
|
333
356
|
static int
|
|
334
|
-
open_cache_file(const char * path, struct bs_cache_key * key)
|
|
357
|
+
open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
|
|
335
358
|
{
|
|
336
359
|
int fd, res;
|
|
337
360
|
|
|
338
361
|
fd = open(path, O_RDONLY);
|
|
339
362
|
if (fd < 0) {
|
|
363
|
+
*errno_provenance = (char *)"bs_fetch:open_cache_file:open";
|
|
340
364
|
if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
|
|
341
365
|
return ERROR_WITH_ERRNO;
|
|
342
366
|
}
|
|
@@ -346,6 +370,7 @@ open_cache_file(const char * path, struct bs_cache_key * key)
|
|
|
346
370
|
|
|
347
371
|
res = bs_read_key(fd, key);
|
|
348
372
|
if (res < 0) {
|
|
373
|
+
*errno_provenance = (char *)"bs_fetch:open_cache_file:read";
|
|
349
374
|
close(fd);
|
|
350
375
|
return res;
|
|
351
376
|
}
|
|
@@ -369,7 +394,7 @@ open_cache_file(const char * path, struct bs_cache_key * key)
|
|
|
369
394
|
* or exception, will be the final data returnable to the user.
|
|
370
395
|
*/
|
|
371
396
|
static int
|
|
372
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag)
|
|
397
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
|
|
373
398
|
{
|
|
374
399
|
char * data = NULL;
|
|
375
400
|
ssize_t nread;
|
|
@@ -378,6 +403,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
|
378
403
|
VALUE storage_data;
|
|
379
404
|
|
|
380
405
|
if (data_size > 100000000000) {
|
|
406
|
+
*errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
|
|
381
407
|
errno = EINVAL; /* because wtf? */
|
|
382
408
|
ret = -1;
|
|
383
409
|
goto done;
|
|
@@ -385,6 +411,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
|
385
411
|
data = ALLOC_N(char, data_size);
|
|
386
412
|
nread = read(fd, data, data_size);
|
|
387
413
|
if (nread < 0) {
|
|
414
|
+
*errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
|
|
388
415
|
ret = -1;
|
|
389
416
|
goto done;
|
|
390
417
|
}
|
|
@@ -436,23 +463,29 @@ mkpath(char * file_path, mode_t mode)
|
|
|
436
463
|
* path.
|
|
437
464
|
*/
|
|
438
465
|
static int
|
|
439
|
-
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
|
466
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
|
440
467
|
{
|
|
441
468
|
char template[MAX_CACHEPATH_SIZE + 20];
|
|
442
469
|
char * dest;
|
|
443
470
|
char * tmp_path;
|
|
444
|
-
int fd;
|
|
471
|
+
int fd, ret;
|
|
445
472
|
ssize_t nwrite;
|
|
446
473
|
|
|
447
474
|
dest = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
|
448
475
|
strcat(dest, ".tmp.XXXXXX");
|
|
449
476
|
|
|
450
477
|
tmp_path = mktemp(template);
|
|
451
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT,
|
|
478
|
+
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
|
452
479
|
if (fd < 0) {
|
|
453
|
-
if (mkpath(path,
|
|
454
|
-
|
|
455
|
-
|
|
480
|
+
if (mkpath(path, 0775) < 0) {
|
|
481
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
|
|
482
|
+
return -1;
|
|
483
|
+
}
|
|
484
|
+
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
|
485
|
+
if (fd < 0) {
|
|
486
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
|
|
487
|
+
return -1;
|
|
488
|
+
}
|
|
456
489
|
}
|
|
457
490
|
#ifdef _WIN32
|
|
458
491
|
setmode(fd, O_BINARY);
|
|
@@ -460,8 +493,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
|
|
460
493
|
|
|
461
494
|
key->data_size = RSTRING_LEN(data);
|
|
462
495
|
nwrite = write(fd, key, KEY_SIZE);
|
|
463
|
-
if (nwrite < 0)
|
|
496
|
+
if (nwrite < 0) {
|
|
497
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
|
|
498
|
+
return -1;
|
|
499
|
+
}
|
|
464
500
|
if (nwrite != KEY_SIZE) {
|
|
501
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
|
|
465
502
|
errno = EIO; /* Lies but whatever */
|
|
466
503
|
return -1;
|
|
467
504
|
}
|
|
@@ -469,38 +506,32 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
|
|
469
506
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
|
470
507
|
if (nwrite < 0) return -1;
|
|
471
508
|
if (nwrite != RSTRING_LEN(data)) {
|
|
509
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
|
|
472
510
|
errno = EIO; /* Lies but whatever */
|
|
473
511
|
return -1;
|
|
474
512
|
}
|
|
475
513
|
|
|
476
514
|
close(fd);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
/*
|
|
481
|
-
* Given an errno value (converted to a ruby Fixnum), return the corresponding
|
|
482
|
-
* Errno::* constant. If none is found, return StandardError instead.
|
|
483
|
-
*/
|
|
484
|
-
static VALUE
|
|
485
|
-
prot_exception_for_errno(VALUE err)
|
|
486
|
-
{
|
|
487
|
-
if (err != INT2FIX(0)) {
|
|
488
|
-
VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
|
|
489
|
-
VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
|
|
490
|
-
VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
|
|
491
|
-
return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
|
|
515
|
+
ret = rename(tmp_path, path);
|
|
516
|
+
if (ret < 0) {
|
|
517
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
|
|
492
518
|
}
|
|
493
|
-
return
|
|
519
|
+
return ret;
|
|
494
520
|
}
|
|
495
521
|
|
|
496
522
|
|
|
497
523
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
|
498
524
|
* long, into a buffer */
|
|
499
525
|
static ssize_t
|
|
500
|
-
bs_read_contents(int fd, size_t size, char ** contents)
|
|
526
|
+
bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
|
|
501
527
|
{
|
|
528
|
+
ssize_t nread;
|
|
502
529
|
*contents = ALLOC_N(char, size);
|
|
503
|
-
|
|
530
|
+
nread = read(fd, *contents, size);
|
|
531
|
+
if (nread < 0) {
|
|
532
|
+
*errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
|
|
533
|
+
}
|
|
534
|
+
return nread;
|
|
504
535
|
}
|
|
505
536
|
|
|
506
537
|
/*
|
|
@@ -508,24 +539,24 @@ bs_read_contents(int fd, size_t size, char ** contents)
|
|
|
508
539
|
* Bootsnap::CompileCache::Native.fetch.
|
|
509
540
|
*
|
|
510
541
|
* There are three "formats" in use here:
|
|
511
|
-
* 1. "input"
|
|
542
|
+
* 1. "input" format, which is what we load from the source file;
|
|
512
543
|
* 2. "storage" format, which we write to the cache;
|
|
513
544
|
* 3. "output" format, which is what we return.
|
|
514
545
|
*
|
|
515
546
|
* E.g., For ISeq compilation:
|
|
516
|
-
* input:
|
|
547
|
+
* input: ruby source, as text
|
|
517
548
|
* storage: binary string (RubyVM::InstructionSequence#to_binary)
|
|
518
|
-
* output:
|
|
549
|
+
* output: Instance of RubyVM::InstructionSequence
|
|
519
550
|
*
|
|
520
551
|
* And for YAML:
|
|
521
|
-
* input:
|
|
552
|
+
* input: yaml as text
|
|
522
553
|
* storage: MessagePack or Marshal text
|
|
523
|
-
* output:
|
|
554
|
+
* output: ruby object, loaded from yaml/messagepack/marshal
|
|
524
555
|
*
|
|
525
|
-
*
|
|
526
|
-
* * storage_to_output(
|
|
527
|
-
* * input_to_output(
|
|
528
|
-
* * input_to_storage(
|
|
556
|
+
* A handler<I,S,O> passed in must support three messages:
|
|
557
|
+
* * storage_to_output(S) -> O
|
|
558
|
+
* * input_to_output(I) -> O
|
|
559
|
+
* * input_to_storage(I) -> S
|
|
529
560
|
* (input_to_storage may raise Bootsnap::CompileCache::Uncompilable, which
|
|
530
561
|
* will prevent caching and cause output to be generated with
|
|
531
562
|
* input_to_output)
|
|
@@ -553,7 +584,8 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
553
584
|
struct bs_cache_key cached_key, current_key;
|
|
554
585
|
char * contents = NULL;
|
|
555
586
|
int cache_fd = -1, current_fd = -1;
|
|
556
|
-
int res, valid_cache, exception_tag = 0;
|
|
587
|
+
int res, valid_cache = 0, exception_tag = 0;
|
|
588
|
+
char * errno_provenance = NULL;
|
|
557
589
|
|
|
558
590
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
|
559
591
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
|
@@ -562,20 +594,27 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
562
594
|
VALUE exception; /* ruby exception object to raise instead of returning */
|
|
563
595
|
|
|
564
596
|
/* Open the source file and generate a cache key for it */
|
|
565
|
-
current_fd = open_current_file(path, ¤t_key);
|
|
597
|
+
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
|
566
598
|
if (current_fd < 0) goto fail_errno;
|
|
567
599
|
|
|
568
600
|
/* Open the cache key if it exists, and read its cache key in */
|
|
569
|
-
cache_fd = open_cache_file(cache_path, &cached_key);
|
|
570
|
-
if (cache_fd
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
601
|
+
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
|
602
|
+
if (cache_fd == CACHE_MISSING_OR_INVALID) {
|
|
603
|
+
/* This is ok: valid_cache remains false, we re-populate it. */
|
|
604
|
+
} else if (cache_fd < 0) {
|
|
605
|
+
goto fail_errno;
|
|
606
|
+
} else {
|
|
607
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
|
608
|
+
* it was generated. */
|
|
609
|
+
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
|
610
|
+
}
|
|
575
611
|
|
|
576
612
|
if (valid_cache) {
|
|
577
613
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
|
578
|
-
res = fetch_cached_data(
|
|
614
|
+
res = fetch_cached_data(
|
|
615
|
+
cache_fd, (ssize_t)cached_key.data_size, handler,
|
|
616
|
+
&output_data, &exception_tag, &errno_provenance
|
|
617
|
+
);
|
|
579
618
|
if (exception_tag != 0) goto raise;
|
|
580
619
|
else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
|
|
581
620
|
else if (res == ERROR_WITH_ERRNO) goto fail_errno;
|
|
@@ -586,7 +625,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
586
625
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
|
587
626
|
|
|
588
627
|
/* Read the contents of the source file into a buffer */
|
|
589
|
-
if (bs_read_contents(current_fd, current_key.size, &contents) < 0) goto fail_errno;
|
|
628
|
+
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
|
590
629
|
input_data = rb_str_new_static(contents, current_key.size);
|
|
591
630
|
|
|
592
631
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
|
@@ -603,7 +642,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
603
642
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
|
604
643
|
|
|
605
644
|
/* Write the cache key and storage_data to the cache directory */
|
|
606
|
-
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data);
|
|
645
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
|
607
646
|
if (res < 0) goto fail_errno;
|
|
608
647
|
|
|
609
648
|
/* Having written the cache, now convert storage_data to output_data */
|
|
@@ -613,7 +652,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
613
652
|
/* If output_data is nil, delete the cache entry and generate the output
|
|
614
653
|
* using input_to_output */
|
|
615
654
|
if (NIL_P(output_data)) {
|
|
616
|
-
if (unlink(cache_path) < 0)
|
|
655
|
+
if (unlink(cache_path) < 0) {
|
|
656
|
+
errno_provenance = (char *)"bs_fetch:unlink";
|
|
657
|
+
goto fail_errno;
|
|
658
|
+
}
|
|
617
659
|
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
|
618
660
|
if (exception_tag != 0) goto raise;
|
|
619
661
|
}
|
|
@@ -630,8 +672,7 @@ succeed:
|
|
|
630
672
|
return output_data;
|
|
631
673
|
fail_errno:
|
|
632
674
|
CLEANUP;
|
|
633
|
-
exception =
|
|
634
|
-
if (res) exception = rb_eStandardError;
|
|
675
|
+
exception = rb_syserr_new(errno, errno_provenance);
|
|
635
676
|
rb_exc_raise(exception);
|
|
636
677
|
__builtin_unreachable();
|
|
637
678
|
raise:
|
|
@@ -650,7 +691,17 @@ invalid_type_storage_data:
|
|
|
650
691
|
/********************* Handler Wrappers **************************************/
|
|
651
692
|
/*****************************************************************************
|
|
652
693
|
* Everything after this point in the file is just wrappers to deal with ruby's
|
|
653
|
-
* clunky method of handling exceptions from ruby methods invoked from C
|
|
694
|
+
* clunky method of handling exceptions from ruby methods invoked from C:
|
|
695
|
+
*
|
|
696
|
+
* In order to call a ruby method from C, while protecting against crashing in
|
|
697
|
+
* the event of an exception, we must call the method with rb_protect().
|
|
698
|
+
*
|
|
699
|
+
* rb_protect takes a C function and precisely one argument; however, we want
|
|
700
|
+
* to pass multiple arguments, so we must create structs to wrap them up.
|
|
701
|
+
*
|
|
702
|
+
* These functions return an exception_tag, which, if non-zero, indicates an
|
|
703
|
+
* exception that should be jumped to with rb_jump_tag after cleaning up
|
|
704
|
+
* allocated resources.
|
|
654
705
|
*/
|
|
655
706
|
|
|
656
707
|
struct s2o_data {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require_relative '../load_path_cache'
|
|
2
1
|
require_relative '../explicit_require'
|
|
3
2
|
|
|
4
3
|
module Bootsnap
|
|
@@ -9,8 +8,9 @@ module Bootsnap
|
|
|
9
8
|
def initialize(store, path_obj, development_mode: false)
|
|
10
9
|
@development_mode = development_mode
|
|
11
10
|
@store = store
|
|
12
|
-
@mutex = ::Thread::Mutex.new
|
|
11
|
+
@mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
|
|
13
12
|
@path_obj = path_obj
|
|
13
|
+
@has_relative_paths = nil
|
|
14
14
|
reinitialize
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -23,24 +23,21 @@ module Bootsnap
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# { 'enumerator' => nil, 'enumerator.so' => nil, ... }
|
|
26
|
-
BUILTIN_FEATURES = $LOADED_FEATURES.
|
|
26
|
+
BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
|
|
27
27
|
# Builtin features are of the form 'enumerator.so'.
|
|
28
28
|
# All others include paths.
|
|
29
|
-
next
|
|
29
|
+
next unless feat.size < 20 && !feat.include?('/')
|
|
30
30
|
|
|
31
31
|
base = File.basename(feat, '.*') # enumerator.so -> enumerator
|
|
32
32
|
ext = File.extname(feat) # .so
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
features[feat] = nil # enumerator.so
|
|
35
|
+
features[base] = nil # enumerator
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
37
|
+
next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
|
|
38
|
+
DL_EXTENSIONS.each do |dl_ext|
|
|
39
|
+
features["#{base}#{dl_ext}"] = nil # enumerator.bundle
|
|
41
40
|
end
|
|
42
|
-
|
|
43
|
-
acc
|
|
44
41
|
end.freeze
|
|
45
42
|
|
|
46
43
|
# Try to resolve this feature to an absolute path without traversing the
|
|
@@ -67,7 +64,8 @@ module Bootsnap
|
|
|
67
64
|
# If the extension was one of the ones we explicitly cache (.rb and the
|
|
68
65
|
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
|
69
66
|
# failure and there's nothing more we can do to find the file.
|
|
70
|
-
|
|
67
|
+
# no extension, .rb, (.bundle or .so)
|
|
68
|
+
when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat
|
|
71
69
|
nil
|
|
72
70
|
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
|
73
71
|
# is '.bundle'. This is where we handle that case.
|
|
@@ -152,7 +150,7 @@ module Bootsnap
|
|
|
152
150
|
|
|
153
151
|
def unshift_paths_locked(*paths)
|
|
154
152
|
@store.transaction do
|
|
155
|
-
paths.map(&:to_s).
|
|
153
|
+
paths.map(&:to_s).reverse_each do |path|
|
|
156
154
|
p = Path.new(path)
|
|
157
155
|
next if p.non_directory?
|
|
158
156
|
entries, dirs = p.entries_and_dirs(@store)
|
|
@@ -2,15 +2,6 @@ module Bootsnap
|
|
|
2
2
|
module LoadPathCache
|
|
3
3
|
module CoreExt
|
|
4
4
|
module ActiveSupport
|
|
5
|
-
def self.with_bootsnap_fallback(error)
|
|
6
|
-
yield
|
|
7
|
-
rescue error => e
|
|
8
|
-
# NoMethodError is a NameError, but we only want to handle actual
|
|
9
|
-
# NameError instances.
|
|
10
|
-
raise unless e.class == error
|
|
11
|
-
without_bootsnap_cache { yield }
|
|
12
|
-
end
|
|
13
|
-
|
|
14
5
|
def self.without_bootsnap_cache
|
|
15
6
|
prev = Thread.current[:without_bootsnap_cache] || false
|
|
16
7
|
Thread.current[:without_bootsnap_cache] = true
|
|
@@ -21,9 +12,8 @@ module Bootsnap
|
|
|
21
12
|
|
|
22
13
|
module ClassMethods
|
|
23
14
|
def autoload_paths=(o)
|
|
24
|
-
|
|
15
|
+
super
|
|
25
16
|
Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o)
|
|
26
|
-
r
|
|
27
17
|
end
|
|
28
18
|
|
|
29
19
|
def search_for_file(path)
|
|
@@ -50,13 +40,25 @@ module Bootsnap
|
|
|
50
40
|
# behaviour. The gymnastics here are a bit awkward, but it prevents
|
|
51
41
|
# 200+ lines of monkeypatches.
|
|
52
42
|
def load_missing_constant(from_mod, const_name)
|
|
53
|
-
|
|
43
|
+
super
|
|
44
|
+
rescue NameError => e
|
|
45
|
+
# NoMethodError is a NameError, but we only want to handle actual
|
|
46
|
+
# NameError instances.
|
|
47
|
+
raise unless e.class == NameError
|
|
48
|
+
# We can only confidently handle cases when *this* constant fails
|
|
49
|
+
# to load, not other constants referred to by it.
|
|
50
|
+
raise unless e.name == const_name
|
|
51
|
+
# If the constant was actually loaded, something else went wrong?
|
|
52
|
+
raise if from_mod.const_defined?(const_name)
|
|
53
|
+
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Signature has changed a few times over the years; easiest to not
|
|
57
57
|
# reiterate it with version polymorphism here...
|
|
58
58
|
def depend_on(*)
|
|
59
|
-
|
|
59
|
+
super
|
|
60
|
+
rescue LoadError
|
|
61
|
+
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
end
|