bootsnap 1.4.5 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +227 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +57 -20
  5. data/exe/bootsnap +5 -0
  6. data/ext/bootsnap/bootsnap.c +301 -155
  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 +64 -19
  12. data/lib/bootsnap/compile_cache/json.rb +93 -0
  13. data/lib/bootsnap/compile_cache/yaml.rb +333 -42
  14. data/lib/bootsnap/compile_cache.rb +26 -8
  15. data/lib/bootsnap/explicit_require.rb +5 -3
  16. data/lib/bootsnap/load_path_cache/cache.rb +65 -37
  17. data/lib/bootsnap/load_path_cache/change_observer.rb +19 -3
  18. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +28 -83
  19. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +2 -0
  20. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +63 -29
  21. data/lib/bootsnap/load_path_cache/path.rb +42 -19
  22. data/lib/bootsnap/load_path_cache/path_scanner.rb +60 -29
  23. data/lib/bootsnap/load_path_cache/store.rb +64 -23
  24. data/lib/bootsnap/load_path_cache.rb +31 -38
  25. data/lib/bootsnap/setup.rb +3 -36
  26. data/lib/bootsnap/version.rb +3 -1
  27. data/lib/bootsnap.rb +127 -36
  28. metadata +15 -99
  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/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
  48. data/shipit.rubygems.yml +0 -0
@@ -14,13 +14,11 @@
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>
20
21
  #include <sys/stat.h>
21
- #ifndef _WIN32
22
- #include <sys/utsname.h>
23
- #endif
24
22
 
25
23
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
26
24
  * 981 for the cache dir */
@@ -29,6 +27,12 @@
29
27
 
30
28
  #define KEY_SIZE 64
31
29
 
30
+ #define MAX_CREATE_TEMPFILE_ATTEMPT 3
31
+
32
+ #ifndef RB_UNLIKELY
33
+ #define RB_UNLIKELY(x) (x)
34
+ #endif
35
+
32
36
  /*
33
37
  * An instance of this key is written as the first 64 bytes of each cache file.
34
38
  * The mtime and size members track whether the file contents have changed, and
@@ -65,7 +69,7 @@ struct bs_cache_key {
65
69
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
66
70
 
67
71
  /* Effectively a schema version. Bumping invalidates all previous caches */
68
- static const uint32_t current_version = 2;
72
+ static const uint32_t current_version = 4;
69
73
 
70
74
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
71
75
  * new OS ABI, etc. */
@@ -81,21 +85,28 @@ static mode_t current_umask;
81
85
  static VALUE rb_mBootsnap;
82
86
  static VALUE rb_mBootsnap_CompileCache;
83
87
  static VALUE rb_mBootsnap_CompileCache_Native;
84
- static VALUE rb_eBootsnap_CompileCache_Uncompilable;
85
- static ID uncompilable;
88
+ static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
89
+ static ID instrumentation_method;
90
+ static VALUE sym_miss;
91
+ static VALUE sym_stale;
92
+ static bool instrumentation_enabled = false;
93
+ static bool readonly = false;
86
94
 
87
95
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
96
+ static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
97
+ static VALUE bs_readonly_set(VALUE self, VALUE enabled);
88
98
  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);
99
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
100
+ static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
90
101
 
91
102
  /* 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);
103
+ static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
94
104
  static int bs_read_key(int fd, struct bs_cache_key * key);
95
105
  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);
106
+ static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
107
+ static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
108
+ static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
109
+ 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);
99
110
  static uint32_t get_ruby_revision(void);
100
111
  static uint32_t get_ruby_platform(void);
101
112
 
@@ -103,12 +114,10 @@ static uint32_t get_ruby_platform(void);
103
114
  * Helper functions to call ruby methods on handler object without crashing on
104
115
  * exception.
105
116
  */
106
- static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
107
- static VALUE prot_storage_to_output(VALUE arg);
117
+ static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
108
118
  static VALUE prot_input_to_output(VALUE arg);
109
- static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
110
- static VALUE prot_input_to_storage(VALUE arg);
111
- static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
119
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
120
+ static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
112
121
  struct s2o_data;
113
122
  struct i2o_data;
114
123
  struct i2s_data;
@@ -122,6 +131,12 @@ bs_rb_coverage_running(VALUE self)
122
131
  return RTEST(cov) ? Qtrue : Qfalse;
