bootsnap 1.4.4 → 1.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +129 -0
  3. data/README.md +46 -15
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +276 -87
  6. data/ext/bootsnap/extconf.rb +20 -14
  7. data/lib/bootsnap/bundler.rb +1 -0
  8. data/lib/bootsnap/cli/worker_pool.rb +135 -0
  9. data/lib/bootsnap/cli.rb +281 -0
  10. data/lib/bootsnap/compile_cache/iseq.rb +51 -11
  11. data/lib/bootsnap/compile_cache/json.rb +79 -0
  12. data/lib/bootsnap/compile_cache/yaml.rb +141 -39
  13. data/lib/bootsnap/compile_cache.rb +14 -4
  14. data/lib/bootsnap/explicit_require.rb +1 -0
  15. data/lib/bootsnap/load_path_cache/cache.rb +47 -26
  16. data/lib/bootsnap/load_path_cache/change_observer.rb +4 -1
  17. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -20
  18. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
  19. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +51 -15
  20. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  21. data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -26
  22. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  23. data/lib/bootsnap/load_path_cache/store.rb +39 -15
  24. data/lib/bootsnap/load_path_cache.rb +3 -16
  25. data/lib/bootsnap/setup.rb +2 -36
  26. data/lib/bootsnap/version.rb +2 -1
  27. data/lib/bootsnap.rb +106 -17
  28. metadata +18 -32
  29. data/.github/CODEOWNERS +0 -2
  30. data/.github/probots.yml +0 -2
  31. data/.gitignore +0 -17
  32. data/.rubocop.yml +0 -20
  33. data/.travis.yml +0 -21
  34. data/CODE_OF_CONDUCT.md +0 -74
  35. data/CONTRIBUTING.md +0 -21
  36. data/Gemfile +0 -8
  37. data/README.jp.md +0 -231
  38. data/Rakefile +0 -12
  39. data/bin/ci +0 -10
  40. data/bin/console +0 -14
  41. data/bin/setup +0 -8
  42. data/bin/test-minimal-support +0 -7
  43. data/bin/testunit +0 -8
  44. data/bootsnap.gemspec +0 -45
  45. data/dev.yml +0 -10
  46. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -106
  47. data/shipit.rubygems.yml +0 -0
@@ -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 = 2;
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 uint64_t fnv1a_64(const char *str);
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 int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
98
- static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
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 = FIX2INT(rb_const_get(rb_cObject, rb_intern("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, 3);
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
- fnv1a_64_iter(uint64_t h, const char *str)
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
- fnv1a_64(const char *str)
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(RSTRING_PTR(ruby_platform));
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 = fnv1a_64_iter(hash, utsname.version);
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 char * path, char ** cache_path)
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/%02x/%014llx", cachedir, first_byte, remainder);
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
- { /* generate cache path to cache_path */
291
- char * tmp = (char *)&cache_path;
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 = (char *)"bs_fetch:open_current_file:open";
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 = (char *)"bs_fetch:open_current_file:fstat";
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 CACHE_MISSING_OR_INVALID -2
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 CACHE_MISSING_OR_INVALID;
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
- * - CACHE_MISSING_OR_INVALID (-2)
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 = (char *)"bs_fetch:open_cache_file:open";
371
- if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
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 = (char *)"bs_fetch:open_cache_file:read";
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 = (char *)"bs_fetch:fetch_cached_data:datasize";
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 = (char *)"bs_fetch:fetch_cached_data:read";
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 = CACHE_MISSING_OR_INVALID;
521
+ ret = CACHE_STALE;
427
522
  goto done;
428
523
  }
429
524
 
430
- storage_data = rb_str_new_static(data, data_size);
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
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
481
- strcat(tmp_path, ".tmp.XXXXXX");
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
- // mkstemp modifies the template to be the actual created path
484
- fd = mkstemp(tmp_path);
485
- if (fd < 0) {
486
- if (mkpath(tmp_path, 0775) < 0) {
487
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
488
- return -1;
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 = (char *)"bs_fetch:atomic_write_cache_file:write";
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 = (char *)"bs_fetch:atomic_write_cache_file:keysize";
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 = (char *)"bs_fetch:atomic_write_cache_file:writelength";
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 = (char *)"bs_fetch:atomic_write_cache_file:rename";
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 = (char *)"bs_fetch:atomic_write_cache_file:chmod";
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 = (char *)"bs_fetch:bs_read_contents:read";
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 == CACHE_MISSING_OR_INVALID) {
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(&current_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) goto raise;
630
- else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
631
- else if (res == ERROR_WITH_ERRNO) goto fail_errno;
632
- else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
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 = rb_str_new_static(contents, current_key.size);
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
- /* Write the cache key and storage_data to the cache directory */
656
- res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
657
- if (res < 0) goto fail_errno;
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, &current_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 = (char *)"bs_fetch:unlink";
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, &current_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(&current_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, &current_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"), 1, data->storage_data);
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"), 1, data->input_data);
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 = {