bootsnap 1.4.1 → 1.10.3

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