123
132
  }
124
133
 
134
+ static VALUE
135
+ bs_rb_get_path(VALUE self, VALUE fname)
136
+ {
137
+ return rb_get_path(fname);
138
+ }
139
+
125
140
  /*
126
141
  * Ruby C extensions are initialized by calling Init_<extname>.
127
142
  *
@@ -133,23 +148,50 @@ void
133
148
  Init_bootsnap(void)
134
149
  {
135
150
  rb_mBootsnap = rb_define_module("Bootsnap");
151
+
152
+ rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
153
+
136
154
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
137
155
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
138
- rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
156
+ rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
157
+ rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
139
158
 
140
159
  current_ruby_revision = get_ruby_revision();
141
160
  current_ruby_platform = get_ruby_platform();
142
161
 
143
- uncompilable = rb_intern("__bootsnap_uncompilable__");
162
+ instrumentation_method = rb_intern("_instrument");
163
+
164
+ sym_miss = ID2SYM(rb_intern("miss"));
165
+ rb_global_variable(&sym_miss);
166
+
167
+ sym_stale = ID2SYM(rb_intern("stale"));
168
+ rb_global_variable(&sym_stale);
144
169
 
170
+ rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
171
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
145
172
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
146
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
173
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
174
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
147
175
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
148
176
 
149
177
  current_umask = umask(0777);
150
178
  umask(current_umask);
151
179
  }
152
180
 
181
+ static VALUE
182
+ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
183
+ {
184
+ instrumentation_enabled = RTEST(enabled);
185
+ return enabled;
186
+ }
187
+
188
+ static VALUE
189
+ bs_readonly_set(VALUE self, VALUE enabled)
190
+ {
191
+ readonly = RTEST(enabled);
192
+ return enabled;
193
+ }
194
+
153
195
  /*
154
196
  * Bootsnap's ruby code registers a hook that notifies us via this function
155
197
  * when compile_option changes. These changes invalidate all existing caches.
@@ -167,22 +209,13 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
167
209
  return Qnil;
168
210
  }
169
211
 
170
- /*
171
- * We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
172
- * it has several nice properties:
173
- *
174
- * - Tiny implementation
175
- * - No external dependency
176
- * - Solid performance
177
- * - Solid randomness
178
- * - 32 bits doesn't feel collision-resistant enough; 64 is nice.
179
- */
180
212
  static uint64_t
181
- fnv1a_64_iter(uint64_t h, const char *str)
213
+ fnv1a_64_iter(uint64_t h, const VALUE str)
182
214
  {
183
- unsigned char *s = (unsigned char *)str;
215
+ unsigned char *s = (unsigned char *)RSTRING_PTR(str);
216
+ unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
184
217
 
185
- while (*s) {
218
+ while (s < str_end) {
186
219
  h ^= (uint64_t)*s++;
187
220
  h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
188
221
  }
@@ -191,7 +224,7 @@ fnv1a_64_iter(uint64_t h, const char *str)
191
224
  }
192
225
 
193
226
  static uint64_t
194
- fnv1a_64(const char *str)
227
+ fnv1a_64(const VALUE str)
195
228
  {
196
229
  uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
197
230
  return fnv1a_64_iter(h, str);
@@ -212,7 +245,7 @@ get_ruby_revision(void)
212
245
  } else {
213
246
  uint64_t hash;
214
247
 
215
- hash = fnv1a_64(StringValueCStr(ruby_revision));
248
+ hash = fnv1a_64(ruby_revision);
216
249
  return (uint32_t)(hash >> 32);
217
250
  }
218
251
  }
@@ -220,10 +253,6 @@ get_ruby_revision(void)
220
253
  /*
221
254
  * When ruby's version doesn't change, but it's recompiled on a different OS
222
255
  * (or OS version), we need to invalidate the cache.
223
- *
224
- * We actually factor in some extra information here, to be extra confident
225
- * that we don't try to re-use caches that will not be compatible, by factoring
226
- * in utsname.version.
227
256
  */
228
257
  static uint32_t
229
258
  get_ruby_platform(void)
@@ -232,20 +261,8 @@ get_ruby_platform(void)
232
261
  VALUE ruby_platform;
233
262
 
234
263
  ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
