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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/README.md +68 -13
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +285 -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 +24 -7
  11. data/lib/bootsnap/compile_cache/json.rb +79 -0
  12. data/lib/bootsnap/compile_cache/yaml.rb +145 -39
  13. data/lib/bootsnap/compile_cache.rb +25 -3
  14. data/lib/bootsnap/explicit_require.rb +1 -0
  15. data/lib/bootsnap/load_path_cache/cache.rb +44 -9
  16. data/lib/bootsnap/load_path_cache/change_observer.rb +5 -1
  17. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +30 -6
  18. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +11 -0
  19. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +43 -11
  20. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  21. data/lib/bootsnap/load_path_cache/path_scanner.rb +53 -27
  22. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  23. data/lib/bootsnap/load_path_cache/store.rb +28 -14
  24. data/lib/bootsnap/load_path_cache.rb +10 -16
  25. data/lib/bootsnap/setup.rb +2 -33
  26. data/lib/bootsnap/version.rb +2 -1
  27. data/lib/bootsnap.rb +96 -17
  28. metadata +18 -29
  29. data/.gitignore +0 -17
  30. data/.rubocop.yml +0 -20
  31. data/.travis.yml +0 -24
  32. data/CODE_OF_CONDUCT.md +0 -74
  33. data/CONTRIBUTING.md +0 -21
  34. data/Gemfile +0 -8
  35. data/README.jp.md +0 -231
  36. data/Rakefile +0 -12
  37. data/bin/ci +0 -10
  38. data/bin/console +0 -14
  39. data/bin/setup +0 -8
  40. data/bin/test-minimal-support +0 -7
  41. data/bin/testunit +0 -8
  42. data/bootsnap.gemspec +0 -45
  43. data/dev.yml +0 -10
  44. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -100
  45. data/shipit.rubygems.yml +0 -4
@@ -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. */
@@ -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 uint64_t fnv1a_64(const char *str);
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 int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
96
- 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);
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 = FIX2INT(rb_const_get(rb_cObject, rb_intern("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, 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);
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
- fnv1a_64_iter(uint64_t h, const char *str)
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
- 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)
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(RSTRING_PTR(ruby_platform));
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 = fnv1a_64_iter(hash, utsname.version);
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 char * path, char ** cache_path)
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/%02x/%014llx", cachedir, first_byte, remainder);
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
- { /* generate cache path to cache_path */
286
- char * tmp = (char *)&cache_path;
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 = (char *)"bs_fetch:open_current_file:open";
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 = (char *)"bs_fetch:open_current_file:fstat";
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 CACHE_MISSING_OR_INVALID -2
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 CACHE_MISSING_OR_INVALID;
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
- * - CACHE_MISSING_OR_INVALID (-2)
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 = (char *)"bs_fetch:open_cache_file:open";
366
- if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
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 = (char *)"bs_fetch:open_cache_file:read";
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 = (char *)"bs_fetch:fetch_cached_data:datasize";
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 = (char *)"bs_fetch:fetch_cached_data:read";
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 = CACHE_MISSING_OR_INVALID;
521
+ ret = CACHE_STALE;
422
522
  goto done;
423
523
  }
424
524
 
425
- storage_data = rb_str_new_static(data, data_size);
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
- dest = strncpy(template, path, MAX_CACHEPATH_SIZE);
477
- strcat(dest, ".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");
478
578
 
479
- tmp_path = mktemp(template);
480
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
481
- if (fd < 0) {
482
- if (mkpath(path, 0775) < 0) {
483
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
484
- return -1;
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 = (char *)"bs_fetch:atomic_write_cache_file:write";
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 = (char *)"bs_fetch:atomic_write_cache_file:keysize";
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 = (char *)"bs_fetch:atomic_write_cache_file:writelength";
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 = (char *)"bs_fetch:atomic_write_cache_file:rename";
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 = (char *)"bs_fetch:bs_read_contents:read";
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 == CACHE_MISSING_OR_INVALID) {
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(&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
+ }
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) goto raise;
621
- else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
622
- else if (res == ERROR_WITH_ERRNO) goto fail_errno;
623
- 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 */
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 = rb_str_new_static(contents, current_key.size);
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
- /* Write the cache key and storage_data to the cache directory */
647
- res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
648
- 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);
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 = (char *)"bs_fetch:unlink";
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, &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
+
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"), 1, data->storage_data);
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"), 1, data->input_data);
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 = {