bootsnap 1.4.5 → 1.18.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +63 -23
  5. data/exe/bootsnap +5 -0
  6. data/ext/bootsnap/bootsnap.c +504 -184
  7. data/ext/bootsnap/extconf.rb +30 -15
  8. data/lib/bootsnap/bundler.rb +3 -1
  9. data/lib/bootsnap/cli/worker_pool.rb +136 -0
  10. data/lib/bootsnap/cli.rb +283 -0
  11. data/lib/bootsnap/compile_cache/iseq.rb +72 -21
  12. data/lib/bootsnap/compile_cache/json.rb +89 -0
  13. data/lib/bootsnap/compile_cache/yaml.rb +316 -41
  14. data/lib/bootsnap/compile_cache.rb +27 -17
  15. data/lib/bootsnap/explicit_require.rb +5 -3
  16. data/lib/bootsnap/load_path_cache/cache.rb +73 -37
  17. data/lib/bootsnap/load_path_cache/change_observer.rb +25 -3
  18. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +27 -82
  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 +40 -38
  25. data/lib/bootsnap/setup.rb +3 -36
  26. data/lib/bootsnap/version.rb +3 -1
  27. data/lib/bootsnap.rb +141 -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,12 +14,21 @@
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
+ #include <unistd.h>
20
22
  #include <sys/stat.h>
21
- #ifndef _WIN32
22
- #include <sys/utsname.h>
23
+
24
+ #ifdef __APPLE__
25
+ // The symbol is present, however not in the headers
26
+ // See: https://github.com/Shopify/bootsnap/issues/470
27
+ extern int fdatasync(int);
28
+ #endif
29
+
30
+ #ifndef O_NOATIME
31
+ #define O_NOATIME 0
23
32
  #endif
24
33
 
25
34
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
@@ -29,6 +38,12 @@
29
38
 
30
39
  #define KEY_SIZE 64
31
40
 
41
+ #define MAX_CREATE_TEMPFILE_ATTEMPT 3
42
+
43
+ #ifndef RB_UNLIKELY
44
+ #define RB_UNLIKELY(x) (x)
45
+ #endif
46
+
32
47
  /*
33
48
  * An instance of this key is written as the first 64 bytes of each cache file.
34
49
  * The mtime and size members track whether the file contents have changed, and
@@ -50,8 +65,10 @@ struct bs_cache_key {
50
65
  uint32_t ruby_revision;
51
66
  uint64_t size;
52
67
  uint64_t mtime;
53
- uint64_t data_size; /* not used for equality */
54
- uint8_t pad[24];
68
+ uint64_t data_size; //
69
+ uint64_t digest;
70
+ uint8_t digest_set;
71
+ uint8_t pad[15];
55
72
  } __attribute__((packed));
56
73
 
57
74
  /*
@@ -65,7 +82,7 @@ struct bs_cache_key {
65
82
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
66
83
 
67
84
  /* Effectively a schema version. Bumping invalidates all previous caches */
68
- static const uint32_t current_version = 2;
85
+ static const uint32_t current_version = 5;
69
86
 
70
87
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
71
88
  * new OS ABI, etc. */
@@ -81,21 +98,39 @@ static mode_t current_umask;
81
98
  static VALUE rb_mBootsnap;
82
99
  static VALUE rb_mBootsnap_CompileCache;
83
100
  static VALUE rb_mBootsnap_CompileCache_Native;
84
- static VALUE rb_eBootsnap_CompileCache_Uncompilable;
85
- static ID uncompilable;
101
+ static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
102
+ static ID instrumentation_method;
103
+ static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated;
104
+ static bool instrumentation_enabled = false;
105
+ static bool readonly = false;
106
+ static bool revalidation = false;
107
+ static bool perm_issue = false;
86
108
 
87
109
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
110
+ static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
111
+ static VALUE bs_readonly_set(VALUE self, VALUE enabled);
112
+ static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
88
113
  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);
114
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
115
+ static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
90
116
 
91
117
  /* 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);
118
+ enum cache_status {
119
+ miss,
120
+ hit,
121
+ stale,
122
+ };
123
+ static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
94
124
  static int bs_read_key(int fd, struct bs_cache_key * key);
95
- 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);
125
+ static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
126
+ static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
127
+ static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance);
128
+
129
+ static void bs_cache_key_digest(struct bs_cache_key * key, const VALUE input_data);
130
+ static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
131
+ static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
132
+ static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
133
+ 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
134
  static uint32_t get_ruby_revision(void);
100
135
  static uint32_t get_ruby_platform(void);
101
136
 
@@ -103,12 +138,10 @@ static uint32_t get_ruby_platform(void);
103
138
  * Helper functions to call ruby methods on handler object without crashing on
104
139
  * exception.
105
140
  */