235
- hash = fnv1a_64(RSTRING_PTR(ruby_platform));
236
-
237
- #ifdef _WIN32
238
- return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
239
- #else
240
- struct utsname utsname;
241
-
242
- /* Not worth crashing if this fails; lose extra cache invalidation potential */
243
- if (uname(&utsname) >= 0) {
244
- hash = fnv1a_64_iter(hash, utsname.version);
245
- }
246
-
264
+ hash = fnv1a_64(ruby_platform);
247
265
  return (uint32_t)(hash >> 32);
248
- #endif
249
266
  }
250
267
 
251
268
  /*
@@ -256,14 +273,13 @@ get_ruby_platform(void)
256
273
  * The path will look something like: <cachedir>/12/34567890abcdef
257
274
  */
258
275
  static void
259
- bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
276
+ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
260
277
  {
261
278
  uint64_t hash = fnv1a_64(path);
262
-
263
279
  uint8_t first_byte = (hash >> (64 - 8));
264
280
  uint64_t remainder = hash & 0x00ffffffffffffff;
265
281
 
266
- sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
282
+ sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
267
283
  }
268
284
 
269
285
  /*
@@ -293,7 +309,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
293
309
  * conversions on the ruby VALUE arguments before passing them along.
294
310
  */
295
311
  static VALUE
296
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
312
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
297
313
  {
298
314
  FilePathValue(path_v);
299
315
 
@@ -308,27 +324,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
308
324
  char * path = RSTRING_PTR(path_v);
309
325
  char cache_path[MAX_CACHEPATH_SIZE];
310
326
 
311
- { /* generate cache path to cache_path */
312
- char * tmp = (char *)&cache_path;
313
- bs_cache_path(cachedir, path, &tmp);
314
- }
327
+ /* generate cache path to cache_path */
328
+ bs_cache_path(cachedir, path_v, &cache_path);
315
329
 
316
- return bs_fetch(path, path_v, cache_path, handler);
330
+ return bs_fetch(path, path_v, cache_path, handler, args);
317
331
  }
318
332
 
333
+ /*
334
+ * Entrypoint for Bootsnap::CompileCache::Native.precompile.
335
+ * Similar to fetch, but it only generate the cache if missing
336
+ * and doesn't return the content.
337
+ */
338
+ static VALUE
339
+ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
340
+ {
341
+ FilePathValue(path_v);
342
+
343
+ Check_Type(cachedir_v, T_STRING);
344
+ Check_Type(path_v, T_STRING);
345
+
346
+ if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
347
+ rb_raise(rb_eArgError, "cachedir too long");
348
+ }
349
+
350
+ char * cachedir = RSTRING_PTR(cachedir_v);
351
+ char * path = RSTRING_PTR(path_v);
352
+ char cache_path[MAX_CACHEPATH_SIZE];
353
+
354
+ /* generate cache path to cache_path */
355
+ bs_cache_path(cachedir, path_v, &cache_path);
356
+
357
+ return bs_precompile(path, path_v, cache_path, handler);
358
+ }
319
359
  /*
320
360
  * Open the file we want to load/cache and generate a cache key for it if it
321
361
  * was loaded.
322
362
  */
323
363
  static int
324
- open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
364
+ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
325
365
  {
326
366
  struct stat statbuf;
327
367
  int fd;
328
368
 
329
369
  fd = open(path, O_RDONLY);
330
370
  if (fd < 0) {
331
- *errno_provenance = (char *)"bs_fetch:open_current_file:open";
371
+ *errno_provenance = "bs_fetch:open_current_file:open";
332
372
  return fd;
333
373
  }
334
374
  #ifdef _WIN32
@@ -336,7 +376,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
336
376
  #endif
337
377
 
338
378
  if (fstat(fd, &statbuf) < 0) {
339
- *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
379
+ *errno_provenance = "bs_fetch:open_current_file:fstat";
340
380
  close(fd);
341
381
  return -1;
342
382
  }
@@ -352,7 +392,9 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
352
392
  }
353
393
 
354
394
  #define ERROR_WITH_ERRNO -1
355
- #define CACHE_MISSING_OR_INVALID -2
395
+ #define CACHE_MISS -2
396
+ #define CACHE_STALE -3
397
+ #define CACHE_UNCOMPILABLE -4
356
398
 
