bootsnap 1.6.0 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|