106
- static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
107
- static VALUE prot_storage_to_output(VALUE arg);
141
+ static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
108
142
  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);
143
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
144
+ static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
112
145
  struct s2o_data;
113
146
  struct i2o_data;
114
147
  struct i2s_data;
@@ -122,6 +155,12 @@ bs_rb_coverage_running(VALUE self)
122
155
  return RTEST(cov) ? Qtrue : Qfalse;
123
156
  }
124
157
 
158
+ static VALUE
159
+ bs_rb_get_path(VALUE self, VALUE fname)
160
+ {
161
+ return rb_get_path(fname);
162
+ }
163
+
125
164
  /*
126
165
  * Ruby C extensions are initialized by calling Init_<extname>.
127
166
  *
@@ -133,23 +172,65 @@ void
133
172
  Init_bootsnap(void)
134
173
  {
135
174
  rb_mBootsnap = rb_define_module("Bootsnap");
175
+
176
+ rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
177
+
136
178
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
137
179
  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);
180
+ rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
181
+ rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
139
182
 
140
183
  current_ruby_revision = get_ruby_revision();
141
184
  current_ruby_platform = get_ruby_platform();
142
185
 
143
- uncompilable = rb_intern("__bootsnap_uncompilable__");
186
+ instrumentation_method = rb_intern("_instrument");
144
187
 
188
+ sym_hit = ID2SYM(rb_intern("hit"));
189
+ sym_miss = ID2SYM(rb_intern("miss"));
190
+ sym_stale = ID2SYM(rb_intern("stale"));
191
+ sym_revalidated = ID2SYM(rb_intern("revalidated"));
192
+
193
+ rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
194
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
195
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
145
196
  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);
197
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
198
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
147
199
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
148
200
 
149
201
  current_umask = umask(0777);
150
202
  umask(current_umask);
151
203
  }
152
204
 
205
+ static VALUE
206
+ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
207
+ {
208
+ instrumentation_enabled = RTEST(enabled);
209
+ return enabled;
210
+ }
211
+
212
+ static inline void
213
+ bs_instrumentation(VALUE event, VALUE path)
214
+ {
215
+ if (RB_UNLIKELY(instrumentation_enabled)) {
216
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
217
+ }
218
+ }
219
+
220
+ static VALUE
221
+ bs_readonly_set(VALUE self, VALUE enabled)
222
+ {
223
+ readonly = RTEST(enabled);
224
+ return enabled;
225
+ }
226
+
227
+ static VALUE
228
+ bs_revalidation_set(VALUE self, VALUE enabled)
229
+ {
230
+ revalidation = RTEST(enabled);
231
+ return enabled;
232
+ }
233
+
153
234
  /*
154
235
  * Bootsnap's ruby code registers a hook that notifies us via this function
155
236
  * when compile_option changes. These changes invalidate all existing caches.
@@ -167,22 +248,13 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
167
248
  return Qnil;
168
249
  }
169
250
 
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
251
  static uint64_t
181
- fnv1a_64_iter(uint64_t h, const char *str)
252
+ fnv1a_64_iter(uint64_t h, const VALUE str)
182
253
  {
183
- unsigned char *s = (unsigned char *)str;
254
+ unsigned char *s = (unsigned char *)RSTRING_PTR(str);
255
+ unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
184
256
 
185
- while (*s) {
257
+ while (s < str_end) {
186
258
  h ^= (uint64_t)*s++;
187
259
  h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
188
260
  }
@@ -191,7 +263,7 @@ fnv1a_64_iter(uint64_t h, const char *str)
191
263
  }
192
264
 
193
265
  static uint64_t
194
- fnv1a_64(const char *str)
266
+ fnv1a_64(const VALUE str)
195
267
  {
196
268
  uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
197
269
  return fnv1a_64_iter(h, str);
@@ -212,7 +284,7 @@ get_ruby_revision(void)
212
284
  } else {
213
285
  uint64_t hash;
214
286
 
215
- hash = fnv1a_64(StringValueCStr(ruby_revision));
287
+ hash = fnv1a_64(ruby_revision);
216
288
  return (uint32_t)(hash >> 32);
217
289
  }
218
290
  }
@@ -220,10 +292,6 @@ get_ruby_revision(void)
220
292
  /*
221
293
  * When ruby's version doesn't change, but it's recompiled on a different OS
222
294
  * (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
295
  */