357
399
  /*
358
400
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -360,15 +402,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
360
402
  *
361
403
  * Possible return values:
362
404
  * - 0 (OK, key was loaded)
363
- * - CACHE_MISSING_OR_INVALID (-2)
364
405
  * - ERROR_WITH_ERRNO (-1, errno is set)
406
+ * - CACHE_MISS (-2)
407
+ * - CACHE_STALE (-3)
365
408
  */
366
409
  static int
367
410
  bs_read_key(int fd, struct bs_cache_key * key)
368
411
  {
369
412
  ssize_t nread = read(fd, key, KEY_SIZE);
370
413
  if (nread < 0) return ERROR_WITH_ERRNO;
371
- if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID;
414
+ if (nread < KEY_SIZE) return CACHE_STALE;
372
415
  return 0;
373
416
  }
374
417
 
@@ -378,19 +421,19 @@ bs_read_key(int fd, struct bs_cache_key * key)
378
421
  *
379
422
  * Possible return values:
380
423
  * - 0 (OK, key was loaded)
381
- * - CACHE_MISSING_OR_INVALID (-2)
424
+ * - CACHE_MISS (-2)
425
+ * - CACHE_STALE (-3)
382
426
  * - ERROR_WITH_ERRNO (-1, errno is set)
383
427
  */
384
428
  static int
