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.
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 = {