228
296
  static uint32_t
229
297
  get_ruby_platform(void)
@@ -232,20 +300,8 @@ get_ruby_platform(void)
232
300
  VALUE ruby_platform;
233
301
 
234
302
  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
-
303
+ hash = fnv1a_64(ruby_platform);
247
304
  return (uint32_t)(hash >> 32);
248
- #endif
249
305
  }
250
306
 
251
307
  /*
@@ -256,14 +312,13 @@ get_ruby_platform(void)
256
312
  * The path will look something like: <cachedir>/12/34567890abcdef
257
313
  */
258
314
  static void
259
- bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
315
+ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
260
316
  {
261
317
  uint64_t hash = fnv1a_64(path);
262
-
263
318
  uint8_t first_byte = (hash >> (64 - 8));
264
319
  uint64_t remainder = hash & 0x00ffffffffffffff;
265
320
 
266
- sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
321
+ sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
267
322
  }
268
323
 
269
324
  /*
@@ -274,17 +329,59 @@ bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
274
329
  * The data_size member is not compared, as it serves more of a "header"
275
330
  * function.
276
331
  */
277
- static int
278
- cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
332
+ static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
333
+ struct bs_cache_key *k2) {
334
+ if (k1->version == k2->version &&
335
+ k1->ruby_platform == k2->ruby_platform &&
336
+ k1->compile_option == k2->compile_option &&
337
+ k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) {
338
+ if (k1->mtime == k2->mtime) {
339
+ return hit;
340
+ }
341
+ if (revalidation) {
342
+ return stale;
343
+ }
344
+ }
345
+ return miss;
346
+ }
347
+
348
+ static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
349
+ struct bs_cache_key *cached_key,
350
+ const VALUE input_data)
279
351
  {
280
- return (
281
- k1->version == k2->version &&
282
- k1->ruby_platform == k2->ruby_platform &&
283
- k1->compile_option == k2->compile_option &&
284
- k1->ruby_revision == k2->ruby_revision &&
285
- k1->size == k2->size &&
286
- k1->mtime == k2->mtime
287
- );
352
+ bs_cache_key_digest(current_key, input_data);
353
+ return current_key->digest == cached_key->digest;
354
+ }
355
+
356
+ static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
357
+ {
358
+ old_key->mtime = current_key->mtime;
359
+ lseek(cache_fd, 0, SEEK_SET);
360
+ ssize_t nwrite = write(cache_fd, old_key, KEY_SIZE);
361
+ if (nwrite < 0) {
362
+ *errno_provenance = "update_cache_key:write";
363
+ return -1;
364
+ }
365
+
366
+ #ifdef HAVE_FDATASYNC
367
+ if (fdatasync(cache_fd) < 0) {
368
+ *errno_provenance = "update_cache_key:fdatasync";
369
+ return -1;
370
+ }
371
+ #endif
372
+
373
+ return 0;
374
+ }
375
+
376
+ /*
377
+ * Fills the cache key digest.
378
+ */
379
+ static void bs_cache_key_digest(struct bs_cache_key *key,
380
+ const VALUE input_data) {
381
+ if (key->digest_set)
382
+ return;
383
+ key->digest = fnv1a_64(input_data);
384
+ key->digest_set = 1;
288
385
  }
289
386
 
290
387
  /*
@@ -293,7 +390,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
293
390
  * conversions on the ruby VALUE arguments before passing them along.
294
391
  */
295
392
  static VALUE