385
- open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
429
+ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
386
430
  {
387
431
  int fd, res;
388
432
 
389
433
  fd = open(path, O_RDONLY);
390
434
  if (fd < 0) {
391
- *errno_provenance = (char *)"bs_fetch:open_cache_file:open";
392
- if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
393
- return ERROR_WITH_ERRNO;
435
+ *errno_provenance = "bs_fetch:open_cache_file:open";
436
+ return CACHE_MISS;
394
437
  }
395
438
  #ifdef _WIN32
396
439
  setmode(fd, O_BINARY);
@@ -398,7 +441,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
398
441
 
399
442
  res = bs_read_key(fd, key);
400
443
  if (res < 0) {
401
- *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
444
+ *errno_provenance = "bs_fetch:open_cache_file:read";
402
445
  close(fd);
403
446
  return res;
404
447
  }
@@ -422,7 +465,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
422
465
  * or exception, will be the final data returnable to the user.
423
466
  */
424
467
  static int
425
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
468
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
426
469
  {
427
470
  char * data = NULL;
428
471
  ssize_t nread;
@@ -431,26 +474,30 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
431
474
  VALUE storage_data;
432
475
 
433
476
  if (data_size > 100000000000) {
434
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
477
+ *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
435
478
  errno = EINVAL; /* because wtf? */
436
- ret = -1;
479
+ ret = ERROR_WITH_ERRNO;
437
480
  goto done;
438
481
  }
439
482
  data = ALLOC_N(char, data_size);
440
483
  nread = read(fd, data, data_size);
441
484
  if (nread < 0) {
442
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
443
- ret = -1;
485
+ *errno_provenance = "bs_fetch:fetch_cached_data:read";
486
+ ret = ERROR_WITH_ERRNO;
444
487
  goto done;
445
488
  }
446
489
  if (nread != data_size) {
447
- ret = CACHE_MISSING_OR_INVALID;
490
+ ret = CACHE_STALE;
448
491
  goto done;
449
492
  }
450
493
 
451
- storage_data = rb_str_new_static(data, data_size);
494
+ storage_data = rb_str_new(data, data_size);
452
495
 
453
- *exception_tag = bs_storage_to_output(handler, storage_data, output_data);
496
+ *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
497
+ if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
498
+ ret = CACHE_UNCOMPILABLE;
499
+ goto done;
500
+ }
454
501
  ret = 0;
455
502
  done:
456
503
  if (data != NULL) xfree(data);
@@ -491,29 +538,36 @@ mkpath(char * file_path, mode_t mode)
491
538
  * path.
492
539
  */
493
540
  static int
494
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
541
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
495
542
  {
496
543
  char template[MAX_CACHEPATH_SIZE + 20];
497
544
  char * tmp_path;
498
- int fd, ret;
545
+ int fd, ret, attempt;
499
546
  ssize_t nwrite;
500
547
 
501
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
502
- strcat(tmp_path, ".tmp.XXXXXX");
548
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
549
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
550
+ strcat(tmp_path, ".tmp.XXXXXX");
503
551
 
504
- // mkstemp modifies the template to be the actual created path
505
- fd = mkstemp(tmp_path);
506
- if (fd < 0) {
507
- if (mkpath(tmp_path, 0775) < 0) {
508
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
509
- return -1;
510
- }
511
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
512
- if (fd < 0) {
513
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
552
+ // mkstemp modifies the template to be the actual created path
553
+ fd = mkstemp(tmp_path);
554
+ if (fd > 0) break;
555
+
556
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
557
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
514
558
  return -1;
515
559
  }
516
560
  }
561
+ if (fd < 0) {
562
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
563
+ return -1;
564
+ }
565
+
566
+ if (chmod(tmp_path, 0644) < 0) {
567
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
568
+ return -1;
569
+ }
570
+
517
571
  #ifdef _WIN32
518
572
  setmode(fd, O_BINARY);
519
573
  #endif
@@ -521,11 +575,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
521
575
  key->data_size = RSTRING_LEN(data);
522
576
  nwrite = write(fd, key, KEY_SIZE);
523
577
  if (nwrite < 0) {
524
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
578
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
525
579
  return -1;
526
580
  }
527
581
  if (nwrite != KEY_SIZE) {
528
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
582
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
529
583
  errno = EIO; /* Lies but whatever */
530
584
  return -1;
531
585
  }
@@ -533,7 +587,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
533
587
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
534
588
  if (nwrite < 0) return -1;
535
589
  if (nwrite != RSTRING_LEN(data)) {
536
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
590
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
537
591
  errno = EIO; /* Lies but whatever */
538
592
  return -1;
539
593
  }
@@ -541,12 +595,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
541
595
  close(fd);
542
596
  ret = rename(tmp_path, path);
543
597
  if (ret < 0) {
544
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
598
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
545
599
  return -1;
546
600
  }
547
601
  ret = chmod(path, 0664 & ~current_umask);
548
602
  if (ret < 0) {
549
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:chmod";
603
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
550
604
  }
551
605
  return ret;
552
606
  }
@@ -555,13 +609,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
555
609
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
556
610
  * long, into a buffer */
557
611
  static ssize_t
558
- bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
612
+ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
559
613
  {
560
614
  ssize_t nread;
561
615
  *contents = ALLOC_N(char, size);
562
616
  nread = read(fd, *contents, size);
563
617
  if (nread < 0) {
564
- *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
618
+ *errno_provenance = "bs_fetch:bs_read_contents:read";
565
619
  }
566
620
  return nread;
567
621
  }
@@ -611,13 +665,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
611
665
  * - Return storage_to_output(storage_data)
612
666
  */
613
667
  static VALUE
614
- bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
668
+ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
615
669
  {
616
670
  struct bs_cache_key cached_key, current_key;
617
671
  char * contents = NULL;
618
672
  int cache_fd = -1, current_fd = -1;
619
673
  int res, valid_cache = 0, exception_tag = 0;
620
- char * errno_provenance = NULL;
674
+ const char * errno_provenance = NULL;
621
675
 
622
676
  VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
623
677
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
@@ -631,26 +685,42 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
631
685
 
632
686
  /* Open the cache key if it exists, and read its cache key in */
633
687
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
634
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
688
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
635
689
  /* This is ok: valid_cache remains false, we re-populate it. */
690
+ if (RB_UNLIKELY(instrumentation_enabled)) {
691
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
692
+ }
636
693
  } else if (cache_fd < 0) {
637
694
  goto fail_errno;
638
695
  } else {
639
696
  /* True if the cache existed and no invalidating changes have occurred since
640
697
  * it was generated. */
641
698
  valid_cache = cache_key_equal(&current_key, &cached_key);
699
+ if (RB_UNLIKELY(instrumentation_enabled)) {
700
+ if (!valid_cache) {
701
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
702
+ }
703
+ }
642
704
  }
643
705
 
644
706
  if (valid_cache) {
645
707
  /* Fetch the cache data and return it if we're able to load it successfully */
646
708
  res = fetch_cached_data(
647
- cache_fd, (ssize_t)cached_key.data_size, handler,
709
+ cache_fd, (ssize_t)cached_key.data_size, handler, args,
648
710
  &output_data, &exception_tag, &errno_provenance
649
711
  );
650
- if (exception_tag != 0) goto raise;
651
- else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
652
- else if (res == ERROR_WITH_ERRNO) goto fail_errno;
653
- else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
712
+ if (exception_tag != 0) goto raise;
713
+ else if (res == CACHE_UNCOMPILABLE) {
714
+ /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
715
+ This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
716
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
717
+ input_data = rb_str_new(contents, current_key.size);
718
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
719
+ if (exception_tag != 0) goto raise;
720
+ goto succeed;
721
+ } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
722
+ else if (res == ERROR_WITH_ERRNO) goto fail_errno;
723
+ else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
654
724
  }
655
725
  close(cache_fd);
656
726
  cache_fd = -1;
@@ -658,37 +728,47 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
658
728
 
659
729
  /* Read the contents of the source file into a buffer */
660
730
  if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
661
- input_data = rb_str_new_static(contents, current_key.size);
731
+ input_data = rb_str_new(contents, current_key.size);
662
732
 
663
733
  /* Try to compile the input_data using input_to_storage(input_data) */
664
- exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
734
+ exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
665
735
  if (exception_tag != 0) goto raise;
666
736
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
667
737
  * to cache anything; just return input_to_output(input_data) */
668
- if (storage_data == uncompilable) {
669
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
738
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
739
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
670
740
  if (exception_tag != 0) goto raise;
671
741
  goto succeed;
672
742
  }
673
743
  /* If storage_data isn't a string, we can't cache it */
674
744
  if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
675
745
 
676
- /* Write the cache key and storage_data to the cache directory */
677
- res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
678
- if (res < 0) goto fail_errno;
746
+ /* Attempt to write the cache key and storage_data to the cache directory.
747
+ * We do however ignore any failures to persist the cache, as it's better
748
+ * to move along, than to interrupt the process.
749
+ */
750
+ atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
679
751
 
680
752
  /* Having written the cache, now convert storage_data to output_data */
681
- exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
753
+ exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
682
754
  if (exception_tag != 0) goto raise;
683
755
 
684
- /* If output_data is nil, delete the cache entry and generate the output
685
- * using input_to_output */
686
- if (NIL_P(output_data)) {
756
+ if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
757
+ /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
758
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
759
+ if (exception_tag != 0) goto raise;
760
+ } else if (NIL_P(output_data)) {
761
+ /* If output_data is nil, delete the cache entry and generate the output
762
+ * using input_to_output */
687
763
  if (unlink(cache_path) < 0) {
688
- errno_provenance = (char *)"bs_fetch:unlink";
689
- goto fail_errno;
764
+ /* If the cache was already deleted, it might be that another process did it before us.
765
+ * No point raising an error */
766
+ if (errno != ENOENT) {
767
+ errno_provenance = "bs_fetch:unlink";
768
+ goto fail_errno;
769
+ }
690
770
  }
691
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
771
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
692
772
  if (exception_tag != 0) goto raise;
693
773
  }
694
774
 
@@ -719,6 +799,79 @@ invalid_type_storage_data:
719
799
  #undef CLEANUP
720
800
  }
