bootsnap 1.4.0 → 1.9.1
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 +126 -0
- data/README.md +68 -13
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +285 -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 +24 -7
- data/lib/bootsnap/compile_cache/json.rb +79 -0
- data/lib/bootsnap/compile_cache/yaml.rb +145 -39
- data/lib/bootsnap/compile_cache.rb +25 -3
- data/lib/bootsnap/explicit_require.rb +1 -0
- data/lib/bootsnap/load_path_cache/cache.rb +44 -9
- data/lib/bootsnap/load_path_cache/change_observer.rb +5 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +30 -6
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +11 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +43 -11
- data/lib/bootsnap/load_path_cache/path.rb +3 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +53 -27
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +28 -14
- data/lib/bootsnap/load_path_cache.rb +10 -16
- data/lib/bootsnap/setup.rb +2 -33
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +96 -17
- metadata +18 -29
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -24
- 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 -100
- data/shipit.rubygems.yml +0 -4
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. */
|
@@ -74,6 +84,8 @@ static uint32_t current_ruby_platform;
|
|
74
84
|
static uint32_t current_ruby_revision;
|
75
85
|
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
76
86
|
static uint32_t current_compile_option_crc32 = 0;
|
87
|
+
/* Current umask */
|
88
|
+
static mode_t current_umask;
|
77
89
|
|
78
90
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
79
91
|
static VALUE rb_mBootsnap;
|
@@ -81,31 +93,38 @@ static VALUE rb_mBootsnap_CompileCache;
|
|
81
93
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
82
94
|
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
83
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;
|
84
100
|
|
85
101
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
102
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
86
103
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
87
|
-
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);
|
88
106
|
|
89
107
|
/* Helpers */
|
90
|
-
static
|
91
|
-
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]);
|
92
109
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
93
110
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
94
|
-
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
95
|
-
static
|
96
|
-
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);
|
97
116
|
static uint32_t get_ruby_platform(void);
|
98
117
|
|
99
118
|
/*
|
100
119
|
* Helper functions to call ruby methods on handler object without crashing on
|
101
120
|
* exception.
|
102
121
|
*/
|
103
|
-
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);
|
104
123
|
static VALUE prot_storage_to_output(VALUE arg);
|
105
124
|
static VALUE prot_input_to_output(VALUE arg);
|
106
|
-
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);
|
107
126
|
static VALUE prot_input_to_storage(VALUE arg);
|
108
|
-
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);
|
109
128
|
struct s2o_data;
|
110
129
|
struct i2o_data;
|
111
130
|
struct i2s_data;
|
@@ -134,14 +153,33 @@ Init_bootsnap(void)
|
|
134
153
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
135
154
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
136
155
|
|
137
|
-
current_ruby_revision =
|
156
|
+
current_ruby_revision = get_ruby_revision();
|
138
157
|
current_ruby_platform = get_ruby_platform();
|
139
158
|
|
140
159
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
160
|
+
instrumentation_method = rb_intern("_instrument");
|
161
|
+
|
162
|
+
sym_miss = ID2SYM(rb_intern("miss"));
|
163
|
+
rb_global_variable(&sym_miss);
|
141
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);
|
142
169
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
143
|
-
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);
|
144
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
173
|
+
|
174
|
+
current_umask = umask(0777);
|
175
|
+
umask(current_umask);
|
176
|
+
}
|
177
|
+
|
178
|
+
static VALUE
|
179
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
180
|
+
{
|
181
|
+
instrumentation_enabled = RTEST(enabled);
|
182
|
+
return enabled;
|
145
183
|
}
|
146
184
|
|
147
185
|
/*
|
@@ -172,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
172
210
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
173
211
|
*/
|
174
212
|
static uint64_t
|
175
|
-
|
213
|
+
fnv1a_64_iter_cstr(uint64_t h, const char *str)
|
176
214
|
{
|
177
215
|
unsigned char *s = (unsigned char *)str;
|
178
216
|
|
@@ -185,12 +223,46 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
185
223
|
}
|
186
224
|
|
187
225
|
static uint64_t
|
188
|
-
|
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)
|
189
241
|
{
|
190
242
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
191
243
|
return fnv1a_64_iter(h, str);
|
192
244
|
}
|
193
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
|
+
|
194
266
|
/*
|
195
267
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
196
268
|
* (or OS version), we need to invalidate the cache.
|
@@ -206,16 +278,19 @@ get_ruby_platform(void)
|
|
206
278
|
VALUE ruby_platform;
|
207
279
|
|
208
280
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
209
|
-
hash = fnv1a_64(
|
281
|
+
hash = fnv1a_64(ruby_platform);
|
210
282
|
|
211
283
|
#ifdef _WIN32
|
212
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);
|
213
288
|
#else
|
214
289
|
struct utsname utsname;
|
215
290
|
|
216
291
|
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
217
292
|
if (uname(&utsname) >= 0) {
|
218
|
-
hash =
|
293
|
+
hash = fnv1a_64_iter_cstr(hash, utsname.version);
|
219
294
|
}
|
220
295
|
|
221
296
|
return (uint32_t)(hash >> 32);
|
@@ -230,14 +305,13 @@ get_ruby_platform(void)
|
|
230
305
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
231
306
|
*/
|
232
307
|
static void
|
233
|
-
bs_cache_path(const char * cachedir, const
|
308
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
234
309
|
{
|
235
310
|
uint64_t hash = fnv1a_64(path);
|
236
|
-
|
237
311
|
uint8_t first_byte = (hash >> (64 - 8));
|
238
312
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
239
313
|
|
240
|
-
sprintf(*cache_path, "%s/%
|
314
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
241
315
|
}
|
242
316
|
|
243
317
|
/*
|
@@ -267,7 +341,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
267
341
|
* conversions on the ruby VALUE arguments before passing them along.
|
268
342
|
*/
|
269
343
|
static VALUE
|
270
|
-
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)
|
271
345
|
{
|
272
346
|
FilePathValue(path_v);
|
273
347
|
|
@@ -282,27 +356,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
282
356
|
char * path = RSTRING_PTR(path_v);
|
283
357
|
char cache_path[MAX_CACHEPATH_SIZE];
|
284
358
|
|
285
|
-
|
286
|
-
|
287
|
-
bs_cache_path(cachedir, path, &tmp);
|
288
|
-
}
|
359
|
+
/* generate cache path to cache_path */
|
360
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
289
361
|
|
290
|
-
return bs_fetch(path, path_v, cache_path, handler);
|
362
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
291
363
|
}
|
292
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
|
+
}
|
293
391
|
/*
|
294
392
|
* Open the file we want to load/cache and generate a cache key for it if it
|
295
393
|
* was loaded.
|
296
394
|
*/
|
297
395
|
static int
|
298
|
-
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)
|
299
397
|
{
|
300
398
|
struct stat statbuf;
|
301
399
|
int fd;
|
302
400
|
|
303
401
|
fd = open(path, O_RDONLY);
|
304
402
|
if (fd < 0) {
|
305
|
-
*errno_provenance =
|
403
|
+
*errno_provenance = "bs_fetch:open_current_file:open";
|
306
404
|
return fd;
|
307
405
|
}
|
308
406
|
#ifdef _WIN32
|
@@ -310,7 +408,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
310
408
|
#endif
|
311
409
|
|
312
410
|
if (fstat(fd, &statbuf) < 0) {
|
313
|
-
*errno_provenance =
|
411
|
+
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
314
412
|
close(fd);
|
315
413
|
return -1;
|
316
414
|
}
|
@@ -326,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
326
424
|
}
|
327
425
|
|
328
426
|
#define ERROR_WITH_ERRNO -1
|
329
|
-
#define
|
427
|
+
#define CACHE_MISS -2
|
428
|
+
#define CACHE_STALE -3
|
330
429
|
|
331
430
|
/*
|
332
431
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -334,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
334
433
|
*
|
335
434
|
* Possible return values:
|
336
435
|
* - 0 (OK, key was loaded)
|
337
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
338
436
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
437
|
+
* - CACHE_MISS (-2)
|
438
|
+
* - CACHE_STALE (-3)
|
339
439
|
*/
|
340
440
|
static int
|
341
441
|
bs_read_key(int fd, struct bs_cache_key * key)
|
342
442
|
{
|
343
443
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
344
444
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
345
|
-
if (nread < KEY_SIZE) return
|
445
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
346
446
|
return 0;
|
347
447
|
}
|
348
448
|
|
@@ -352,19 +452,19 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
352
452
|
*
|
353
453
|
* Possible return values:
|
354
454
|
* - 0 (OK, key was loaded)
|
355
|
-
* -
|
455
|
+
* - CACHE_MISS (-2)
|
456
|
+
* - CACHE_STALE (-3)
|
356
457
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
357
458
|
*/
|
358
459
|
static int
|
359
|
-
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)
|
360
461
|
{
|
361
462
|
int fd, res;
|
362
463
|
|
363
464
|
fd = open(path, O_RDONLY);
|
364
465
|
if (fd < 0) {
|
365
|
-
*errno_provenance =
|
366
|
-
|
367
|
-
return ERROR_WITH_ERRNO;
|
466
|
+
*errno_provenance = "bs_fetch:open_cache_file:open";
|
467
|
+
return CACHE_MISS;
|
368
468
|
}
|
369
469
|
#ifdef _WIN32
|
370
470
|
setmode(fd, O_BINARY);
|
@@ -372,7 +472,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
372
472
|
|
373
473
|
res = bs_read_key(fd, key);
|
374
474
|
if (res < 0) {
|
375
|
-
*errno_provenance =
|
475
|
+
*errno_provenance = "bs_fetch:open_cache_file:read";
|
376
476
|
close(fd);
|
377
477
|
return res;
|
378
478
|
}
|
@@ -396,7 +496,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
396
496
|
* or exception, will be the final data returnable to the user.
|
397
497
|
*/
|
398
498
|
static int
|
399
|
-
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)
|
400
500
|
{
|
401
501
|
char * data = NULL;
|
402
502
|
ssize_t nread;
|
@@ -405,7 +505,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
405
505
|
VALUE storage_data;
|
406
506
|
|
407
507
|
if (data_size > 100000000000) {
|
408
|
-
*errno_provenance =
|
508
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
409
509
|
errno = EINVAL; /* because wtf? */
|
410
510
|
ret = -1;
|
411
511
|
goto done;
|
@@ -413,18 +513,18 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
413
513
|
data = ALLOC_N(char, data_size);
|
414
514
|
nread = read(fd, data, data_size);
|
415
515
|
if (nread < 0) {
|
416
|
-
*errno_provenance =
|
516
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
417
517
|
ret = -1;
|
418
518
|
goto done;
|
419
519
|
}
|
420
520
|
if (nread != data_size) {
|
421
|
-
ret =
|
521
|
+
ret = CACHE_STALE;
|
422
522
|
goto done;
|
423
523
|
}
|
424
524
|
|
425
|
-
storage_data =
|
525
|
+
storage_data = rb_str_new(data, data_size);
|
426
526
|
|
427
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
527
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
428
528
|
ret = 0;
|
429
529
|
done:
|
430
530
|
if (data != NULL) xfree(data);
|
@@ -465,30 +565,36 @@ mkpath(char * file_path, mode_t mode)
|
|
465
565
|
* path.
|
466
566
|
*/
|
467
567
|
static int
|
468
|
-
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)
|
469
569
|
{
|
470
570
|
char template[MAX_CACHEPATH_SIZE + 20];
|
471
|
-
char * dest;
|
472
571
|
char * tmp_path;
|
473
|
-
int fd, ret;
|
572
|
+
int fd, ret, attempt;
|
474
573
|
ssize_t nwrite;
|
475
574
|
|
476
|
-
|
477
|
-
|
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");
|
478
578
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
}
|
486
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
487
|
-
if (fd < 0) {
|
488
|
-
*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";
|
489
585
|
return -1;
|
490
586
|
}
|
491
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
|
+
|
492
598
|
#ifdef _WIN32
|
493
599
|
setmode(fd, O_BINARY);
|
494
600
|
#endif
|
@@ -496,11 +602,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
496
602
|
key->data_size = RSTRING_LEN(data);
|
497
603
|
nwrite = write(fd, key, KEY_SIZE);
|
498
604
|
if (nwrite < 0) {
|
499
|
-
*errno_provenance =
|
605
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
500
606
|
return -1;
|
501
607
|
}
|
502
608
|
if (nwrite != KEY_SIZE) {
|
503
|
-
*errno_provenance =
|
609
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
504
610
|
errno = EIO; /* Lies but whatever */
|
505
611
|
return -1;
|
506
612
|
}
|
@@ -508,7 +614,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
508
614
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
509
615
|
if (nwrite < 0) return -1;
|
510
616
|
if (nwrite != RSTRING_LEN(data)) {
|
511
|
-
*errno_provenance =
|
617
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
512
618
|
errno = EIO; /* Lies but whatever */
|
513
619
|
return -1;
|
514
620
|
}
|
@@ -516,7 +622,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
516
622
|
close(fd);
|
517
623
|
ret = rename(tmp_path, path);
|
518
624
|
if (ret < 0) {
|
519
|
-
*errno_provenance =
|
625
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
626
|
+
return -1;
|
627
|
+
}
|
628
|
+
ret = chmod(path, 0664 & ~current_umask);
|
629
|
+
if (ret < 0) {
|
630
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
520
631
|
}
|
521
632
|
return ret;
|
522
633
|
}
|
@@ -525,13 +636,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
525
636
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
526
637
|
* long, into a buffer */
|
527
638
|
static ssize_t
|
528
|
-
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)
|
529
640
|
{
|
530
641
|
ssize_t nread;
|
531
642
|
*contents = ALLOC_N(char, size);
|
532
643
|
nread = read(fd, *contents, size);
|
533
644
|
if (nread < 0) {
|
534
|
-
*errno_provenance =
|
645
|
+
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
535
646
|
}
|
536
647
|
return nread;
|
537
648
|
}
|
@@ -581,13 +692,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
|
|
581
692
|
* - Return storage_to_output(storage_data)
|
582
693
|
*/
|
583
694
|
static VALUE
|
584
|
-
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)
|
585
696
|
{
|
586
697
|
struct bs_cache_key cached_key, current_key;
|
587
698
|
char * contents = NULL;
|
588
699
|
int cache_fd = -1, current_fd = -1;
|
589
700
|
int res, valid_cache = 0, exception_tag = 0;
|
590
|
-
char * errno_provenance = NULL;
|
701
|
+
const char * errno_provenance = NULL;
|
591
702
|
|
592
703
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
593
704
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
@@ -601,26 +712,34 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
601
712
|
|
602
713
|
/* Open the cache key if it exists, and read its cache key in */
|
603
714
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
604
|
-
if (cache_fd ==
|
715
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
605
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
|
+
}
|
606
720
|
} else if (cache_fd < 0) {
|
607
721
|
goto fail_errno;
|
608
722
|
} else {
|
609
723
|
/* True if the cache existed and no invalidating changes have occurred since
|
610
724
|
* it was generated. */
|
611
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
|
+
}
|
612
731
|
}
|
613
732
|
|
614
733
|
if (valid_cache) {
|
615
734
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
616
735
|
res = fetch_cached_data(
|
617
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
736
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
618
737
|
&output_data, &exception_tag, &errno_provenance
|
619
738
|
);
|
620
|
-
if (exception_tag != 0)
|
621
|
-
else if (res ==
|
622
|
-
else if (res == ERROR_WITH_ERRNO)
|
623
|
-
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 */
|
624
743
|
}
|
625
744
|
close(cache_fd);
|
626
745
|
cache_fd = -1;
|
@@ -628,37 +747,39 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
628
747
|
|
629
748
|
/* Read the contents of the source file into a buffer */
|
630
749
|
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
631
|
-
input_data =
|
750
|
+
input_data = rb_str_new(contents, current_key.size);
|
632
751
|
|
633
752
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
634
|
-
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);
|
635
754
|
if (exception_tag != 0) goto raise;
|
636
755
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
637
756
|
* to cache anything; just return input_to_output(input_data) */
|
638
757
|
if (storage_data == uncompilable) {
|
639
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
758
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
640
759
|
if (exception_tag != 0) goto raise;
|
641
760
|
goto succeed;
|
642
761
|
}
|
643
762
|
/* If storage_data isn't a string, we can't cache it */
|
644
763
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
645
764
|
|
646
|
-
/*
|
647
|
-
|
648
|
-
|
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);
|
649
770
|
|
650
771
|
/* Having written the cache, now convert storage_data to output_data */
|
651
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
772
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
652
773
|
if (exception_tag != 0) goto raise;
|
653
774
|
|
654
775
|
/* If output_data is nil, delete the cache entry and generate the output
|
655
776
|
* using input_to_output */
|
656
777
|
if (NIL_P(output_data)) {
|
657
778
|
if (unlink(cache_path) < 0) {
|
658
|
-
errno_provenance =
|
779
|
+
errno_provenance = "bs_fetch:unlink";
|
659
780
|
goto fail_errno;
|
660
781
|
}
|
661
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
782
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
662
783
|
if (exception_tag != 0) goto raise;
|
663
784
|
}
|
664
785
|
|
@@ -689,6 +810,79 @@ invalid_type_storage_data:
|
|
689
810
|
#undef CLEANUP
|
690
811
|
}
|
691
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
|
+
|
692
886
|
/*****************************************************************************/
|
693
887
|
/********************* Handler Wrappers **************************************/
|
694
888
|
/*****************************************************************************
|
@@ -708,11 +902,13 @@ invalid_type_storage_data:
|
|
708
902
|
|
709
903
|
struct s2o_data {
|
710
904
|
VALUE handler;
|
905
|
+
VALUE args;
|
711
906
|
VALUE storage_data;
|
712
907
|
};
|
713
908
|
|
714
909
|
struct i2o_data {
|
715
910
|
VALUE handler;
|
911
|
+
VALUE args;
|
716
912
|
VALUE input_data;
|
717
913
|
};
|
718
914
|
|
@@ -726,15 +922,16 @@ static VALUE
|
|
726
922
|
prot_storage_to_output(VALUE arg)
|
727
923
|
{
|
728
924
|
struct s2o_data * data = (struct s2o_data *)arg;
|
729
|
-
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);
|
730
926
|
}
|
731
927
|
|
732
928
|
static int
|
733
|
-
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)
|
734
930
|
{
|
735
931
|
int state;
|
736
932
|
struct s2o_data s2o_data = {
|
737
933
|
.handler = handler,
|
934
|
+
.args = args,
|
738
935
|
.storage_data = storage_data,
|
739
936
|
};
|
740
937
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -742,10 +939,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
742
939
|
}
|
743
940
|
|
744
941
|
static void
|
745
|
-
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)
|
746
943
|
{
|
747
944
|
struct i2o_data i2o_data = {
|
748
945
|
.handler = handler,
|
946
|
+
.args = args,
|
749
947
|
.input_data = input_data,
|
750
948
|
};
|
751
949
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -755,7 +953,7 @@ static VALUE
|
|
755
953
|
prot_input_to_output(VALUE arg)
|
756
954
|
{
|
757
955
|
struct i2o_data * data = (struct i2o_data *)arg;
|
758
|
-
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);
|
759
957
|
}
|
760
958
|
|
761
959
|
static VALUE
|
@@ -766,7 +964,7 @@ try_input_to_storage(VALUE arg)
|
|
766
964
|
}
|
767
965
|
|
768
966
|
static VALUE
|
769
|
-
rescue_input_to_storage(VALUE arg)
|
967
|
+
rescue_input_to_storage(VALUE arg, VALUE e)
|
770
968
|
{
|
771
969
|
return uncompilable;
|
772
970
|
}
|
@@ -782,7 +980,7 @@ prot_input_to_storage(VALUE arg)
|
|
782
980
|
}
|
783
981
|
|
784
982
|
static int
|
785
|
-
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)
|
786
984
|
{
|
787
985
|
int state;
|
788
986
|
struct i2s_data i2s_data = {
|