bootsnap 1.4.4 → 1.9.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 +129 -0
- data/README.md +46 -15
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +276 -87
- data/ext/bootsnap/extconf.rb +20 -14
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +135 -0
- data/lib/bootsnap/cli.rb +281 -0
- data/lib/bootsnap/compile_cache/iseq.rb +51 -11
- data/lib/bootsnap/compile_cache/json.rb +79 -0
- data/lib/bootsnap/compile_cache/yaml.rb +141 -39
- data/lib/bootsnap/compile_cache.rb +14 -4
- data/lib/bootsnap/explicit_require.rb +1 -0
- data/lib/bootsnap/load_path_cache/cache.rb +47 -26
- data/lib/bootsnap/load_path_cache/change_observer.rb +4 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -20
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +51 -15
- data/lib/bootsnap/load_path_cache/path.rb +3 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +39 -15
- data/lib/bootsnap/load_path_cache.rb +3 -16
- data/lib/bootsnap/setup.rb +2 -36
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +106 -17
- metadata +18 -32
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/README.jp.md +0 -231
- data/Rakefile +0 -12
- data/bin/ci +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -45
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -106
- data/shipit.rubygems.yml +0 -0
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -14,6 +14,7 @@
|
|
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>
|
@@ -21,6 +22,9 @@
|
|
21
22
|
#ifndef _WIN32
|
22
23
|
#include <sys/utsname.h>
|
23
24
|
#endif
|
25
|
+
#ifdef __GLIBC__
|
26
|
+
#include <gnu/libc-version.h>
|
27
|
+
#endif
|
24
28
|
|
25
29
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
26
30
|
* 981 for the cache dir */
|
@@ -29,6 +33,12 @@
|
|
29
33
|
|
30
34
|
#define KEY_SIZE 64
|
31
35
|
|
36
|
+
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
37
|
+
|
38
|
+
#ifndef RB_UNLIKELY
|
39
|
+
#define RB_UNLIKELY(x) (x)
|
40
|
+
#endif
|
41
|
+
|
32
42
|
/*
|
33
43
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
34
44
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -65,7 +75,7 @@ struct bs_cache_key {
|
|
65
75
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
66
76
|
|
67
77
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
68
|
-
static const uint32_t current_version =
|
78
|
+
static const uint32_t current_version = 3;
|
69
79
|
|
70
80
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
71
81
|
* new OS ABI, etc. */
|
@@ -83,31 +93,38 @@ static VALUE rb_mBootsnap_CompileCache;
|
|
83
93
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
84
94
|
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
85
95
|
static ID uncompilable;
|
96
|
+
static ID instrumentation_method;
|
97
|
+
static VALUE sym_miss;
|
98
|
+
static VALUE sym_stale;
|
99
|
+
static bool instrumentation_enabled = false;
|
86
100
|
|
87
101
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
102
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
88
103
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
89
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
104
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
105
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
90
106
|
|
91
107
|
/* Helpers */
|
92
|
-
static
|
93
|
-
static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
|
108
|
+
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
94
109
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
95
110
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
96
|
-
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
97
|
-
static
|
98
|
-
static int
|
111
|
+
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
112
|
+
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
113
|
+
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
114
|
+
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);
|
115
|
+
static uint32_t get_ruby_revision(void);
|
99
116
|
static uint32_t get_ruby_platform(void);
|
100
117
|
|
101
118
|
/*
|
102
119
|
* Helper functions to call ruby methods on handler object without crashing on
|
103
120
|
* exception.
|
104
121
|
*/
|
105
|
-
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
122
|
+
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
106
123
|
static VALUE prot_storage_to_output(VALUE arg);
|
107
124
|
static VALUE prot_input_to_output(VALUE arg);
|
108
|
-
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
125
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
109
126
|
static VALUE prot_input_to_storage(VALUE arg);
|
110
|
-
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
127
|
+
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
111
128
|
struct s2o_data;
|
112
129
|
struct i2o_data;
|
113
130
|
struct i2s_data;
|
@@ -136,19 +153,35 @@ Init_bootsnap(void)
|
|
136
153
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
137
154
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
138
155
|
|
139
|
-
current_ruby_revision =
|
156
|
+
current_ruby_revision = get_ruby_revision();
|
140
157
|
current_ruby_platform = get_ruby_platform();
|
141
158
|
|
142
159
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
160
|
+
instrumentation_method = rb_intern("_instrument");
|
143
161
|
|
162
|
+
sym_miss = ID2SYM(rb_intern("miss"));
|
163
|
+
rb_global_variable(&sym_miss);
|
164
|
+
|
165
|
+
sym_stale = ID2SYM(rb_intern("stale"));
|
166
|
+
rb_global_variable(&sym_stale);
|
167
|
+
|
168
|
+
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
144
169
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
145
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
170
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
171
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
146
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
147
173
|
|
148
174
|
current_umask = umask(0777);
|
149
175
|
umask(current_umask);
|
150
176
|
}
|
151
177
|
|
178
|
+
static VALUE
|
179
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
180
|
+
{
|
181
|
+
instrumentation_enabled = RTEST(enabled);
|
182
|
+
return enabled;
|
183
|
+
}
|
184
|
+
|
152
185
|
/*
|
153
186
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
154
187
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -177,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
177
210
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
178
211
|
*/
|
179
212
|
static uint64_t
|
180
|
-
|
213
|
+
fnv1a_64_iter_cstr(uint64_t h, const char *str)
|
181
214
|
{
|
182
215
|
unsigned char *s = (unsigned char *)str;
|
183
216
|
|
@@ -190,12 +223,46 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
190
223
|
}
|
191
224
|
|
192
225
|
static uint64_t
|
193
|
-
|
226
|
+
fnv1a_64_iter(uint64_t h, const VALUE str)
|
227
|
+
{
|
228
|
+
unsigned char *s = (unsigned char *)RSTRING_PTR(str);
|
229
|
+
unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
|
230
|
+
|
231
|
+
while (s < str_end) {
|
232
|
+
h ^= (uint64_t)*s++;
|
233
|
+
h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
|
234
|
+
}
|
235
|
+
|
236
|
+
return h;
|
237
|
+
}
|
238
|
+
|
239
|
+
static uint64_t
|
240
|
+
fnv1a_64(const VALUE str)
|
194
241
|
{
|
195
242
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
196
243
|
return fnv1a_64_iter(h, str);
|
197
244
|
}
|
198
245
|
|
246
|
+
/*
|
247
|
+
* Ruby's revision may be Integer or String. CRuby 2.7 or later uses
|
248
|
+
* Git commit ID as revision. It's String.
|
249
|
+
*/
|
250
|
+
static uint32_t
|
251
|
+
get_ruby_revision(void)
|
252
|
+
{
|
253
|
+
VALUE ruby_revision;
|
254
|
+
|
255
|
+
ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
|
256
|
+
if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
|
257
|
+
return FIX2INT(ruby_revision);
|
258
|
+
} else {
|
259
|
+
uint64_t hash;
|
260
|
+
|
261
|
+
hash = fnv1a_64(ruby_revision);
|
262
|
+
return (uint32_t)(hash >> 32);
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
199
266
|
/*
|
200
267
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
201
268
|
* (or OS version), we need to invalidate the cache.
|
@@ -211,16 +278,19 @@ get_ruby_platform(void)
|
|
211
278
|
VALUE ruby_platform;
|
212
279
|
|
213
280
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
214
|
-
hash = fnv1a_64(
|
281
|
+
hash = fnv1a_64(ruby_platform);
|
215
282
|
|
216
283
|
#ifdef _WIN32
|
217
284
|
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
285
|
+
#elif defined(__GLIBC__)
|
286
|
+
hash = fnv1a_64_iter_cstr(hash, gnu_get_libc_version());
|
287
|
+
return (uint32_t)(hash >> 32);
|
218
288
|
#else
|
219
289
|
struct utsname utsname;
|
220
290
|
|
221
291
|
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
222
292
|
if (uname(&utsname) >= 0) {
|
223
|
-
hash =
|
293
|
+
hash = fnv1a_64_iter_cstr(hash, utsname.version);
|
224
294
|
}
|
225
295
|
|
226
296
|
return (uint32_t)(hash >> 32);
|
@@ -235,14 +305,13 @@ get_ruby_platform(void)
|
|
235
305
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
236
306
|
*/
|
237
307
|
static void
|
238
|
-
bs_cache_path(const char * cachedir, const
|
308
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
239
309
|
{
|
240
310
|
uint64_t hash = fnv1a_64(path);
|
241
|
-
|
242
311
|
uint8_t first_byte = (hash >> (64 - 8));
|
243
312
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
244
313
|
|
245
|
-
sprintf(*cache_path, "%s/%
|
314
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
246
315
|
}
|
247
316
|
|
248
317
|
/*
|
@@ -272,7 +341,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
272
341
|
* conversions on the ruby VALUE arguments before passing them along.
|
273
342
|
*/
|
274
343
|
static VALUE
|
275
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
344
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
276
345
|
{
|
277
346
|
FilePathValue(path_v);
|
278
347
|
|
@@ -287,27 +356,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
287
356
|
char * path = RSTRING_PTR(path_v);
|
288
357
|
char cache_path[MAX_CACHEPATH_SIZE];
|
289
358
|
|
290
|
-
|
291
|
-
|
292
|
-
bs_cache_path(cachedir, path, &tmp);
|
293
|
-
}
|
359
|
+
/* generate cache path to cache_path */
|
360
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
294
361
|
|
295
|
-
return bs_fetch(path, path_v, cache_path, handler);
|
362
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
296
363
|
}
|
297
364
|
|
365
|
+
/*
|
366
|
+
* Entrypoint for Bootsnap::CompileCache::Native.precompile.
|
367
|
+
* Similar to fetch, but it only generate the cache if missing
|
368
|
+
* and doesn't return the content.
|
369
|
+
*/
|
370
|
+
static VALUE
|
371
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
372
|
+
{
|
373
|
+
FilePathValue(path_v);
|
374
|
+
|
375
|
+
Check_Type(cachedir_v, T_STRING);
|
376
|
+
Check_Type(path_v, T_STRING);
|
377
|
+
|
378
|
+
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
379
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
380
|
+
}
|
381
|
+
|
382
|
+
char * cachedir = RSTRING_PTR(cachedir_v);
|
383
|
+
char * path = RSTRING_PTR(path_v);
|
384
|
+
char cache_path[MAX_CACHEPATH_SIZE];
|
385
|
+
|
386
|
+
/* generate cache path to cache_path */
|
387
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
388
|
+
|
389
|
+
return bs_precompile(path, path_v, cache_path, handler);
|
390
|
+
}
|
298
391
|
/*
|
299
392
|
* Open the file we want to load/cache and generate a cache key for it if it
|
300
393
|
* was loaded.
|
301
394
|
*/
|
302
395
|
static int
|
303
|
-
open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
|
396
|
+
open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
304
397
|
{
|
305
398
|
struct stat statbuf;
|
306
399
|
int fd;
|
307
400
|
|
308
401
|
fd = open(path, O_RDONLY);
|
309
402
|
if (fd < 0) {
|
310
|
-
*errno_provenance =
|
403
|
+
*errno_provenance = "bs_fetch:open_current_file:open";
|
311
404
|
return fd;
|
312
405
|
}
|
313
406
|
#ifdef _WIN32
|
@@ -315,7 +408,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
315
408
|
#endif
|
316
409
|
|
317
410
|
if (fstat(fd, &statbuf) < 0) {
|
318
|
-
*errno_provenance =
|
411
|
+
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
319
412
|
close(fd);
|
320
413
|
return -1;
|
321
414
|
}
|
@@ -331,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
331
424
|
}
|
332
425
|
|
333
426
|
#define ERROR_WITH_ERRNO -1
|
334
|
-
#define
|
427
|
+
#define CACHE_MISS -2
|
428
|
+
#define CACHE_STALE -3
|
335
429
|
|
336
430
|
/*
|
337
431
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -339,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
339
433
|
*
|
340
434
|
* Possible return values:
|
341
435
|
* - 0 (OK, key was loaded)
|
342
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
343
436
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
437
|
+
* - CACHE_MISS (-2)
|
438
|
+
* - CACHE_STALE (-3)
|
344
439
|
*/
|
345
440
|
static int
|
346
441
|
bs_read_key(int fd, struct bs_cache_key * key)
|
347
442
|
{
|
348
443
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
349
444
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
350
|
-
if (nread < KEY_SIZE) return
|
445
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
351
446
|
return 0;
|
352
447
|
}
|
353
448
|
|
@@ -357,19 +452,19 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
357
452
|
*
|
358
453
|
* Possible return values:
|
359
454
|
* - 0 (OK, key was loaded)
|
360
|
-
* -
|
455
|
+
* - CACHE_MISS (-2)
|
456
|
+
* - CACHE_STALE (-3)
|
361
457
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
362
458
|
*/
|
363
459
|
static int
|
364
|
-
open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
|
460
|
+
open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
365
461
|
{
|
366
462
|
int fd, res;
|
367
463
|
|
368
464
|
fd = open(path, O_RDONLY);
|
369
465
|
if (fd < 0) {
|
370
|
-
*errno_provenance =
|
371
|
-
|
372
|
-
return ERROR_WITH_ERRNO;
|
466
|
+
*errno_provenance = "bs_fetch:open_cache_file:open";
|
467
|
+
return CACHE_MISS;
|
373
468
|
}
|
374
469
|
#ifdef _WIN32
|
375
470
|
setmode(fd, O_BINARY);
|
@@ -377,7 +472,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
377
472
|
|
378
473
|
res = bs_read_key(fd, key);
|
379
474
|
if (res < 0) {
|
380
|
-
*errno_provenance =
|
475
|
+
*errno_provenance = "bs_fetch:open_cache_file:read";
|
381
476
|
close(fd);
|
382
477
|
return res;
|
383
478
|
}
|
@@ -401,7 +496,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
401
496
|
* or exception, will be the final data returnable to the user.
|
402
497
|
*/
|
403
498
|
static int
|
404
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
|
499
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
405
500
|
{
|
406
501
|
char * data = NULL;
|
407
502
|
ssize_t nread;
|
@@ -410,7 +505,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
410
505
|
VALUE storage_data;
|
411
506
|
|
412
507
|
if (data_size > 100000000000) {
|
413
|
-
*errno_provenance =
|
508
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
414
509
|
errno = EINVAL; /* because wtf? */
|
415
510
|
ret = -1;
|
416
511
|
goto done;
|
@@ -418,18 +513,18 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
418
513
|
data = ALLOC_N(char, data_size);
|
419
514
|
nread = read(fd, data, data_size);
|
420
515
|
if (nread < 0) {
|
421
|
-
*errno_provenance =
|
516
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
422
517
|
ret = -1;
|
423
518
|
goto done;
|
424
519
|
}
|
425
520
|
if (nread != data_size) {
|
426
|
-
ret =
|
521
|
+
ret = CACHE_STALE;
|
427
522
|
goto done;
|
428
523
|
}
|
429
524
|
|
430
|
-
storage_data =
|
525
|
+
storage_data = rb_str_new(data, data_size);
|
431
526
|
|
432
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
527
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
433
528
|
ret = 0;
|
434
529
|
done:
|
435
530
|
if (data != NULL) xfree(data);
|
@@ -470,29 +565,36 @@ mkpath(char * file_path, mode_t mode)
|
|
470
565
|
* path.
|
471
566
|
*/
|
472
567
|
static int
|
473
|
-
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
568
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
|
474
569
|
{
|
475
570
|
char template[MAX_CACHEPATH_SIZE + 20];
|
476
571
|
char * tmp_path;
|
477
|
-
int fd, ret;
|
572
|
+
int fd, ret, attempt;
|
478
573
|
ssize_t nwrite;
|
479
574
|
|
480
|
-
|
481
|
-
|
575
|
+
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
576
|
+
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
577
|
+
strcat(tmp_path, ".tmp.XXXXXX");
|
482
578
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
}
|
490
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
491
|
-
if (fd < 0) {
|
492
|
-
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
|
579
|
+
// mkstemp modifies the template to be the actual created path
|
580
|
+
fd = mkstemp(tmp_path);
|
581
|
+
if (fd > 0) break;
|
582
|
+
|
583
|
+
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
584
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
493
585
|
return -1;
|
494
586
|
}
|
495
587
|
}
|
588
|
+
if (fd < 0) {
|
589
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
|
590
|
+
return -1;
|
591
|
+
}
|
592
|
+
|
593
|
+
if (chmod(tmp_path, 0644) < 0) {
|
594
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
595
|
+
return -1;
|
596
|
+
}
|
597
|
+
|
496
598
|
#ifdef _WIN32
|
497
599
|
setmode(fd, O_BINARY);
|
498
600
|
#endif
|
@@ -500,11 +602,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
500
602
|
key->data_size = RSTRING_LEN(data);
|
501
603
|
nwrite = write(fd, key, KEY_SIZE);
|
502
604
|
if (nwrite < 0) {
|
503
|
-
*errno_provenance =
|
605
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
504
606
|
return -1;
|
505
607
|
}
|
506
608
|
if (nwrite != KEY_SIZE) {
|
507
|
-
*errno_provenance =
|
609
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
508
610
|
errno = EIO; /* Lies but whatever */
|
509
611
|
return -1;
|
510
612
|
}
|
@@ -512,7 +614,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
512
614
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
513
615
|
if (nwrite < 0) return -1;
|
514
616
|
if (nwrite != RSTRING_LEN(data)) {
|
515
|
-
*errno_provenance =
|
617
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
516
618
|
errno = EIO; /* Lies but whatever */
|
517
619
|
return -1;
|
518
620
|
}
|
@@ -520,12 +622,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
520
622
|
close(fd);
|
521
623
|
ret = rename(tmp_path, path);
|
522
624
|
if (ret < 0) {
|
523
|
-
*errno_provenance =
|
625
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
524
626
|
return -1;
|
525
627
|
}
|
526
628
|
ret = chmod(path, 0664 & ~current_umask);
|
527
629
|
if (ret < 0) {
|
528
|
-
*errno_provenance =
|
630
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
529
631
|
}
|
530
632
|
return ret;
|
531
633
|
}
|
@@ -534,13 +636,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
534
636
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
535
637
|
* long, into a buffer */
|
536
638
|
static ssize_t
|
537
|
-
bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
|
639
|
+
bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
|
538
640
|
{
|
539
641
|
ssize_t nread;
|
540
642
|
*contents = ALLOC_N(char, size);
|
541
643
|
nread = read(fd, *contents, size);
|
542
644
|
if (nread < 0) {
|
543
|
-
*errno_provenance =
|
645
|
+
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
544
646
|
}
|
545
647
|
return nread;
|
546
648
|
}
|
@@ -590,13 +692,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
|
|
590
692
|
* - Return storage_to_output(storage_data)
|
591
693
|
*/
|
592
694
|
static VALUE
|
593
|
-
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
695
|
+
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
594
696
|
{
|
595
697
|
struct bs_cache_key cached_key, current_key;
|
596
698
|
char * contents = NULL;
|
597
699
|
int cache_fd = -1, current_fd = -1;
|
598
700
|
int res, valid_cache = 0, exception_tag = 0;
|
599
|
-
char * errno_provenance = NULL;
|
701
|
+
const char * errno_provenance = NULL;
|
600
702
|
|
601
703
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
602
704
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
@@ -610,26 +712,34 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
610
712
|
|
611
713
|
/* Open the cache key if it exists, and read its cache key in */
|
612
714
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
613
|
-
if (cache_fd ==
|
715
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
614
716
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
717
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
718
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
719
|
+
}
|
615
720
|
} else if (cache_fd < 0) {
|
616
721
|
goto fail_errno;
|
617
722
|
} else {
|
618
723
|
/* True if the cache existed and no invalidating changes have occurred since
|
619
724
|
* it was generated. */
|
620
725
|
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
726
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
727
|
+
if (!valid_cache) {
|
728
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
|
729
|
+
}
|
730
|
+
}
|
621
731
|
}
|
622
732
|
|
623
733
|
if (valid_cache) {
|
624
734
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
625
735
|
res = fetch_cached_data(
|
626
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
736
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
627
737
|
&output_data, &exception_tag, &errno_provenance
|
628
738
|
);
|
629
|
-
if (exception_tag != 0)
|
630
|
-
else if (res ==
|
631
|
-
else if (res == ERROR_WITH_ERRNO)
|
632
|
-
else if (!NIL_P(output_data))
|
739
|
+
if (exception_tag != 0) goto raise;
|
740
|
+
else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
741
|
+
else if (res == ERROR_WITH_ERRNO) goto fail_errno;
|
742
|
+
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
633
743
|
}
|
634
744
|
close(cache_fd);
|
635
745
|
cache_fd = -1;
|
@@ -637,37 +747,39 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
637
747
|
|
638
748
|
/* Read the contents of the source file into a buffer */
|
639
749
|
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
640
|
-
input_data =
|
750
|
+
input_data = rb_str_new(contents, current_key.size);
|
641
751
|
|
642
752
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
643
|
-
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
753
|
+
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
644
754
|
if (exception_tag != 0) goto raise;
|
645
755
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
646
756
|
* to cache anything; just return input_to_output(input_data) */
|
647
757
|
if (storage_data == uncompilable) {
|
648
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
758
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
649
759
|
if (exception_tag != 0) goto raise;
|
650
760
|
goto succeed;
|
651
761
|
}
|
652
762
|
/* If storage_data isn't a string, we can't cache it */
|
653
763
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
654
764
|
|
655
|
-
/*
|
656
|
-
|
657
|
-
|
765
|
+
/* Attempt to write the cache key and storage_data to the cache directory.
|
766
|
+
* We do however ignore any failures to persist the cache, as it's better
|
767
|
+
* to move along, than to interrupt the process.
|
768
|
+
*/
|
769
|
+
atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
658
770
|
|
659
771
|
/* Having written the cache, now convert storage_data to output_data */
|
660
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
772
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
661
773
|
if (exception_tag != 0) goto raise;
|
662
774
|
|
663
775
|
/* If output_data is nil, delete the cache entry and generate the output
|
664
776
|
* using input_to_output */
|
665
777
|
if (NIL_P(output_data)) {
|
666
778
|
if (unlink(cache_path) < 0) {
|
667
|
-
errno_provenance =
|
779
|
+
errno_provenance = "bs_fetch:unlink";
|
668
780
|
goto fail_errno;
|
669
781
|
}
|
670
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
782
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
671
783
|
if (exception_tag != 0) goto raise;
|
672
784
|
}
|
673
785
|
|
@@ -698,6 +810,79 @@ invalid_type_storage_data:
|
|
698
810
|
#undef CLEANUP
|
699
811
|
}
|
700
812
|
|
813
|
+
static VALUE
|
814
|
+
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
815
|
+
{
|
816
|
+
struct bs_cache_key cached_key, current_key;
|
817
|
+
char * contents = NULL;
|
818
|
+
int cache_fd = -1, current_fd = -1;
|
819
|
+
int res, valid_cache = 0, exception_tag = 0;
|
820
|
+
const char * errno_provenance = NULL;
|
821
|
+
|
822
|
+
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
823
|
+
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
824
|
+
|
825
|
+
/* Open the source file and generate a cache key for it */
|
826
|
+
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
827
|
+
if (current_fd < 0) goto fail;
|
828
|
+
|
829
|
+
/* Open the cache key if it exists, and read its cache key in */
|
830
|
+
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
831
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
832
|
+
/* This is ok: valid_cache remains false, we re-populate it. */
|
833
|
+
} else if (cache_fd < 0) {
|
834
|
+
goto fail;
|
835
|
+
} else {
|
836
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
837
|
+
* it was generated. */
|
838
|
+
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
839
|
+
}
|
840
|
+
|
841
|
+
if (valid_cache) {
|
842
|
+
goto succeed;
|
843
|
+
}
|
844
|
+
|
845
|
+
close(cache_fd);
|
846
|
+
cache_fd = -1;
|
847
|
+
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
848
|
+
|
849
|
+
/* Read the contents of the source file into a buffer */
|
850
|
+
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
|
851
|
+
input_data = rb_str_new(contents, current_key.size);
|
852
|
+
|
853
|
+
/* Try to compile the input_data using input_to_storage(input_data) */
|
854
|
+
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
855
|
+
if (exception_tag != 0) goto fail;
|
856
|
+
|
857
|
+
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
858
|
+
* to cache anything; just return false */
|
859
|
+
if (storage_data == uncompilable) {
|
860
|
+
goto fail;
|
861
|
+
}
|
862
|
+
/* If storage_data isn't a string, we can't cache it */
|
863
|
+
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
864
|
+
|
865
|
+
/* Write the cache key and storage_data to the cache directory */
|
866
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
867
|
+
if (res < 0) goto fail;
|
868
|
+
|
869
|
+
goto succeed;
|
870
|
+
|
871
|
+
#define CLEANUP \
|
872
|
+
if (contents != NULL) xfree(contents); \
|
873
|
+
if (current_fd >= 0) close(current_fd); \
|
874
|
+
if (cache_fd >= 0) close(cache_fd);
|
875
|
+
|
876
|
+
succeed:
|
877
|
+
CLEANUP;
|
878
|
+
return Qtrue;
|
879
|
+
fail:
|
880
|
+
CLEANUP;
|
881
|
+
return Qfalse;
|
882
|
+
#undef CLEANUP
|
883
|
+
}
|
884
|
+
|
885
|
+
|
701
886
|
/*****************************************************************************/
|
702
887
|
/********************* Handler Wrappers **************************************/
|
703
888
|
/*****************************************************************************
|
@@ -717,11 +902,13 @@ invalid_type_storage_data:
|
|
717
902
|
|
718
903
|
struct s2o_data {
|
719
904
|
VALUE handler;
|
905
|
+
VALUE args;
|
720
906
|
VALUE storage_data;
|
721
907
|
};
|
722
908
|
|
723
909
|
struct i2o_data {
|
724
910
|
VALUE handler;
|
911
|
+
VALUE args;
|
725
912
|
VALUE input_data;
|
726
913
|
};
|
727
914
|
|
@@ -735,15 +922,16 @@ static VALUE
|
|
735
922
|
prot_storage_to_output(VALUE arg)
|
736
923
|
{
|
737
924
|
struct s2o_data * data = (struct s2o_data *)arg;
|
738
|
-
return rb_funcall(data->handler, rb_intern("storage_to_output"),
|
925
|
+
return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
|
739
926
|
}
|
740
927
|
|
741
928
|
static int
|
742
|
-
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
929
|
+
bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
|
743
930
|
{
|
744
931
|
int state;
|
745
932
|
struct s2o_data s2o_data = {
|
746
933
|
.handler = handler,
|
934
|
+
.args = args,
|
747
935
|
.storage_data = storage_data,
|
748
936
|
};
|
749
937
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -751,10 +939,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
751
939
|
}
|
752
940
|
|
753
941
|
static void
|
754
|
-
bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
|
942
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
755
943
|
{
|
756
944
|
struct i2o_data i2o_data = {
|
757
945
|
.handler = handler,
|
946
|
+
.args = args,
|
758
947
|
.input_data = input_data,
|
759
948
|
};
|
760
949
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -764,7 +953,7 @@ static VALUE
|
|
764
953
|
prot_input_to_output(VALUE arg)
|
765
954
|
{
|
766
955
|
struct i2o_data * data = (struct i2o_data *)arg;
|
767
|
-
return rb_funcall(data->handler, rb_intern("input_to_output"),
|
956
|
+
return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
|
768
957
|
}
|
769
958
|
|
770
959
|
static VALUE
|
@@ -775,7 +964,7 @@ try_input_to_storage(VALUE arg)
|
|
775
964
|
}
|
776
965
|
|
777
966
|
static VALUE
|
778
|
-
rescue_input_to_storage(VALUE arg)
|
967
|
+
rescue_input_to_storage(VALUE arg, VALUE e)
|
779
968
|
{
|
780
969
|
return uncompilable;
|
781
970
|
}
|
@@ -791,7 +980,7 @@ prot_input_to_storage(VALUE arg)
|
|
791
980
|
}
|
792
981
|
|
793
982
|
static int
|
794
|
-
bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
983
|
+
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
795
984
|
{
|
796
985
|
int state;
|
797
986
|
struct i2s_data i2s_data = {
|