bootsnap 1.6.0 → 1.15.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 +4 -4
- data/CHANGELOG.md +174 -0
- data/LICENSE.txt +1 -1
- data/README.md +41 -18
- data/exe/bootsnap +1 -1
- data/ext/bootsnap/bootsnap.c +133 -105
- data/ext/bootsnap/extconf.rb +21 -14
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +6 -1
- data/lib/bootsnap/cli.rb +84 -49
- data/lib/bootsnap/compile_cache/iseq.rb +43 -13
- data/lib/bootsnap/compile_cache/json.rb +93 -0
- data/lib/bootsnap/compile_cache/yaml.rb +299 -61
- data/lib/bootsnap/compile_cache.rb +24 -7
- data/lib/bootsnap/explicit_require.rb +4 -3
- data/lib/bootsnap/load_path_cache/cache.rb +63 -35
- data/lib/bootsnap/load_path_cache/change_observer.rb +17 -2
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +27 -95
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +36 -25
- data/lib/bootsnap/load_path_cache/path.rb +39 -17
- data/lib/bootsnap/load_path_cache/path_scanner.rb +25 -7
- data/lib/bootsnap/load_path_cache/store.rb +64 -24
- data/lib/bootsnap/load_path_cache.rb +31 -38
- data/lib/bootsnap/setup.rb +2 -36
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +125 -36
- metadata +8 -79
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -14,16 +14,11 @@
|
|
14
14
|
#include "bootsnap.h"
|
15
15
|
#include "ruby.h"
|
16
16
|
#include <stdint.h>
|
17
|
+
#include <stdbool.h>
|
17
18
|
#include <sys/types.h>
|
18
19
|
#include <errno.h>
|
19
20
|
#include <fcntl.h>
|
20
21
|
#include <sys/stat.h>
|
21
|
-
#ifndef _WIN32
|
22
|
-
#include <sys/utsname.h>
|
23
|
-
#endif
|
24
|
-
#ifdef __GLIBC__
|
25
|
-
#include <gnu/libc-version.h>
|
26
|
-
#endif
|
27
22
|
|
28
23
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
29
24
|
* 981 for the cache dir */
|
@@ -34,6 +29,10 @@
|
|
34
29
|
|
35
30
|
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
36
31
|
|
32
|
+
#ifndef RB_UNLIKELY
|
33
|
+
#define RB_UNLIKELY(x) (x)
|
34
|
+
#endif
|
35
|
+
|
37
36
|
/*
|
38
37
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
39
38
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -70,7 +69,7 @@ struct bs_cache_key {
|
|
70
69
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
71
70
|
|
72
71
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
73
|
-
static const uint32_t current_version =
|
72
|
+
static const uint32_t current_version = 4;
|
74
73
|
|
75
74
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
76
75
|
* new OS ABI, etc. */
|
@@ -86,17 +85,22 @@ static mode_t current_umask;
|
|
86
85
|
static VALUE rb_mBootsnap;
|
87
86
|
static VALUE rb_mBootsnap_CompileCache;
|
88
87
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
89
|
-
static VALUE
|
90
|
-
static ID
|
88
|
+
static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
89
|
+
static ID instrumentation_method;
|
90
|
+
static VALUE sym_miss;
|
91
|
+
static VALUE sym_stale;
|
92
|
+
static bool instrumentation_enabled = false;
|
93
|
+
static bool readonly = false;
|
91
94
|
|
92
95
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
96
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
97
|
+
static VALUE bs_readonly_set(VALUE self, VALUE enabled);
|
93
98
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
94
99
|
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
95
100
|
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
96
101
|
|
97
102
|
/* Helpers */
|
98
|
-
static
|
99
|
-
static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
103
|
+
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
100
104
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
101
105
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
102
106
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
@@ -111,10 +115,8 @@ static uint32_t get_ruby_platform(void);
|
|
111
115
|
* exception.
|
112
116
|
*/
|
113
117
|
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
114
|
-
static VALUE prot_storage_to_output(VALUE arg);
|
115
118
|
static VALUE prot_input_to_output(VALUE arg);
|
116
119
|
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
117
|
-
static VALUE prot_input_to_storage(VALUE arg);
|
118
120
|
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
119
121
|
struct s2o_data;
|
120
122
|
struct i2o_data;
|
@@ -129,6 +131,12 @@ bs_rb_coverage_running(VALUE self)
|
|
129
131
|
return RTEST(cov) ? Qtrue : Qfalse;
|
130
132
|
}
|
131
133
|
|
134
|
+
static VALUE
|
135
|
+
bs_rb_get_path(VALUE self, VALUE fname)
|
136
|
+
{
|
137
|
+
return rb_get_path(fname);
|
138
|
+
}
|
139
|
+
|
132
140
|
/*
|
133
141
|
* Ruby C extensions are initialized by calling Init_<extname>.
|
134
142
|
*
|
@@ -140,15 +148,27 @@ void
|
|
140
148
|
Init_bootsnap(void)
|
141
149
|
{
|
142
150
|
rb_mBootsnap = rb_define_module("Bootsnap");
|
151
|
+
|
152
|
+
rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
|
153
|
+
|
143
154
|
rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
|
144
155
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
145
|
-
|
156
|
+
rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
|
157
|
+
rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
|
146
158
|
|
147
159
|
current_ruby_revision = get_ruby_revision();
|
148
160
|
current_ruby_platform = get_ruby_platform();
|
149
161
|
|
150
|
-
|
162
|
+
instrumentation_method = rb_intern("_instrument");
|
163
|
+
|
164
|
+
sym_miss = ID2SYM(rb_intern("miss"));
|
165
|
+
rb_global_variable(&sym_miss);
|
166
|
+
|
167
|
+
sym_stale = ID2SYM(rb_intern("stale"));
|
168
|
+
rb_global_variable(&sym_stale);
|
151
169
|
|
170
|
+
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
171
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
152
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
153
173
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
154
174
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
@@ -158,6 +178,20 @@ Init_bootsnap(void)
|
|
158
178
|
umask(current_umask);
|
159
179
|
}
|
160
180
|
|
181
|
+
static VALUE
|
182
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
183
|
+
{
|
184
|
+
instrumentation_enabled = RTEST(enabled);
|
185
|
+
return enabled;
|
186
|
+
}
|
187
|
+
|
188
|
+
static VALUE
|
189
|
+
bs_readonly_set(VALUE self, VALUE enabled)
|
190
|
+
{
|
191
|
+
readonly = RTEST(enabled);
|
192
|
+
return enabled;
|
193
|
+
}
|
194
|
+
|
161
195
|
/*
|
162
196
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
163
197
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -175,22 +209,13 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
175
209
|
return Qnil;
|
176
210
|
}
|
177
211
|
|
178
|
-
/*
|
179
|
-
* We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
|
180
|
-
* it has several nice properties:
|
181
|
-
*
|
182
|
-
* - Tiny implementation
|
183
|
-
* - No external dependency
|
184
|
-
* - Solid performance
|
185
|
-
* - Solid randomness
|
186
|
-
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
187
|
-
*/
|
188
212
|
static uint64_t
|
189
|
-
fnv1a_64_iter(uint64_t h, const
|
213
|
+
fnv1a_64_iter(uint64_t h, const VALUE str)
|
190
214
|
{
|
191
|
-
unsigned char *s = (unsigned char *)str;
|
215
|
+
unsigned char *s = (unsigned char *)RSTRING_PTR(str);
|
216
|
+
unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
|
192
217
|
|
193
|
-
while (
|
218
|
+
while (s < str_end) {
|
194
219
|
h ^= (uint64_t)*s++;
|
195
220
|
h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
|
196
221
|
}
|
@@ -199,7 +224,7 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
199
224
|
}
|
200
225
|
|
201
226
|
static uint64_t
|
202
|
-
fnv1a_64(const
|
227
|
+
fnv1a_64(const VALUE str)
|
203
228
|
{
|
204
229
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
205
230
|
return fnv1a_64_iter(h, str);
|
@@ -220,7 +245,7 @@ get_ruby_revision(void)
|
|
220
245
|
} else {
|
221
246
|
uint64_t hash;
|
222
247
|
|
223
|
-
hash = fnv1a_64(
|
248
|
+
hash = fnv1a_64(ruby_revision);
|
224
249
|
return (uint32_t)(hash >> 32);
|
225
250
|
}
|
226
251
|
}
|
@@ -228,10 +253,6 @@ get_ruby_revision(void)
|
|
228
253
|
/*
|
229
254
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
230
255
|
* (or OS version), we need to invalidate the cache.
|
231
|
-
*
|
232
|
-
* We actually factor in some extra information here, to be extra confident
|
233
|
-
* that we don't try to re-use caches that will not be compatible, by factoring
|
234
|
-
* in utsname.version.
|
235
256
|
*/
|
236
257
|
static uint32_t
|
237
258
|
get_ruby_platform(void)
|
@@ -240,23 +261,8 @@ get_ruby_platform(void)
|
|
240
261
|
VALUE ruby_platform;
|
241
262
|
|
242
263
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
243
|
-
hash = fnv1a_64(
|
244
|
-
|
245
|
-
#ifdef _WIN32
|
246
|
-
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
247
|
-
#elif defined(__GLIBC__)
|
248
|
-
hash = fnv1a_64_iter(hash, gnu_get_libc_version());
|
249
|
-
return (uint32_t)(hash >> 32);
|
250
|
-
#else
|
251
|
-
struct utsname utsname;
|
252
|
-
|
253
|
-
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
254
|
-
if (uname(&utsname) >= 0) {
|
255
|
-
hash = fnv1a_64_iter(hash, utsname.version);
|
256
|
-
}
|
257
|
-
|
264
|
+
hash = fnv1a_64(ruby_platform);
|
258
265
|
return (uint32_t)(hash >> 32);
|
259
|
-
#endif
|
260
266
|
}
|
261
267
|
|
262
268
|
/*
|
@@ -267,13 +273,13 @@ get_ruby_platform(void)
|
|
267
273
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
268
274
|
*/
|
269
275
|
static void
|
270
|
-
bs_cache_path(const char * cachedir, const
|
276
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
271
277
|
{
|
272
278
|
uint64_t hash = fnv1a_64(path);
|
273
279
|
uint8_t first_byte = (hash >> (64 - 8));
|
274
280
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
275
281
|
|
276
|
-
sprintf(*cache_path, "%s/%
|
282
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
277
283
|
}
|
278
284
|
|
279
285
|
/*
|
@@ -319,7 +325,7 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
|
|
319
325
|
char cache_path[MAX_CACHEPATH_SIZE];
|
320
326
|
|
321
327
|
/* generate cache path to cache_path */
|
322
|
-
bs_cache_path(cachedir,
|
328
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
323
329
|
|
324
330
|
return bs_fetch(path, path_v, cache_path, handler, args);
|
325
331
|
}
|
@@ -346,7 +352,7 @@ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
346
352
|
char cache_path[MAX_CACHEPATH_SIZE];
|
347
353
|
|
348
354
|
/* generate cache path to cache_path */
|
349
|
-
bs_cache_path(cachedir,
|
355
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
350
356
|
|
351
357
|
return bs_precompile(path, path_v, cache_path, handler);
|
352
358
|
}
|
@@ -386,7 +392,9 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
386
392
|
}
|
387
393
|
|
388
394
|
#define ERROR_WITH_ERRNO -1
|
389
|
-
#define
|
395
|
+
#define CACHE_MISS -2
|
396
|
+
#define CACHE_STALE -3
|
397
|
+
#define CACHE_UNCOMPILABLE -4
|
390
398
|
|
391
399
|
/*
|
392
400
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -394,15 +402,16 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
394
402
|
*
|
395
403
|
* Possible return values:
|
396
404
|
* - 0 (OK, key was loaded)
|
397
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
398
405
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
406
|
+
* - CACHE_MISS (-2)
|
407
|
+
* - CACHE_STALE (-3)
|
399
408
|
*/
|
400
409
|
static int
|
401
410
|
bs_read_key(int fd, struct bs_cache_key * key)
|
402
411
|
{
|
403
412
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
404
413
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
405
|
-
if (nread < KEY_SIZE) return
|
414
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
406
415
|
return 0;
|
407
416
|
}
|
408
417
|
|
@@ -412,7 +421,8 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
412
421
|
*
|
413
422
|
* Possible return values:
|
414
423
|
* - 0 (OK, key was loaded)
|
415
|
-
* -
|
424
|
+
* - CACHE_MISS (-2)
|
425
|
+
* - CACHE_STALE (-3)
|
416
426
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
417
427
|
*/
|
418
428
|
static int
|
@@ -423,8 +433,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
423
433
|
fd = open(path, O_RDONLY);
|
424
434
|
if (fd < 0) {
|
425
435
|
*errno_provenance = "bs_fetch:open_cache_file:open";
|
426
|
-
|
427
|
-
return ERROR_WITH_ERRNO;
|
436
|
+
return CACHE_MISS;
|
428
437
|
}
|
429
438
|
#ifdef _WIN32
|
430
439
|
setmode(fd, O_BINARY);
|
@@ -467,24 +476,28 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
467
476
|
if (data_size > 100000000000) {
|
468
477
|
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
469
478
|
errno = EINVAL; /* because wtf? */
|
470
|
-
ret =
|
479
|
+
ret = ERROR_WITH_ERRNO;
|
471
480
|
goto done;
|
472
481
|
}
|
473
482
|
data = ALLOC_N(char, data_size);
|
474
483
|
nread = read(fd, data, data_size);
|
475
484
|
if (nread < 0) {
|
476
485
|
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
477
|
-
ret =
|
486
|
+
ret = ERROR_WITH_ERRNO;
|
478
487
|
goto done;
|
479
488
|
}
|
480
489
|
if (nread != data_size) {
|
481
|
-
ret =
|
490
|
+
ret = CACHE_STALE;
|
482
491
|
goto done;
|
483
492
|
}
|
484
493
|
|
485
494
|
storage_data = rb_str_new(data, data_size);
|
486
495
|
|
487
496
|
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
497
|
+
if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
498
|
+
ret = CACHE_UNCOMPILABLE;
|
499
|
+
goto done;
|
500
|
+
}
|
488
501
|
ret = 0;
|
489
502
|
done:
|
490
503
|
if (data != NULL) xfree(data);
|
@@ -672,14 +685,22 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
672
685
|
|
673
686
|
/* Open the cache key if it exists, and read its cache key in */
|
674
687
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
675
|
-
if (cache_fd ==
|
688
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
676
689
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
690
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
691
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
692
|
+
}
|
677
693
|
} else if (cache_fd < 0) {
|
678
694
|
goto fail_errno;
|
679
695
|
} else {
|
680
696
|
/* True if the cache existed and no invalidating changes have occurred since
|
681
697
|
* it was generated. */
|
682
698
|
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
699
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
700
|
+
if (!valid_cache) {
|
701
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
|
702
|
+
}
|
703
|
+
}
|
683
704
|
}
|
684
705
|
|
685
706
|
if (valid_cache) {
|
@@ -688,10 +709,18 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
688
709
|
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
689
710
|
&output_data, &exception_tag, &errno_provenance
|
690
711
|
);
|
691
|
-
if (exception_tag != 0)
|
692
|
-
else if (res ==
|
693
|
-
|
694
|
-
|
712
|
+
if (exception_tag != 0) goto raise;
|
713
|
+
else if (res == CACHE_UNCOMPILABLE) {
|
714
|
+
/* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
|
715
|
+
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, &contents, &errno_provenance) < 0) goto fail_errno;
|
717
|
+
input_data = rb_str_new(contents, current_key.size);
|
718
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
719
|
+
if (exception_tag != 0) goto raise;
|
720
|
+
goto succeed;
|
721
|
+
} else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
722
|
+
else if (res == ERROR_WITH_ERRNO) goto fail_errno;
|
723
|
+
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
695
724
|
}
|
696
725
|
close(cache_fd);
|
697
726
|
cache_fd = -1;
|
@@ -706,7 +735,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
706
735
|
if (exception_tag != 0) goto raise;
|
707
736
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
708
737
|
* to cache anything; just return input_to_output(input_data) */
|
709
|
-
if (storage_data ==
|
738
|
+
if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
710
739
|
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
711
740
|
if (exception_tag != 0) goto raise;
|
712
741
|
goto succeed;
|
@@ -714,20 +743,30 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
714
743
|
/* If storage_data isn't a string, we can't cache it */
|
715
744
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
716
745
|
|
717
|
-
/*
|
718
|
-
|
719
|
-
|
746
|
+
/* Attempt to write the cache key and storage_data to the cache directory.
|
747
|
+
* We do however ignore any failures to persist the cache, as it's better
|
748
|
+
* to move along, than to interrupt the process.
|
749
|
+
*/
|
750
|
+
atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
720
751
|
|
721
752
|
/* Having written the cache, now convert storage_data to output_data */
|
722
753
|
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
723
754
|
if (exception_tag != 0) goto raise;
|
724
755
|
|
725
|
-
|
726
|
-
|
727
|
-
|
756
|
+
if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
757
|
+
/* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
|
758
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
759
|
+
if (exception_tag != 0) goto raise;
|
760
|
+
} else if (NIL_P(output_data)) {
|
761
|
+
/* If output_data is nil, delete the cache entry and generate the output
|
762
|
+
* using input_to_output */
|
728
763
|
if (unlink(cache_path) < 0) {
|
729
|
-
|
730
|
-
|
764
|
+
/* If the cache was already deleted, it might be that another process did it before us.
|
765
|
+
* No point raising an error */
|
766
|
+
if (errno != ENOENT) {
|
767
|
+
errno_provenance = "bs_fetch:unlink";
|
768
|
+
goto fail_errno;
|
769
|
+
}
|
731
770
|
}
|
732
771
|
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
733
772
|
if (exception_tag != 0) goto raise;
|
@@ -778,7 +817,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
778
817
|
|
779
818
|
/* Open the cache key if it exists, and read its cache key in */
|
780
819
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
781
|
-
if (cache_fd ==
|
820
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
782
821
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
783
822
|
} else if (cache_fd < 0) {
|
784
823
|
goto fail;
|
@@ -806,7 +845,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
806
845
|
|
807
846
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
808
847
|
* to cache anything; just return false */
|
809
|
-
if (storage_data ==
|
848
|
+
if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
810
849
|
goto fail;
|
811
850
|
}
|
812
851
|
/* If storage_data isn't a string, we can't cache it */
|
@@ -869,7 +908,7 @@ struct i2s_data {
|
|
869
908
|
};
|
870
909
|
|
871
910
|
static VALUE
|
872
|
-
|
911
|
+
try_storage_to_output(VALUE arg)
|
873
912
|
{
|
874
913
|
struct s2o_data * data = (struct s2o_data *)arg;
|
875
914
|
return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
|
@@ -884,7 +923,7 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
|
|
884
923
|
.args = args,
|
885
924
|
.storage_data = storage_data,
|
886
925
|
};
|
887
|
-
*output_data = rb_protect(
|
926
|
+
*output_data = rb_protect(try_storage_to_output, (VALUE)&s2o_data, &state);
|
888
927
|
return state;
|
889
928
|
}
|
890
929
|
|
@@ -913,31 +952,20 @@ try_input_to_storage(VALUE arg)
|
|
913
952
|
return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
|
914
953
|
}
|
915
954
|
|
916
|
-
static VALUE
|
917
|
-
rescue_input_to_storage(VALUE arg, VALUE e)
|
918
|
-
{
|
919
|
-
return uncompilable;
|
920
|
-
}
|
921
|
-
|
922
|
-
static VALUE
|
923
|
-
prot_input_to_storage(VALUE arg)
|
924
|
-
{
|
925
|
-
struct i2s_data * data = (struct i2s_data *)arg;
|
926
|
-
return rb_rescue2(
|
927
|
-
try_input_to_storage, (VALUE)data,
|
928
|
-
rescue_input_to_storage, Qnil,
|
929
|
-
rb_eBootsnap_CompileCache_Uncompilable, 0);
|
930
|
-
}
|
931
|
-
|
932
955
|
static int
|
933
956
|
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
934
957
|
{
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
958
|
+
if (readonly) {
|
959
|
+
*storage_data = rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
960
|
+
return 0;
|
961
|
+
} else {
|
962
|
+
int state;
|
963
|
+
struct i2s_data i2s_data = {
|
964
|
+
.handler = handler,
|
965
|
+
.input_data = input_data,
|
966
|
+
.pathval = pathval,
|
967
|
+
};
|
968
|
+
*storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
|
969
|
+
return state;
|
970
|
+
}
|
943
971
|
}
|
data/ext/bootsnap/extconf.rb
CHANGED
@@ -1,19 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require("mkmf")
|
3
|
-
$CFLAGS << ' -O3 '
|
4
|
-
$CFLAGS << ' -std=c99'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
$CFLAGS << ' -Wall'
|
10
|
-
$CFLAGS << ' -Werror'
|
11
|
-
$CFLAGS << ' -Wextra'
|
12
|
-
$CFLAGS << ' -Wpedantic'
|
5
|
+
if RUBY_ENGINE == "ruby"
|
6
|
+
$CFLAGS << " -O3 "
|
7
|
+
$CFLAGS << " -std=c99"
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
# ruby.h has some -Wpedantic fails in some cases
|
10
|
+
# (e.g. https://github.com/Shopify/bootsnap/issues/15)
|
11
|
+
unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
|
12
|
+
$CFLAGS << " -Wall"
|
13
|
+
$CFLAGS << " -Werror"
|
14
|
+
$CFLAGS << " -Wextra"
|
15
|
+
$CFLAGS << " -Wpedantic"
|
18
16
|
|
19
|
-
|
17
|
+
$CFLAGS << " -Wno-unused-parameter" # VALUE self has to be there but we don't care what it is.
|
18
|
+
$CFLAGS << " -Wno-keyword-macro" # hiding return
|
19
|
+
$CFLAGS << " -Wno-gcc-compat" # ruby.h 2.6.0 on macos 10.14, dunno
|
20
|
+
$CFLAGS << " -Wno-compound-token-split-by-macro"
|
21
|
+
end
|
22
|
+
|
23
|
+
create_makefile("bootsnap/bootsnap")
|
24
|
+
else
|
25
|
+
File.write("Makefile", dummy_makefile($srcdir).join)
|
26
|
+
end
|
data/lib/bootsnap/bundler.rb
CHANGED
@@ -37,7 +37,11 @@ module Bootsnap
|
|
37
37
|
|
38
38
|
def initialize(jobs)
|
39
39
|
@jobs = jobs
|
40
|
-
@pipe_out, @to_io = IO.pipe
|
40
|
+
@pipe_out, @to_io = IO.pipe(binmode: true)
|
41
|
+
# Set the writer encoding to binary since IO.pipe only sets it for the reader.
|
42
|
+
# https://github.com/rails/rails/issues/16514#issuecomment-52313290
|
43
|
+
@to_io.set_encoding(Encoding::BINARY)
|
44
|
+
|
41
45
|
@pid = nil
|
42
46
|
end
|
43
47
|
|
@@ -59,6 +63,7 @@ module Bootsnap
|
|
59
63
|
loop do
|
60
64
|
job, *args = Marshal.load(@pipe_out)
|
61
65
|
return if job == :exit
|
66
|
+
|
62
67
|
@jobs.fetch(job).call(*args)
|
63
68
|
end
|
64
69
|
rescue IOError
|