721
801
 
802
+ static VALUE
803
+ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
804
+ {
805
+ struct bs_cache_key cached_key, current_key;
806
+ char * contents = NULL;
807
+ int cache_fd = -1, current_fd = -1;
808
+ int res, valid_cache = 0, exception_tag = 0;
809
+ const char * errno_provenance = NULL;
810
+
811
+ VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
812
+ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
813
+
814
+ /* Open the source file and generate a cache key for it */
815
+ current_fd = open_current_file(path, &current_key, &errno_provenance);
816
+ if (current_fd < 0) goto fail;
817
+
818
+ /* Open the cache key if it exists, and read its cache key in */
819
+ cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
820
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
821
+ /* This is ok: valid_cache remains false, we re-populate it. */
822
+ } else if (cache_fd < 0) {
823
+ goto fail;
824
+ } else {
825
+ /* True if the cache existed and no invalidating changes have occurred since
826
+ * it was generated. */
827
+ valid_cache = cache_key_equal(&current_key, &cached_key);
828
+ }
829
+
830
+ if (valid_cache) {
831
+ goto succeed;
832
+ }
833
+
834
+ close(cache_fd);
835
+ cache_fd = -1;
836
+ /* Cache is stale, invalid, or missing. Regenerate and write it out. */
837
+
838
+ /* Read the contents of the source file into a buffer */
839
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
840
+ input_data = rb_str_new(contents, current_key.size);
841
+
842
+ /* Try to compile the input_data using input_to_storage(input_data) */
843
+ exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
844
+ if (exception_tag != 0) goto fail;
845
+
846
+ /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
847
+ * to cache anything; just return false */
848
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
849
+ goto fail;
850
+ }
851
+ /* If storage_data isn't a string, we can't cache it */
852
+ if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
853
+
854
+ /* Write the cache key and storage_data to the cache directory */
855
+ res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
856
+ if (res < 0) goto fail;
857
+
858
+ goto succeed;
859
+
860
+ #define CLEANUP \
861
+ if (contents != NULL) xfree(contents); \
862
+ if (current_fd >= 0) close(current_fd); \
863
+ if (cache_fd >= 0) close(cache_fd);
864
+
865
+ succeed:
866
+ CLEANUP;
867
+ return Qtrue;
868
+ fail:
869
+ CLEANUP;
870
+ return Qfalse;
871
+ #undef CLEANUP
872
+ }
873
+
874
+
722
875
  /*****************************************************************************/
