bootsnap 1.12.0 → 1.18.4
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 +73 -1
- data/README.md +17 -8
- data/ext/bootsnap/bootsnap.c +256 -77
- data/ext/bootsnap/extconf.rb +20 -13
- data/lib/bootsnap/bundler.rb +1 -1
- data/lib/bootsnap/cli.rb +28 -23
- data/lib/bootsnap/compile_cache/iseq.rb +15 -9
- data/lib/bootsnap/compile_cache/json.rb +18 -17
- data/lib/bootsnap/compile_cache/yaml.rb +46 -60
- data/lib/bootsnap/compile_cache.rb +11 -15
- data/lib/bootsnap/explicit_require.rb +5 -0
- data/lib/bootsnap/load_path_cache/cache.rb +20 -21
- data/lib/bootsnap/load_path_cache/change_observer.rb +19 -2
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +7 -14
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +2 -2
- data/lib/bootsnap/load_path_cache/path.rb +16 -18
- data/lib/bootsnap/load_path_cache/path_scanner.rb +7 -1
- data/lib/bootsnap/load_path_cache/store.rb +13 -14
- data/lib/bootsnap/load_path_cache.rb +36 -13
- data/lib/bootsnap/setup.rb +1 -1
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +52 -36
- metadata +7 -7
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 = 6;
|
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,23 +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;
|
105
|
+
static bool readonly = false;
|
106
|
+
static bool revalidation = false;
|
107
|
+
static bool perm_issue = false;
|
93
108
|
|
94
109
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
95
110
|
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
111
|
+
static VALUE bs_readonly_set(VALUE self, VALUE enabled);
|
112
|
+
static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
|
96
113
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
97
114
|
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
98
115
|
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
99
116
|
|
100
117
|
/* Helpers */
|
118
|
+
enum cache_status {
|
119
|
+
miss,
|
120
|
+
hit,
|
121
|
+
stale,
|
122
|
+
};
|
101
123
|
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
102
124
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
103
|
-
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);
|
104
130
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
105
131
|
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
106
|
-
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);
|
107
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);
|
108
134
|
static uint32_t get_ruby_revision(void);
|
109
135
|
static uint32_t get_ruby_platform(void);
|
@@ -120,15 +146,6 @@ struct s2o_data;
|
|
120
146
|
struct i2o_data;
|
121
147
|
struct i2s_data;
|
122
148
|
|
123
|
-
/* https://bugs.ruby-lang.org/issues/13667 */
|
124
|
-
extern VALUE rb_get_coverages(void);
|
125
|
-
static VALUE
|
126
|
-
bs_rb_coverage_running(VALUE self)
|
127
|
-
{
|
128
|
-
VALUE cov = rb_get_coverages();
|
129
|
-
return RTEST(cov) ? Qtrue : Qfalse;
|
130
|
-
}
|
131
|
-
|
132
149
|
static VALUE
|
133
150
|
bs_rb_get_path(VALUE self, VALUE fname)
|
134
151
|
{
|
@@ -159,14 +176,14 @@ Init_bootsnap(void)
|
|
159
176
|
|
160
177
|
instrumentation_method = rb_intern("_instrument");
|
161
178
|
|
179
|
+
sym_hit = ID2SYM(rb_intern("hit"));
|
162
180
|
sym_miss = ID2SYM(rb_intern("miss"));
|
163
|
-
rb_global_variable(&sym_miss);
|
164
|
-
|
165
181
|
sym_stale = ID2SYM(rb_intern("stale"));
|
166
|
-
|
182
|
+
sym_revalidated = ID2SYM(rb_intern("revalidated"));
|
167
183
|
|
168
184
|
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
169
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "
|
185
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
186
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
|
170
187
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
171
188
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
172
189
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
@@ -182,6 +199,28 @@ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
|
182
199
|
return enabled;
|
183
200
|
}
|
184
201
|
|
202
|
+
static inline void
|
203
|
+
bs_instrumentation(VALUE event, VALUE path)
|
204
|
+
{
|
205
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
206
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
static VALUE
|
211
|
+
bs_readonly_set(VALUE self, VALUE enabled)
|
212
|
+
{
|
213
|
+
readonly = RTEST(enabled);
|
214
|
+
return enabled;
|
215
|
+
}
|
216
|
+
|
217
|
+
static VALUE
|
218
|
+
bs_revalidation_set(VALUE self, VALUE enabled)
|
219
|
+
{
|
220
|
+
revalidation = RTEST(enabled);
|
221
|
+
return enabled;
|
222
|
+
}
|
223
|
+
|
185
224
|
/*
|
186
225
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
187
226
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -280,17 +319,59 @@ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_C
|
|
280
319
|
* The data_size member is not compared, as it serves more of a "header"
|
281
320
|
* function.
|
282
321
|
*/
|
283
|
-
static
|
284
|
-
|
322
|
+
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
|
323
|
+
struct bs_cache_key *k2) {
|
324
|
+
if (k1->version == k2->version &&
|
325
|
+
k1->ruby_platform == k2->ruby_platform &&
|
326
|
+
k1->compile_option == k2->compile_option &&
|
327
|
+
k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) {
|
328
|
+
if (k1->mtime == k2->mtime) {
|
329
|
+
return hit;
|
330
|
+
}
|
331
|
+
if (revalidation) {
|
332
|
+
return stale;
|
333
|
+
}
|
334
|
+
}
|
335
|
+
return miss;
|
336
|
+
}
|
337
|
+
|
338
|
+
static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
|
339
|
+
struct bs_cache_key *cached_key,
|
340
|
+
const VALUE input_data)
|
341
|
+
{
|
342
|
+
bs_cache_key_digest(current_key, input_data);
|
343
|
+
return current_key->digest == cached_key->digest;
|
344
|
+
}
|
345
|
+
|
346
|
+
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
|
285
347
|
{
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
348
|
+
old_key->mtime = current_key->mtime;
|
349
|
+
lseek(cache_fd, 0, SEEK_SET);
|
350
|
+
ssize_t nwrite = write(cache_fd, old_key, KEY_SIZE);
|
351
|
+
if (nwrite < 0) {
|
352
|
+
*errno_provenance = "update_cache_key:write";
|
353
|
+
return -1;
|
354
|
+
}
|
355
|
+
|
356
|
+
#ifdef HAVE_FDATASYNC
|
357
|
+
if (fdatasync(cache_fd) < 0) {
|
358
|
+
*errno_provenance = "update_cache_key:fdatasync";
|
359
|
+
return -1;
|
360
|
+
}
|
361
|
+
#endif
|
362
|
+
|
363
|
+
return 0;
|
364
|
+
}
|
365
|
+
|
366
|
+
/*
|
367
|
+
* Fills the cache key digest.
|
368
|
+
*/
|
369
|
+
static void bs_cache_key_digest(struct bs_cache_key *key,
|
370
|
+
const VALUE input_data) {
|
371
|
+
if (key->digest_set)
|
372
|
+
return;
|
373
|
+
key->digest = fnv1a_64(input_data);
|
374
|
+
key->digest_set = 1;
|
294
375
|
}
|
295
376
|
|
296
377
|
/*
|
@@ -346,17 +427,34 @@ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
346
427
|
|
347
428
|
return bs_precompile(path, path_v, cache_path, handler);
|
348
429
|
}
|
430
|
+
|
431
|
+
static int bs_open_noatime(const char *path, int flags) {
|
432
|
+
int fd = 1;
|
433
|
+
if (!perm_issue) {
|
434
|
+
fd = open(path, flags | O_NOATIME);
|
435
|
+
if (fd < 0 && errno == EPERM) {
|
436
|
+
errno = 0;
|
437
|
+
perm_issue = true;
|
438
|
+
}
|
439
|
+
}
|
440
|
+
|
441
|
+
if (perm_issue) {
|
442
|
+
fd = open(path, flags);
|
443
|
+
}
|
444
|
+
return fd;
|
445
|
+
}
|
446
|
+
|
349
447
|
/*
|
350
448
|
* Open the file we want to load/cache and generate a cache key for it if it
|
351
449
|
* was loaded.
|
352
450
|
*/
|
353
451
|
static int
|
354
|
-
open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
452
|
+
open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
355
453
|
{
|
356
454
|
struct stat statbuf;
|
357
455
|
int fd;
|
358
456
|
|
359
|
-
fd =
|
457
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
360
458
|
if (fd < 0) {
|
361
459
|
*errno_provenance = "bs_fetch:open_current_file:open";
|
362
460
|
return fd;
|
@@ -367,7 +465,9 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
367
465
|
|
368
466
|
if (fstat(fd, &statbuf) < 0) {
|
369
467
|
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
468
|
+
int previous_errno = errno;
|
370
469
|
close(fd);
|
470
|
+
errno = previous_errno;
|
371
471
|
return -1;
|
372
472
|
}
|
373
473
|
|
@@ -377,6 +477,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
377
477
|
key->ruby_revision = current_ruby_revision;
|
378
478
|
key->size = (uint64_t)statbuf.st_size;
|
379
479
|
key->mtime = (uint64_t)statbuf.st_mtime;
|
480
|
+
key->digest_set = false;
|
380
481
|
|
381
482
|
return fd;
|
382
483
|
}
|
@@ -420,7 +521,12 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
420
521
|
{
|
421
522
|
int fd, res;
|
422
523
|
|
423
|
-
|
524
|
+
if (readonly || !revalidation) {
|
525
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
526
|
+
} else {
|
527
|
+
fd = bs_open_noatime(path, O_RDWR);
|
528
|
+
}
|
529
|
+
|
424
530
|
if (fd < 0) {
|
425
531
|
*errno_provenance = "bs_fetch:open_cache_file:open";
|
426
532
|
return CACHE_MISS;
|
@@ -457,7 +563,6 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
457
563
|
static int
|
458
564
|
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
459
565
|
{
|
460
|
-
char * data = NULL;
|
461
566
|
ssize_t nread;
|
462
567
|
int ret;
|
463
568
|
|
@@ -469,8 +574,8 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
469
574
|
ret = ERROR_WITH_ERRNO;
|
470
575
|
goto done;
|
471
576
|
}
|
472
|
-
|
473
|
-
nread = read(fd,
|
577
|
+
storage_data = rb_str_buf_new(data_size);
|
578
|
+
nread = read(fd, RSTRING_PTR(storage_data), data_size);
|
474
579
|
if (nread < 0) {
|
475
580
|
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
476
581
|
ret = ERROR_WITH_ERRNO;
|
@@ -481,7 +586,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
481
586
|
goto done;
|
482
587
|
}
|
483
588
|
|
484
|
-
storage_data
|
589
|
+
rb_str_set_len(storage_data, nread);
|
485
590
|
|
486
591
|
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
487
592
|
if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
@@ -490,7 +595,6 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
490
595
|
}
|
491
596
|
ret = 0;
|
492
597
|
done:
|
493
|
-
if (data != NULL) xfree(data);
|
494
598
|
return ret;
|
495
599
|
}
|
496
600
|
|
@@ -597,17 +701,22 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
|
|
597
701
|
|
598
702
|
|
599
703
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
600
|
-
* long,
|
601
|
-
static
|
602
|
-
bs_read_contents(int fd, size_t size,
|
704
|
+
* long, returning a Ruby string on success and Qfalse on failure */
|
705
|
+
static VALUE
|
706
|
+
bs_read_contents(int fd, size_t size, const char ** errno_provenance)
|
603
707
|
{
|
708
|
+
VALUE contents;
|
604
709
|
ssize_t nread;
|
605
|
-
|
606
|
-
nread = read(fd,
|
710
|
+
contents = rb_str_buf_new(size);
|
711
|
+
nread = read(fd, RSTRING_PTR(contents), size);
|
712
|
+
|
607
713
|
if (nread < 0) {
|
608
714
|
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
715
|
+
return Qfalse;
|
716
|
+
} else {
|
717
|
+
rb_str_set_len(contents, nread);
|
718
|
+
return contents;
|
609
719
|
}
|
610
|
-
return nread;
|
611
720
|
}
|
612
721
|
|
613
722
|
/*
|
@@ -658,38 +767,67 @@ static VALUE
|
|
658
767
|
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
659
768
|
{
|
660
769
|
struct bs_cache_key cached_key, current_key;
|
661
|
-
char * contents = NULL;
|
662
770
|
int cache_fd = -1, current_fd = -1;
|
663
771
|
int res, valid_cache = 0, exception_tag = 0;
|
664
772
|
const char * errno_provenance = NULL;
|
665
773
|
|
666
|
-
VALUE
|
774
|
+
VALUE status = Qfalse;
|
775
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
667
776
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
668
777
|
VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
|
669
778
|
|
670
779
|
VALUE exception; /* ruby exception object to raise instead of returning */
|
780
|
+
VALUE exception_message; /* ruby exception string to use instead of errno_provenance */
|
671
781
|
|
672
782
|
/* Open the source file and generate a cache key for it */
|
673
783
|
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
674
|
-
if (current_fd < 0)
|
784
|
+
if (current_fd < 0) {
|
785
|
+
exception_message = path_v;
|
786
|
+
goto fail_errno;
|
787
|
+
}
|
675
788
|
|
676
789
|
/* Open the cache key if it exists, and read its cache key in */
|
677
790
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
678
791
|
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
679
792
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
680
|
-
|
681
|
-
rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
682
|
-
}
|
793
|
+
bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
683
794
|
} else if (cache_fd < 0) {
|
795
|
+
exception_message = rb_str_new_cstr(cache_path);
|
684
796
|
goto fail_errno;
|
685
797
|
} else {
|
686
798
|
/* True if the cache existed and no invalidating changes have occurred since
|
687
799
|
* it was generated. */
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
800
|
+
|
801
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
802
|
+
case hit:
|
803
|
+
status = sym_hit;
|
804
|
+
valid_cache = true;
|
805
|
+
break;
|
806
|
+
case miss:
|
807
|
+
valid_cache = false;
|
808
|
+
break;
|
809
|
+
case stale:
|
810
|
+
valid_cache = false;
|
811
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size,
|
812
|
+
&errno_provenance)) == Qfalse) {
|
813
|
+
exception_message = path_v;
|
814
|
+
goto fail_errno;
|
815
|
+
}
|
816
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
817
|
+
if (valid_cache) {
|
818
|
+
if (!readonly) {
|
819
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
820
|
+
exception_message = path_v;
|
821
|
+
goto fail_errno;
|
822
|
+
}
|
823
|
+
}
|
824
|
+
status = sym_revalidated;
|
692
825
|
}
|
826
|
+
break;
|
827
|
+
};
|
828
|
+
|
829
|
+
if (!valid_cache) {
|
830
|
+
status = sym_stale;
|
693
831
|
}
|
694
832
|
}
|
695
833
|
|
@@ -703,13 +841,18 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
703
841
|
else if (res == CACHE_UNCOMPILABLE) {
|
704
842
|
/* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
|
705
843
|
This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
|
706
|
-
if (bs_read_contents(current_fd, current_key.size, &
|
707
|
-
|
844
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
845
|
+
exception_message = path_v;
|
846
|
+
goto fail_errno;
|
847
|
+
}
|
708
848
|
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
709
849
|
if (exception_tag != 0) goto raise;
|
710
850
|
goto succeed;
|
711
851
|
} else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
712
|
-
else if (res == ERROR_WITH_ERRNO)
|
852
|
+
else if (res == ERROR_WITH_ERRNO){
|
853
|
+
exception_message = rb_str_new_cstr(cache_path);
|
854
|
+
goto fail_errno;
|
855
|
+
}
|
713
856
|
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
714
857
|
}
|
715
858
|
close(cache_fd);
|
@@ -717,8 +860,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
717
860
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
718
861
|
|
719
862
|
/* Read the contents of the source file into a buffer */
|
720
|
-
if (bs_read_contents(current_fd, current_key.size, &
|
721
|
-
|
863
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
864
|
+
exception_message = path_v;
|
865
|
+
goto fail_errno;
|
866
|
+
}
|
722
867
|
|
723
868
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
724
869
|
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
@@ -737,6 +882,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
737
882
|
* We do however ignore any failures to persist the cache, as it's better
|
738
883
|
* to move along, than to interrupt the process.
|
739
884
|
*/
|
885
|
+
bs_cache_key_digest(¤t_key, input_data);
|
740
886
|
atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
741
887
|
|
742
888
|
/* Having written the cache, now convert storage_data to output_data */
|
@@ -755,6 +901,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
755
901
|
* No point raising an error */
|
756
902
|
if (errno != ENOENT) {
|
757
903
|
errno_provenance = "bs_fetch:unlink";
|
904
|
+
exception_message = rb_str_new_cstr(cache_path);
|
758
905
|
goto fail_errno;
|
759
906
|
}
|
760
907
|
}
|
@@ -765,16 +912,22 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
765
912
|
goto succeed; /* output_data is now the correct return. */
|
766
913
|
|
767
914
|
#define CLEANUP \
|
768
|
-
if (contents != NULL) xfree(contents); \
|
769
915
|
if (current_fd >= 0) close(current_fd); \
|
770
|
-
if (cache_fd >= 0) close(cache_fd);
|
916
|
+
if (cache_fd >= 0) close(cache_fd); \
|
917
|
+
if (status != Qfalse) bs_instrumentation(status, path_v);
|
771
918
|
|
772
919
|
succeed:
|
773
920
|
CLEANUP;
|
774
921
|
return output_data;
|
775
922
|
fail_errno:
|
776
923
|
CLEANUP;
|
777
|
-
|
924
|
+
if (errno_provenance) {
|
925
|
+
exception_message = rb_str_concat(
|
926
|
+
rb_str_new_cstr(errno_provenance),
|
927
|
+
rb_str_concat(rb_str_new_cstr(": "), exception_message)
|
928
|
+
);
|
929
|
+
}
|
930
|
+
exception = rb_syserr_new_str(errno, exception_message);
|
778
931
|
rb_exc_raise(exception);
|
779
932
|
__builtin_unreachable();
|
780
933
|
raise:
|
@@ -792,13 +945,16 @@ invalid_type_storage_data:
|
|
792
945
|
static VALUE
|
793
946
|
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
794
947
|
{
|
948
|
+
if (readonly) {
|
949
|
+
return Qfalse;
|
950
|
+
}
|
951
|
+
|
795
952
|
struct bs_cache_key cached_key, current_key;
|
796
|
-
char * contents = NULL;
|
797
953
|
int cache_fd = -1, current_fd = -1;
|
798
954
|
int res, valid_cache = 0, exception_tag = 0;
|
799
955
|
const char * errno_provenance = NULL;
|
800
956
|
|
801
|
-
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
957
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
802
958
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
803
959
|
|
804
960
|
/* Open the source file and generate a cache key for it */
|
@@ -814,7 +970,26 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
814
970
|
} else {
|
815
971
|
/* True if the cache existed and no invalidating changes have occurred since
|
816
972
|
* it was generated. */
|
817
|
-
|
973
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
974
|
+
case hit:
|
975
|
+
valid_cache = true;
|
976
|
+
break;
|
977
|
+
case miss:
|
978
|
+
valid_cache = false;
|
979
|
+
break;
|
980
|
+
case stale:
|
981
|
+
valid_cache = false;
|
982
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
983
|
+
goto fail;
|
984
|
+
}
|
985
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
986
|
+
if (valid_cache) {
|
987
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
988
|
+
goto fail;
|
989
|
+
}
|
990
|
+
}
|
991
|
+
break;
|
992
|
+
};
|
818
993
|
}
|
819
994
|
|
820
995
|
if (valid_cache) {
|
@@ -826,8 +1001,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
826
1001
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
827
1002
|
|
828
1003
|
/* Read the contents of the source file into a buffer */
|
829
|
-
if (bs_read_contents(current_fd, current_key.size, &
|
830
|
-
input_data = rb_str_new(contents, current_key.size);
|
1004
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
|
831
1005
|
|
832
1006
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
833
1007
|
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
@@ -842,13 +1016,13 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
842
1016
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
843
1017
|
|
844
1018
|
/* Write the cache key and storage_data to the cache directory */
|
1019
|
+
bs_cache_key_digest(¤t_key, input_data);
|
845
1020
|
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
846
1021
|
if (res < 0) goto fail;
|
847
1022
|
|
848
1023
|
goto succeed;
|
849
1024
|
|
850
1025
|
#define CLEANUP \
|
851
|
-
if (contents != NULL) xfree(contents); \
|
852
1026
|
if (current_fd >= 0) close(current_fd); \
|
853
1027
|
if (cache_fd >= 0) close(cache_fd);
|
854
1028
|
|
@@ -945,12 +1119,17 @@ try_input_to_storage(VALUE arg)
|
|
945
1119
|
static int
|
946
1120
|
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
947
1121
|
{
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
1122
|
+
if (readonly) {
|
1123
|
+
*storage_data = rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
1124
|
+
return 0;
|
1125
|
+
} else {
|
1126
|
+
int state;
|
1127
|
+
struct i2s_data i2s_data = {
|
1128
|
+
.handler = handler,
|
1129
|
+
.input_data = input_data,
|
1130
|
+
.pathval = pathval,
|
1131
|
+
};
|
1132
|
+
*storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
|
1133
|
+
return state;
|
1134
|
+
}
|
956
1135
|
}
|
data/ext/bootsnap/extconf.rb
CHANGED
@@ -1,26 +1,33 @@
|
|
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")
|
24
31
|
else
|
25
|
-
File.write("Makefile", dummy_makefile($srcdir).join
|
32
|
+
File.write("Makefile", dummy_makefile($srcdir).join)
|
26
33
|
end
|