296
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
393
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
297
394
  {
298
395
  FilePathValue(path_v);
299
396
 
@@ -308,12 +405,53 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
308
405
  char * path = RSTRING_PTR(path_v);
309
406
  char cache_path[MAX_CACHEPATH_SIZE];
310
407
 
311
- { /* generate cache path to cache_path */
312
- char * tmp = (char *)&cache_path;
313
- bs_cache_path(cachedir, path, &tmp);
408
+ /* generate cache path to cache_path */
409
+ bs_cache_path(cachedir, path_v, &cache_path);
410
+
411
+ return bs_fetch(path, path_v, cache_path, handler, args);
412
+ }
413
+
414
+ /*
415
+ * Entrypoint for Bootsnap::CompileCache::Native.precompile.
416
+ * Similar to fetch, but it only generate the cache if missing
417
+ * and doesn't return the content.
418
+ */
419
+ static VALUE
420
+ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
421
+ {
422
+ FilePathValue(path_v);
423
+
424
+ Check_Type(cachedir_v, T_STRING);
425
+ Check_Type(path_v, T_STRING);
426
+
427
+ if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
428
+ rb_raise(rb_eArgError, "cachedir too long");
429
+ }
430
+
431
+ char * cachedir = RSTRING_PTR(cachedir_v);
432
+ char * path = RSTRING_PTR(path_v);
433
+ char cache_path[MAX_CACHEPATH_SIZE];
434
+
435
+ /* generate cache path to cache_path */
436
+ bs_cache_path(cachedir, path_v, &cache_path);
437
+
438
+ return bs_precompile(path, path_v, cache_path, handler);
439
+ }
440
+
441
+ static int bs_open_noatime(const char *path, int flags) {
442
+ int fd = 1;
443
+ if (!perm_issue) {
444
+ fd = open(path, flags | O_NOATIME);
445
+ if (fd < 0 && errno == EPERM) {
446
+ errno = 0;
447
+ perm_issue = true;
448
+ }
314
449
  }
315
450
 
316
- return bs_fetch(path, path_v, cache_path, handler);
451
+ if (perm_issue) {
452
+ fd = open(path, flags);
453
+ }
454
+ return fd;
317
455
  }
318
456
 
319
457
  /*
@@ -321,14 +459,14 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
321
459
  * was loaded.
322
460
  */
323
461
  static int
324
- open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
462
+ open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
325
463
  {
326
464
  struct stat statbuf;
327
465
  int fd;
328
466
 
329
- fd = open(path, O_RDONLY);
467
+ fd = bs_open_noatime(path, O_RDONLY);
330
468
  if (fd < 0) {
331
- *errno_provenance = (char *)"bs_fetch:open_current_file:open";
469
+ *errno_provenance = "bs_fetch:open_current_file:open";
332
470
  return fd;
333
471
  }
334
472
  #ifdef _WIN32
@@ -336,8 +474,10 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
336
474
  #endif
337
475
 
338
476
  if (fstat(fd, &statbuf) < 0) {
339
- *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
477
+ *errno_provenance = "bs_fetch:open_current_file:fstat";
478
+ int previous_errno = errno;
340
479
  close(fd);
480
+ errno = previous_errno;
341
481
  return -1;
342
482
  }
343
483
 
@@ -347,12 +487,15 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
347
487
  key->ruby_revision = current_ruby_revision;
348
488
  key->size = (uint64_t)statbuf.st_size;
349
489
  key->mtime = (uint64_t)statbuf.st_mtime;
490
+ key->digest_set = false;
350
491
 
351
492
  return fd;
352
493
  }
353
494
 
354
495
  #define ERROR_WITH_ERRNO -1
355
- #define CACHE_MISSING_OR_INVALID -2
496
+ #define CACHE_MISS -2
497
+ #define CACHE_STALE -3
498
+ #define CACHE_UNCOMPILABLE -4
356
499
 
357
500
  /*
358
501
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -360,15 +503,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
360
503
  *
361
504
  * Possible return values:
362
505
  * - 0 (OK, key was loaded)
363
- * - CACHE_MISSING_OR_INVALID (-2)
364
506
  * - ERROR_WITH_ERRNO (-1, errno is set)
507
+ * - CACHE_MISS (-2)
508
+ * - CACHE_STALE (-3)
365
509
  */
366
510
  static int
367
511
  bs_read_key(int fd, struct bs_cache_key * key)
368
512
  {
369
513
  ssize_t nread = read(fd, key, KEY_SIZE);
370
514
  if (nread < 0) return ERROR_WITH_ERRNO;
371
- if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID;
515
+ if (nread < KEY_SIZE) return CACHE_STALE;
372
516
  return 0;
373
517
  }
374
518
 
@@ -378,19 +522,24 @@ bs_read_key(int fd, struct bs_cache_key * key)
378
522
  *
379
523
  * Possible return values:
380
524
  * - 0 (OK, key was loaded)
381
- * - CACHE_MISSING_OR_INVALID (-2)
525
+ * - CACHE_MISS (-2)
526
+ * - CACHE_STALE (-3)
382
527
  * - ERROR_WITH_ERRNO (-1, errno is set)
383
528
  */
384
529
  static int
385
- open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
530
+ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
386
531
  {
387
532
  int fd, res;
388
533
 
389
- fd = open(path, O_RDONLY);
534
+ if (readonly || !revalidation) {
535
+ fd = bs_open_noatime(path, O_RDONLY);
536
+ } else {
537
+ fd = bs_open_noatime(path, O_RDWR);
538
+ }
539
+
390
540
  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;
541
+ *errno_provenance = "bs_fetch:open_cache_file:open";
542
+ return CACHE_MISS;
394
543
  }