723
876
  /********************* Handler Wrappers **************************************/
724
877
  /*****************************************************************************
@@ -738,11 +891,13 @@ invalid_type_storage_data:
738
891
 
739
892
  struct s2o_data {
740
893
  VALUE handler;
894
+ VALUE args;
741
895
  VALUE storage_data;
742
896
  };
743
897
 
744
898
  struct i2o_data {
745
899
  VALUE handler;
900
+ VALUE args;
746
901
  VALUE input_data;
747
902
  };
748
903
 
@@ -753,29 +908,31 @@ struct i2s_data {
753
908
  };
754
909
 
755
910
  static VALUE
756
- prot_storage_to_output(VALUE arg)
911
+ try_storage_to_output(VALUE arg)
757
912
  {
758
913
  struct s2o_data * data = (struct s2o_data *)arg;
759
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
914
+ return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
760
915
  }
761
916
 
762
917
  static int
763
- bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
918
+ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
764
919
  {
765
920
  int state;
766
921
  struct s2o_data s2o_data = {
767
922
  .handler = handler,
923
+ .args = args,
768
924
  .storage_data = storage_data,
769
925
  };
770
- *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
926
+ *output_data = rb_protect(try_storage_to_output, (VALUE)&s2o_data, &state);
771
927
  return state;
772
928
  }
773
929
 
774
930
  static void
775
- bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
931
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
776
932
  {
777
933
  struct i2o_data i2o_data = {
778
934
  .handler = handler,
935
+ .args = args,
779
936
  .input_data = input_data,
780
937
  };
781
938
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
@@ -785,7 +942,7 @@ static VALUE
785
942
  prot_input_to_output(VALUE arg)
786
943
  {
787
944
  struct i2o_data * data = (struct i2o_data *)arg;
788
- return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
945
+ return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
789
946
  }
790
947
 
791
948
  static VALUE
@@ -795,31 +952,20 @@ try_input_to_storage(VALUE arg)
795
952
  return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
796
953
  }
797
954
 
798
- static VALUE
799
- rescue_input_to_storage(VALUE arg)
800
- {
801
- return uncompilable;
802
- }
803
-
804
- static VALUE
805
- prot_input_to_storage(VALUE arg)
806
- {
807
- struct i2s_data * data = (struct i2s_data *)arg;
808
- return rb_rescue2(
809
- try_input_to_storage, (VALUE)data,
810
- rescue_input_to_storage, Qnil,
811
- rb_eBootsnap_CompileCache_Uncompilable, 0);
812
- }
813
-
814
955
  static int
815
- bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
956
+ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
816
957
  {
817
- int state;
818
- struct i2s_data i2s_data = {
819
- .handler = handler,
820
- .input_data = input_data,
821
- .pathval = pathval,
822
- };
823
- *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
824
- return state;
958
+ if (readonly) {
959
+ *storage_data = rb_cBootsnap_CompileCache_UNCOMPILABLE;
960
+ return 0;
961
+ } else {
962
+ int state;
963
+ struct i2s_data i2s_data = {
964
+ .handler = handler,
965
+ .input_data = input_data,
966
+ .pathval = pathval,
967
+ };
968
+ *storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
969
+ return state;
970
+ }
825
971
  }