395
544
  #ifdef _WIN32
396
545
  setmode(fd, O_BINARY);
@@ -398,7 +547,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
398
547
 
399
548
  res = bs_read_key(fd, key);
400
549
  if (res < 0) {
401
- *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
550
+ *errno_provenance = "bs_fetch:open_cache_file:read";
402
551
  close(fd);
403
552
  return res;
404
553
  }
@@ -422,38 +571,40 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
422
571
  * or exception, will be the final data returnable to the user.
423
572
  */
424
573
  static int
425
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
574
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
426
575
  {
427
- char * data = NULL;
428
576
  ssize_t nread;
429
577
  int ret;
430
578
 
431
579
  VALUE storage_data;
432
580
 
433
581
  if (data_size > 100000000000) {
434
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
582
+ *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
435
583
  errno = EINVAL; /* because wtf? */
436
- ret = -1;
584
+ ret = ERROR_WITH_ERRNO;
437
585
  goto done;
438
586
  }
439
- data = ALLOC_N(char, data_size);
440
- nread = read(fd, data, data_size);
587
+ storage_data = rb_str_buf_new(data_size);
588
+ nread = read(fd, RSTRING_PTR(storage_data), data_size);
441
589
  if (nread < 0) {
442
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
443
- ret = -1;
590
+ *errno_provenance = "bs_fetch:fetch_cached_data:read";
591
+ ret = ERROR_WITH_ERRNO;
444
592
  goto done;
445
593
  }
446
594
  if (nread != data_size) {
447
- ret = CACHE_MISSING_OR_INVALID;
595
+ ret = CACHE_STALE;
448
596
  goto done;
449
597
  }
450
598
 
451
- storage_data = rb_str_new_static(data, data_size);
599
+ rb_str_set_len(storage_data, nread);
452
600
 
453
- *exception_tag = bs_storage_to_output(handler, storage_data, output_data);
601
+ *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
602
+ if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
603
+ ret = CACHE_UNCOMPILABLE;
604
+ goto done;
605
+ }
454
606
  ret = 0;
455
607
  done:
456
- if (data != NULL) xfree(data);
457
608
  return ret;
458
609
  }
459
610
 
@@ -491,29 +642,36 @@ mkpath(char * file_path, mode_t mode)
491
642
  * path.
492
643
  */
493
644
  static int
494
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
645
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
495
646
  {
496
647
  char template[MAX_CACHEPATH_SIZE + 20];
497
648
  char * tmp_path;
498
- int fd, ret;
649
+ int fd, ret, attempt;
499
650
  ssize_t nwrite;
500
651
 
501
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
502
- strcat(tmp_path, ".tmp.XXXXXX");
652
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
653
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
654
+ strcat(tmp_path, ".tmp.XXXXXX");
503
655
 
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";
656
+ // mkstemp modifies the template to be the actual created path
657
+ fd = mkstemp(tmp_path);
658
+ if (fd > 0) break;
659
+
660
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
661
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
514
662
  return -1;
515
663
  }
516
664
  }
665
+ if (fd < 0) {
666
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
667
+ return -1;
668
+ }
669
+
670
+ if (chmod(tmp_path, 0644) < 0) {
671
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
672
+ return -1;
673
+ }
674
+
517
675
  #ifdef _WIN32
518
676
  setmode(fd, O_BINARY);
519
677
  #endif
@@ -521,11 +679,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
521
679
  key->data_size = RSTRING_LEN(data);
522
680
  nwrite = write(fd, key, KEY_SIZE);
523
681
  if (nwrite < 0) {
524
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
682
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
525
683
  return -1;
526
684
  }
527
685
  if (nwrite != KEY_SIZE) {
528
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
686
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
529
687
  errno = EIO; /* Lies but whatever */
530
688
  return -1;
531
689
  }
@@ -533,7 +691,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
533
691
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
534
692
  if (nwrite < 0) return -1;
535
693
  if (nwrite != RSTRING_LEN(data)) {
536
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
694
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
537
695
  errno = EIO; /* Lies but whatever */
538
696
  return -1;
539
697
  }
@@ -541,29 +699,34 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
541
699
  close(fd);
542
700
  ret = rename(tmp_path, path);
543
701
  if (ret < 0) {
544
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
702
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
545
703
  return -1;
546
704
  }
547
705
  ret = chmod(path, 0664 & ~current_umask);
548
706
  if (ret < 0) {
549
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:chmod";
707
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
550
708
  }
551
709
  return ret;
552
710
  }
553
711
 
554
712
 
555
713
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
556
- * long, into a buffer */
557
- static ssize_t
558
- bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
714
+ * long, returning a Ruby string on success and Qfalse on failure */
715
+ static VALUE
716
+ bs_read_contents(int fd, size_t size, const char ** errno_provenance)
559
717
  {
718
+ VALUE contents;
560
719
  ssize_t nread;
561
- *contents = ALLOC_N(char, size);
562
- nread = read(fd, *contents, size);
720
+ contents = rb_str_buf_new(size);
721
+ nread = read(fd, RSTRING_PTR(contents), size);
722
+
563
723
  if (nread < 0) {
564
- *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
724
+ *errno_provenance = "bs_fetch:bs_read_contents:read";
725
+ return Qfalse;
726
+ } else {
727
+ rb_str_set_len(contents, nread);
728
+ return contents;
565
729
  }
566
- return nread;
567
730
  }
568
731
 
569
732
  /*
@@ -611,91 +774,155 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
611
774
  * - Return storage_to_output(storage_data)
612
775
  */
613
776
  static VALUE
614
- bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
777
+ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
615
778
  {
616
779
  struct bs_cache_key cached_key, current_key;
617
- char * contents = NULL;
618
780
  int cache_fd = -1, current_fd = -1;
619
781
  int res, valid_cache = 0, exception_tag = 0;
620
- char * errno_provenance = NULL;
782
+ const char * errno_provenance = NULL;
621
783
 
622
- VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
784
+ VALUE status = Qfalse;
785
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
623
786
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
624
787
  VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
625
788
 
626
789
  VALUE exception; /* ruby exception object to raise instead of returning */
790
+ VALUE exception_message; /* ruby exception string to use instead of errno_provenance */
627
791
 
628
792
  /* Open the source file and generate a cache key for it */
629
793
  current_fd = open_current_file(path, &current_key, &errno_provenance);
630
- if (current_fd < 0) goto fail_errno;
794
+ if (current_fd < 0) {
795
+ exception_message = path_v;
796
+ goto fail_errno;
797
+ }
631
798
 
632
799
  /* Open the cache key if it exists, and read its cache key in */
633
800
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
634
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
801
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
635
802
  /* This is ok: valid_cache remains false, we re-populate it. */
803
+ bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
636
804
  } else if (cache_fd < 0) {
805
+ exception_message = rb_str_new_cstr(cache_path);
637
806
  goto fail_errno;
638
807
  } else {
639
808
  /* True if the cache existed and no invalidating changes have occurred since
640
809
  * it was generated. */
641
- valid_cache = cache_key_equal(&current_key, &cached_key);
810
+
811
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
812
+ case hit:
813
+ status = sym_hit;
814
+ valid_cache = true;
815
+ break;
816
+ case miss:
817
+ valid_cache = false;
818
+ break;
819
+ case stale:
820
+ valid_cache = false;
821
+ if ((input_data = bs_read_contents(current_fd, current_key.size,
822
+ &errno_provenance)) == Qfalse) {
823
+ exception_message = path_v;
824
+ goto fail_errno;
825
+ }
826
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
827
+ if (valid_cache) {
828
+ if (!readonly) {
829
+ if (update_cache_key(&current_key, &cached_key, cache_fd, &errno_provenance)) {
830
+ exception_message = path_v;
831
+ goto fail_errno;
832
+ }
833
+ }
834
+ status = sym_revalidated;
835
+ }
836
+ break;
837
+ };
838
+
839
+ if (!valid_cache) {
840
+ status = sym_stale;
841
+ }
642
842
  }
643
843
 
644
844
  if (valid_cache) {
645
845
  /* Fetch the cache data and return it if we're able to load it successfully */
646
846
  res = fetch_cached_data(
647
- cache_fd, (ssize_t)cached_key.data_size, handler,
847
+ cache_fd, (ssize_t)cached_key.data_size, handler, args,
648
848
  &output_data, &exception_tag, &errno_provenance
649
849
  );
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 */
850
+ if (exception_tag != 0) goto raise;
851
+ else if (res == CACHE_UNCOMPILABLE) {
852
+ /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
853
+ This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
854
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
855
+ exception_message = path_v;
856
+ goto fail_errno;
857
+ }
858
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
859
+ if (exception_tag != 0) goto raise;
860
+ goto succeed;
861
+ } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
862
+ else if (res == ERROR_WITH_ERRNO){
863
+ exception_message = rb_str_new_cstr(cache_path);
864
+ goto fail_errno;
865
+ }
866
+ else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
654
867
  }
655
868
  close(cache_fd);
656
869
  cache_fd = -1;
657
870
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
658
871
 
659
872
  /* Read the contents of the source file into a buffer */
660
- 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);
873
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
874
+ exception_message = path_v;
875
+ goto fail_errno;
876
+ }
662
877
 
663
878
  /* 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);
879
+ exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
665
880
  if (exception_tag != 0) goto raise;
666
881
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
667
882
  * 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);
883
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
884
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
670
885
  if (exception_tag != 0) goto raise;
671
886
  goto succeed;
672
887
  }
673
888
  /* If storage_data isn't a string, we can't cache it */
674
889
  if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
675
890
 
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;
891
+ /* Attempt to write the cache key and storage_data to the cache directory.
892
+ * We do however ignore any failures to persist the cache, as it's better
893
+ * to move along, than to interrupt the process.
894
+ */
895
+ bs_cache_key_digest(&current_key, input_data);
896
+ atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
679
897
 
680
898
  /* Having written the cache, now convert storage_data to output_data */
681
- exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
899
+ exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
682
900
  if (exception_tag != 0) goto raise;
683
901
 
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)) {
902
+ if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
903
+ /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
904
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
905
+ if (exception_tag != 0) goto raise;
906
+ } else if (NIL_P(output_data)) {
907
+ /* If output_data is nil, delete the cache entry and generate the output
908
+ * using input_to_output */
687
909
  if (unlink(cache_path) < 0) {
688
- errno_provenance = (char *)"bs_fetch:unlink";
689
- goto fail_errno;
910
+ /* If the cache was already deleted, it might be that another process did it before us.
911
+ * No point raising an error */
912
+ if (errno != ENOENT) {
913
+ errno_provenance = "bs_fetch:unlink";
914
+ exception_message = rb_str_new_cstr(cache_path);
915
+ goto fail_errno;
916
+ }
690
917
  }
691
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
918
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
692
919
  if (exception_tag != 0) goto raise;
693
920
  }
694
921
 
695
922
  goto succeed; /* output_data is now the correct return. */
696
923
 
697
924
  #define CLEANUP \
698
- if (contents != NULL) xfree(contents); \
925
+ if (status != Qfalse) bs_instrumentation(status, path_v); \
699
926
  if (current_fd >= 0) close(current_fd); \
700
927
  if (cache_fd >= 0) close(cache_fd);
701
928
 
@@ -704,7 +931,13 @@ succeed:
704
931
  return output_data;
705
932
  fail_errno:
706
933
  CLEANUP;
707
- exception = rb_syserr_new(errno, errno_provenance);
934
+ if (errno_provenance) {
935
+ exception_message = rb_str_concat(
936
+ rb_str_new_cstr(errno_provenance),
937
+ rb_str_concat(rb_str_new_cstr(": "), exception_message)
938
+ );
939
+ }
940
+ exception = rb_syserr_new_str(errno, exception_message);
708
941
  rb_exc_raise(exception);
709
942
  __builtin_unreachable();
710
943
  raise:
@@ -719,6 +952,100 @@ invalid_type_storage_data:
719
952
  #undef CLEANUP
720
953
  }
721
954
 
955
+ static VALUE
956
+ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
957
+ {
958
+ if (readonly) {
959
+ return Qfalse;
960
+ }
961
+
962
+ struct bs_cache_key cached_key, current_key;
963
+ int cache_fd = -1, current_fd = -1;
964
+ int res, valid_cache = 0, exception_tag = 0;
965
+ const char * errno_provenance = NULL;
966
+
967
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
968
+ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
969
+
970
+ /* Open the source file and generate a cache key for it */
971
+ current_fd = open_current_file(path, &current_key, &errno_provenance);
972
+ if (current_fd < 0) goto fail;
973
+
974
+ /* Open the cache key if it exists, and read its cache key in */
975
+ cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
976
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
977
+ /* This is ok: valid_cache remains false, we re-populate it. */
978
+ } else if (cache_fd < 0) {
979
+ goto fail;
980
+ } else {
981
+ /* True if the cache existed and no invalidating changes have occurred since
982
+ * it was generated. */
983
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
984
+ case hit:
985
+ valid_cache = true;
986
+ break;
987
+ case miss:
988
+ valid_cache = false;
989
+ break;
990
+ case stale:
991
+ valid_cache = false;
992
+ if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
993
+ goto fail;
994
+ }
995
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
996
+ if (valid_cache) {
997
+ if (update_cache_key(&current_key, &cached_key, cache_fd, &errno_provenance)) {
998
+ goto fail;
999
+ }
1000
+ }
1001
+ break;
1002
+ };
1003
+ }
1004
+
1005
+ if (valid_cache) {
1006
+ goto succeed;
1007
+ }
1008
+
1009
+ close(cache_fd);
1010
+ cache_fd = -1;
1011
+ /* Cache is stale, invalid, or missing. Regenerate and write it out. */
1012
+
1013
+ /* Read the contents of the source file into a buffer */
1014
+ if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
1015
+
1016
+ /* Try to compile the input_data using input_to_storage(input_data) */
1017
+ exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
1018
+ if (exception_tag != 0) goto fail;
1019
+
1020
+ /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
1021
+ * to cache anything; just return false */
1022
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
1023
+ goto fail;
1024
+ }
1025
+ /* If storage_data isn't a string, we can't cache it */
1026
+ if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
1027
+
1028
+ /* Write the cache key and storage_data to the cache directory */
1029
+ bs_cache_key_digest(&current_key, input_data);
1030
+ res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
1031
+ if (res < 0) goto fail;
1032
+
1033
+ goto succeed;
1034
+
1035
+ #define CLEANUP \
1036
+ if (current_fd >= 0) close(current_fd); \
1037
+ if (cache_fd >= 0) close(cache_fd);
1038
+
1039
+ succeed:
1040
+ CLEANUP;
1041
+ return Qtrue;
1042
+ fail:
1043
+ CLEANUP;
1044
+ return Qfalse;
1045
+ #undef CLEANUP
1046
+ }
1047
+
1048
+
722
1049
  /*****************************************************************************/
723
1050
  /********************* Handler Wrappers **************************************/
724
1051
  /*****************************************************************************
@@ -738,11 +1065,13 @@ invalid_type_storage_data:
738
1065
 
739
1066
  struct s2o_data {
740
1067
  VALUE handler;
1068
+ VALUE args;
741
1069
  VALUE storage_data;
742
1070
  };
743
1071
 
744
1072
  struct i2o_data {
745
1073
  VALUE handler;
1074
+ VALUE args;
746
1075
  VALUE input_data;
747
1076
  };
748
1077
 
@@ -753,29 +1082,31 @@ struct i2s_data {
753
1082
  };
754
1083
 
755
1084
  static VALUE
756
- prot_storage_to_output(VALUE arg)
1085
+ try_storage_to_output(VALUE arg)
757
1086
  {
758
1087
  struct s2o_data * data = (struct s2o_data *)arg;
759
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
1088
+ return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
760
1089
  }
761
1090
 
762
1091
  static int
763
- bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
1092
+ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
764
1093
  {
765
1094
  int state;
766
1095
  struct s2o_data s2o_data = {
767
1096
  .handler = handler,
1097
+ .args = args,
768
1098
  .storage_data = storage_data,
769
1099
  };
770
- *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
1100
+ *output_data = rb_protect(try_storage_to_output, (VALUE)&s2o_data, &state);
771
1101
  return state;
772
1102
  }
773
1103
 
774
1104
  static void
775
- bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
1105
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
776
1106
  {
777
1107
  struct i2o_data i2o_data = {
778
1108
  .handler = handler,
1109
+ .args = args,
779
1110
  .input_data = input_data,
780
1111
  };
781
1112
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
@@ -785,7 +1116,7 @@ static VALUE
785
1116
  prot_input_to_output(VALUE arg)
786
1117
  {
787
1118
  struct i2o_data * data = (struct i2o_data *)arg;
788
- return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
1119
+ return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
789
1120
  }
790
1121
 
791
1122
  static VALUE
@@ -795,31 +1126,20 @@ try_input_to_storage(VALUE arg)
795
1126
  return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
796
1127
  }
797
1128
 
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
1129
  static int
815
- bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
1130
+ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
816
1131
  {
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;
1132
+ if (readonly) {
1133
+ *storage_data = rb_cBootsnap_CompileCache_UNCOMPILABLE;
1134
+ return 0;
1135
+ } else {
1136
+ int state;
1137
+ struct i2s_data i2s_data = {
1138
+ .handler = handler,
1139
+ .input_data = input_data,
1140
+ .pathval = pathval,
1141
+ };
1142
+ *storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
1143
+ return state;
1144
+ }
825
1145
  }