bootsnap 1.4.6 → 1.18.3

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 +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 +487 -171
  7. data/ext/bootsnap/extconf.rb +29 -15
  8. data/lib/bootsnap/bundler.rb +2 -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 +71 -21
  12. data/lib/bootsnap/compile_cache/json.rb +89 -0
  13. data/lib/bootsnap/compile_cache/yaml.rb +315 -41
  14. data/lib/bootsnap/compile_cache.rb +26 -17
  15. data/lib/bootsnap/explicit_require.rb +4 -3
  16. data/lib/bootsnap/load_path_cache/cache.rb +72 -36
  17. data/lib/bootsnap/load_path_cache/change_observer.rb +24 -3
  18. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +26 -82
  19. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
  20. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +38 -27
  21. data/lib/bootsnap/load_path_cache/path.rb +41 -19
  22. data/lib/bootsnap/load_path_cache/path_scanner.rb +60 -29
  23. data/lib/bootsnap/load_path_cache/store.rb +64 -24
  24. data/lib/bootsnap/load_path_cache.rb +40 -38
  25. data/lib/bootsnap/setup.rb +2 -36
  26. data/lib/bootsnap/version.rb +2 -1
  27. data/lib/bootsnap.rb +140 -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 -9
  37. data/README.jp.md +0 -231
  38. data/Rakefile +0 -13
  39. data/bin/ci +0 -10
  40. data/bin/console +0 -15
  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 -46
  45. data/dev.yml +0 -10
  46. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
  47. data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
  48. data/shipit.rubygems.yml +0 -0
@@ -14,15 +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);
23
28
  #endif
24
- #ifdef __GLIBC__
25
- #include <gnu/libc-version.h>
29
+
30
+ #ifndef O_NOATIME
31
+ #define O_NOATIME 0
26
32
  #endif
27
33
 
28
34
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
@@ -32,6 +38,12 @@
32
38
 
33
39
  #define KEY_SIZE 64
34
40
 
41
+ #define MAX_CREATE_TEMPFILE_ATTEMPT 3
42
+
43
+ #ifndef RB_UNLIKELY
44
+ #define RB_UNLIKELY(x) (x)
45
+ #endif
46
+
35
47
  /*
36
48
  * An instance of this key is written as the first 64 bytes of each cache file.
37
49
  * The mtime and size members track whether the file contents have changed, and
@@ -53,8 +65,10 @@ struct bs_cache_key {
53
65
  uint32_t ruby_revision;
54
66
  uint64_t size;
55
67
  uint64_t mtime;
56
- uint64_t data_size; /* not used for equality */
57
- uint8_t pad[24];
68
+ uint64_t data_size; //
69
+ uint64_t digest;
70
+ uint8_t digest_set;
71
+ uint8_t pad[15];
58
72
  } __attribute__((packed));
59
73
 
60
74
  /*
@@ -68,7 +82,7 @@ struct bs_cache_key {
68
82
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
69
83
 
70
84
  /* Effectively a schema version. Bumping invalidates all previous caches */
71
- static const uint32_t current_version = 2;
85
+ static const uint32_t current_version = 5;
72
86
 
73
87
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
74
88
  * new OS ABI, etc. */
@@ -84,21 +98,39 @@ static mode_t current_umask;
84
98
  static VALUE rb_mBootsnap;
85
99
  static VALUE rb_mBootsnap_CompileCache;
86
100
  static VALUE rb_mBootsnap_CompileCache_Native;
87
- static VALUE rb_eBootsnap_CompileCache_Uncompilable;
88
- 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;
89
108
 
90
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);
91
113
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
92
- 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);
93
116
 
94
117
  /* Helpers */
95
- static uint64_t fnv1a_64(const char *str);
96
- static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
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]);
97
124
  static int bs_read_key(int fd, struct bs_cache_key * key);
98
- static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
99
- static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
100
- static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
101
- static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const 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);
102
134
  static uint32_t get_ruby_revision(void);
103
135
  static uint32_t get_ruby_platform(void);
104
136
 
@@ -106,12 +138,10 @@ static uint32_t get_ruby_platform(void);
106
138
  * Helper functions to call ruby methods on handler object without crashing on
107
139
  * exception.
108
140
  */
109
- static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
110
- 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);
111
142
  static VALUE prot_input_to_output(VALUE arg);
112
- static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
113
- static VALUE prot_input_to_storage(VALUE arg);
114
- 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);
115
145
  struct s2o_data;
116
146
  struct i2o_data;
117
147
  struct i2s_data;
@@ -125,6 +155,12 @@ bs_rb_coverage_running(VALUE self)
125
155
  return RTEST(cov) ? Qtrue : Qfalse;
126
156
  }
127
157
 
158
+ static VALUE
159
+ bs_rb_get_path(VALUE self, VALUE fname)
160
+ {
161
+ return rb_get_path(fname);
162
+ }
163
+
128
164
  /*
129
165
  * Ruby C extensions are initialized by calling Init_<extname>.
130
166
  *
@@ -136,23 +172,65 @@ void
136
172
  Init_bootsnap(void)
137
173
  {
138
174
  rb_mBootsnap = rb_define_module("Bootsnap");
175
+
176
+ rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
177
+
139
178
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
140
179
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
141
- 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);
142
182
 
143
183
  current_ruby_revision = get_ruby_revision();
144
184
  current_ruby_platform = get_ruby_platform();
145
185
 
146
- uncompilable = rb_intern("__bootsnap_uncompilable__");
186
+ instrumentation_method = rb_intern("_instrument");
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"));
147
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);
148
196
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
149
- 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);
150
199
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
151
200
 
152
201
  current_umask = umask(0777);
153
202
  umask(current_umask);
154
203
  }
155
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
+
156
234
  /*
157
235
  * Bootsnap's ruby code registers a hook that notifies us via this function
158
236
  * when compile_option changes. These changes invalidate all existing caches.
@@ -170,22 +248,13 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
170
248
  return Qnil;
171
249
  }
172
250
 
173
- /*
174
- * We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
175
- * it has several nice properties:
176
- *
177
- * - Tiny implementation
178
- * - No external dependency
179
- * - Solid performance
180
- * - Solid randomness
181
- * - 32 bits doesn't feel collision-resistant enough; 64 is nice.
182
- */
183
251
  static uint64_t
184
- fnv1a_64_iter(uint64_t h, const char *str)
252
+ fnv1a_64_iter(uint64_t h, const VALUE str)
185
253
  {
186
- 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);
187
256
 
188
- while (*s) {
257
+ while (s < str_end) {
189
258
  h ^= (uint64_t)*s++;
190
259
  h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
191
260
  }
@@ -194,7 +263,7 @@ fnv1a_64_iter(uint64_t h, const char *str)
194
263
  }
195
264
 
196
265
  static uint64_t
197
- fnv1a_64(const char *str)
266
+ fnv1a_64(const VALUE str)
198
267
  {
199
268
  uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
200
269
  return fnv1a_64_iter(h, str);
@@ -215,7 +284,7 @@ get_ruby_revision(void)
215
284
  } else {
216
285
  uint64_t hash;
217
286
 
218
- hash = fnv1a_64(StringValueCStr(ruby_revision));
287
+ hash = fnv1a_64(ruby_revision);
219
288
  return (uint32_t)(hash >> 32);
220
289
  }
221
290
  }
@@ -223,10 +292,6 @@ get_ruby_revision(void)
223
292
  /*
224
293
  * When ruby's version doesn't change, but it's recompiled on a different OS
225
294
  * (or OS version), we need to invalidate the cache.
226
- *
227
- * We actually factor in some extra information here, to be extra confident
228
- * that we don't try to re-use caches that will not be compatible, by factoring
229
- * in utsname.version.
230
295
  */
231
296
  static uint32_t
232
297
  get_ruby_platform(void)
@@ -235,23 +300,8 @@ get_ruby_platform(void)
235
300
  VALUE ruby_platform;
236
301
 
237
302
  ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
238
- hash = fnv1a_64(RSTRING_PTR(ruby_platform));
239
-
240
- #ifdef _WIN32
241
- return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
242
- #elif defined(__GLIBC__)
243
- hash = fnv1a_64_iter(hash, gnu_get_libc_version());
244
- return (uint32_t)(hash >> 32);
245
- #else
246
- struct utsname utsname;
247
-
248
- /* Not worth crashing if this fails; lose extra cache invalidation potential */
249
- if (uname(&utsname) >= 0) {
250
- hash = fnv1a_64_iter(hash, utsname.version);
251
- }
252
-
303
+ hash = fnv1a_64(ruby_platform);
253
304
  return (uint32_t)(hash >> 32);
254
- #endif
255
305
  }
256
306
 
257
307
  /*
@@ -262,14 +312,13 @@ get_ruby_platform(void)
262
312
  * The path will look something like: <cachedir>/12/34567890abcdef
263
313
  */
264
314
  static void
265
- bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
315
+ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
266
316
  {
267
317
  uint64_t hash = fnv1a_64(path);
268
-
269
318
  uint8_t first_byte = (hash >> (64 - 8));
270
319
  uint64_t remainder = hash & 0x00ffffffffffffff;
271
320
 
272
- sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
321
+ sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
273
322
  }
274
323
 
275
324
  /*
@@ -280,17 +329,59 @@ bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_
280
329
  * The data_size member is not compared, as it serves more of a "header"
281
330
  * function.
282
331
  */
283
- static int
284
- 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)
285
351
  {
286
- return (
287
- k1->version == k2->version &&
288
- k1->ruby_platform == k2->ruby_platform &&
289
- k1->compile_option == k2->compile_option &&
290
- k1->ruby_revision == k2->ruby_revision &&
291
- k1->size == k2->size &&
292
- k1->mtime == k2->mtime
293
- );
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;
294
385
  }
295
386
 
296
387
  /*
@@ -299,7 +390,34 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
299
390
  * conversions on the ruby VALUE arguments before passing them along.
300
391
  */
301
392
  static VALUE
302
- 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)
394
+ {
395
+ FilePathValue(path_v);
396
+
397
+ Check_Type(cachedir_v, T_STRING);
398
+ Check_Type(path_v, T_STRING);
399
+
400
+ if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
401
+ rb_raise(rb_eArgError, "cachedir too long");
402
+ }
403
+
404
+ char * cachedir = RSTRING_PTR(cachedir_v);
405
+ char * path = RSTRING_PTR(path_v);
406
+ char cache_path[MAX_CACHEPATH_SIZE];
407
+
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)
303
421
  {
304
422
  FilePathValue(path_v);
305
423
 
@@ -315,9 +433,25 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
315
433
  char cache_path[MAX_CACHEPATH_SIZE];
316
434
 
317
435
  /* generate cache path to cache_path */
318
- bs_cache_path(cachedir, path, &cache_path);
436
+ bs_cache_path(cachedir, path_v, &cache_path);
319
437
 
320
- return bs_fetch(path, path_v, cache_path, handler);
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
+ }
449
+ }
450
+
451
+ if (perm_issue) {
452
+ fd = open(path, flags);
453
+ }
454
+ return fd;
321
455
  }
322
456
 
323
457
  /*
@@ -325,12 +459,12 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
325
459
  * was loaded.
326
460
  */
327
461
  static int
328
- open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
462
+ open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
329
463
  {
330
464
  struct stat statbuf;
331
465
  int fd;
332
466
 
333
- fd = open(path, O_RDONLY);
467
+ fd = bs_open_noatime(path, O_RDONLY);
334
468
  if (fd < 0) {
335
469
  *errno_provenance = "bs_fetch:open_current_file:open";
336
470
  return fd;
@@ -341,7 +475,9 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
341
475
 
342
476
  if (fstat(fd, &statbuf) < 0) {
343
477
  *errno_provenance = "bs_fetch:open_current_file:fstat";
478
+ int previous_errno = errno;
344
479
  close(fd);
480
+ errno = previous_errno;
345
481
  return -1;
346
482
  }
347
483
 
@@ -351,12 +487,15 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
351
487
  key->ruby_revision = current_ruby_revision;
352
488
  key->size = (uint64_t)statbuf.st_size;
353
489
  key->mtime = (uint64_t)statbuf.st_mtime;
490
+ key->digest_set = false;
354
491
 
355
492
  return fd;
356
493
  }
357
494
 
358
495
  #define ERROR_WITH_ERRNO -1
359
- #define CACHE_MISSING_OR_INVALID -2
496
+ #define CACHE_MISS -2
497
+ #define CACHE_STALE -3
498
+ #define CACHE_UNCOMPILABLE -4
360
499
 
361
500
  /*
362
501
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -364,15 +503,16 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
364
503
  *
365
504
  * Possible return values:
366
505
  * - 0 (OK, key was loaded)
367
- * - CACHE_MISSING_OR_INVALID (-2)
368
506
  * - ERROR_WITH_ERRNO (-1, errno is set)
507
+ * - CACHE_MISS (-2)
508
+ * - CACHE_STALE (-3)
369
509
  */
370
510
  static int
371
511
  bs_read_key(int fd, struct bs_cache_key * key)
372
512
  {
373
513
  ssize_t nread = read(fd, key, KEY_SIZE);
374
514
  if (nread < 0) return ERROR_WITH_ERRNO;
375
- if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID;
515
+ if (nread < KEY_SIZE) return CACHE_STALE;
376
516
  return 0;
377
517
  }
378
518
 
@@ -382,7 +522,8 @@ bs_read_key(int fd, struct bs_cache_key * key)
382
522
  *
383
523
  * Possible return values:
384
524
  * - 0 (OK, key was loaded)
385
- * - CACHE_MISSING_OR_INVALID (-2)
525
+ * - CACHE_MISS (-2)
526
+ * - CACHE_STALE (-3)
386
527
  * - ERROR_WITH_ERRNO (-1, errno is set)
387
528
  */
388
529
  static int
@@ -390,11 +531,15 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
390
531
  {
391
532
  int fd, res;
392
533
 
393
- 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
+
394
540
  if (fd < 0) {
395
541
  *errno_provenance = "bs_fetch:open_cache_file:open";
396
- if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
397
- return ERROR_WITH_ERRNO;
542
+ return CACHE_MISS;
398
543
  }
399
544
  #ifdef _WIN32
400
545
  setmode(fd, O_BINARY);
@@ -426,9 +571,8 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
426
571
  * or exception, will be the final data returnable to the user.
427
572
  */
428
573
  static int
429
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const 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)
430
575
  {
431
- char * data = NULL;
432
576
  ssize_t nread;
433
577
  int ret;
434
578
 
@@ -437,27 +581,30 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
437
581
  if (data_size > 100000000000) {
438
582
  *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
439
583
  errno = EINVAL; /* because wtf? */
440
- ret = -1;
584
+ ret = ERROR_WITH_ERRNO;
441
585
  goto done;
442
586
  }
443
- data = ALLOC_N(char, data_size);
444
- 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);
445
589
  if (nread < 0) {
446
590
  *errno_provenance = "bs_fetch:fetch_cached_data:read";
447
- ret = -1;
591
+ ret = ERROR_WITH_ERRNO;
448
592
  goto done;
449
593
  }
450
594
  if (nread != data_size) {
451
- ret = CACHE_MISSING_OR_INVALID;
595
+ ret = CACHE_STALE;
452
596
  goto done;
453
597
  }
454
598
 
455
- storage_data = rb_str_new_static(data, data_size);
599
+ rb_str_set_len(storage_data, nread);
456
600
 
457
- *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
+ }
458
606
  ret = 0;
459
607
  done:
460
- if (data != NULL) xfree(data);
461
608
  return ret;
462
609
  }
463
610
 
@@ -499,25 +646,32 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
499
646
  {
500
647
  char template[MAX_CACHEPATH_SIZE + 20];
501
648
  char * tmp_path;
502
- int fd, ret;
649
+ int fd, ret, attempt;
503
650
  ssize_t nwrite;
504
651
 
505
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
506
- 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");
507
655
 
508
- // mkstemp modifies the template to be the actual created path
509
- fd = mkstemp(tmp_path);
510
- if (fd < 0) {
511
- if (mkpath(tmp_path, 0775) < 0) {
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) {
512
661
  *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
513
662
  return -1;
514
663
  }
515
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
516
- if (fd < 0) {
517
- *errno_provenance = "bs_fetch:atomic_write_cache_file:open";
518
- return -1;
519
- }
520
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
+
521
675
  #ifdef _WIN32
522
676
  setmode(fd, O_BINARY);
523
677
  #endif
@@ -557,17 +711,22 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
557
711
 
558
712
 
559
713
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
560
- * long, into a buffer */
561
- static ssize_t
562
- bs_read_contents(int fd, size_t size, char ** contents, const 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)
563
717
  {
718
+ VALUE contents;
564
719
  ssize_t nread;
565
- *contents = ALLOC_N(char, size);
566
- nread = read(fd, *contents, size);
720
+ contents = rb_str_buf_new(size);
721
+ nread = read(fd, RSTRING_PTR(contents), size);
722
+
567
723
  if (nread < 0) {
568
724
  *errno_provenance = "bs_fetch:bs_read_contents:read";
725
+ return Qfalse;
726
+ } else {
727
+ rb_str_set_len(contents, nread);
728
+ return contents;
569
729
  }
570
- return nread;
571
730
  }
572
731
 
573
732
  /*
@@ -615,91 +774,155 @@ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_prov
615
774
  * - Return storage_to_output(storage_data)
616
775
  */
617
776
  static VALUE
618
- 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)
619
778
  {
620
779
  struct bs_cache_key cached_key, current_key;
621
- char * contents = NULL;
622
780
  int cache_fd = -1, current_fd = -1;
623
781
  int res, valid_cache = 0, exception_tag = 0;
624
782
  const char * errno_provenance = NULL;
625
783
 
626
- 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 */
627
786
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
628
787
  VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
629
788
 
630
789
  VALUE exception; /* ruby exception object to raise instead of returning */
790
+ VALUE exception_message; /* ruby exception string to use instead of errno_provenance */
631
791
 
632
792
  /* Open the source file and generate a cache key for it */
633
793
  current_fd = open_current_file(path, &current_key, &errno_provenance);
634
- if (current_fd < 0) goto fail_errno;
794
+ if (current_fd < 0) {
795
+ exception_message = path_v;
796
+ goto fail_errno;
797
+ }
635
798
 
636
799
  /* Open the cache key if it exists, and read its cache key in */
637
800
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
638
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
801
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
639
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);
640
804
  } else if (cache_fd < 0) {
805
+ exception_message = rb_str_new_cstr(cache_path);
641
806
  goto fail_errno;
642
807
  } else {
643
808
  /* True if the cache existed and no invalidating changes have occurred since
644
809
  * it was generated. */
645
- 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
+ }
646
842
  }
647
843
 
648
844
  if (valid_cache) {
649
845
  /* Fetch the cache data and return it if we're able to load it successfully */
650
846
  res = fetch_cached_data(
651
- cache_fd, (ssize_t)cached_key.data_size, handler,
847
+ cache_fd, (ssize_t)cached_key.data_size, handler, args,
652
848
  &output_data, &exception_tag, &errno_provenance
653
849
  );
654
- if (exception_tag != 0) goto raise;
655
- else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
656
- else if (res == ERROR_WITH_ERRNO) goto fail_errno;
657
- 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 */
658
867
  }
659
868
  close(cache_fd);
660
869
  cache_fd = -1;
661
870
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
662
871
 
663
872
  /* Read the contents of the source file into a buffer */
664
- if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
665
- 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
+ }
666
877
 
667
878
  /* Try to compile the input_data using input_to_storage(input_data) */
668
- 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);
669
880
  if (exception_tag != 0) goto raise;
670
881
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
671
882
  * to cache anything; just return input_to_output(input_data) */
672
- if (storage_data == uncompilable) {
673
- 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);
674
885
  if (exception_tag != 0) goto raise;
675
886
  goto succeed;
676
887
  }
677
888
  /* If storage_data isn't a string, we can't cache it */
678
889
  if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
679
890
 
680
- /* Write the cache key and storage_data to the cache directory */
681
- res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
682
- 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);
683
897
 
684
898
  /* Having written the cache, now convert storage_data to output_data */
685
- exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
899
+ exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
686
900
  if (exception_tag != 0) goto raise;
687
901
 
688
- /* If output_data is nil, delete the cache entry and generate the output
689
- * using input_to_output */
690
- 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 */
691
909
  if (unlink(cache_path) < 0) {
692
- errno_provenance = "bs_fetch:unlink";
693
- 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
+ }
694
917
  }
695
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
918
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
696
919
  if (exception_tag != 0) goto raise;
697
920
  }
698
921
 
699
922
  goto succeed; /* output_data is now the correct return. */
700
923
 
701
924
  #define CLEANUP \
702
- if (contents != NULL) xfree(contents); \
925
+ if (status != Qfalse) bs_instrumentation(status, path_v); \
703
926
  if (current_fd >= 0) close(current_fd); \
704
927
  if (cache_fd >= 0) close(cache_fd);
705
928
 
@@ -708,7 +931,13 @@ succeed:
708
931
  return output_data;
709
932
  fail_errno:
710
933
  CLEANUP;
711
- 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);
712
941
  rb_exc_raise(exception);
713
942
  __builtin_unreachable();
714
943
  raise:
@@ -723,6 +952,100 @@ invalid_type_storage_data:
723
952
  #undef CLEANUP
724
953
  }
725
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
+
726
1049
  /*****************************************************************************/
727
1050
  /********************* Handler Wrappers **************************************/
728
1051
  /*****************************************************************************
@@ -742,11 +1065,13 @@ invalid_type_storage_data:
742
1065
 
743
1066
  struct s2o_data {
744
1067
  VALUE handler;
1068
+ VALUE args;
745
1069
  VALUE storage_data;
746
1070
  };
747
1071
 
748
1072
  struct i2o_data {
749
1073
  VALUE handler;
1074
+ VALUE args;
750
1075
  VALUE input_data;
751
1076
  };
752
1077
 
@@ -757,29 +1082,31 @@ struct i2s_data {
757
1082
  };
758
1083
 
759
1084
  static VALUE
760
- prot_storage_to_output(VALUE arg)
1085
+ try_storage_to_output(VALUE arg)
761
1086
  {
762
1087
  struct s2o_data * data = (struct s2o_data *)arg;
763
- 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);
764
1089
  }
765
1090
 
766
1091
  static int
767
- 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)
768
1093
  {
769
1094
  int state;
770
1095
  struct s2o_data s2o_data = {
771
1096
  .handler = handler,
1097
+ .args = args,
772
1098
  .storage_data = storage_data,
773
1099
  };
774
- *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);
775
1101
  return state;
776
1102
  }
777
1103
 
778
1104
  static void
779
- 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)
780
1106
  {
781
1107
  struct i2o_data i2o_data = {
782
1108
  .handler = handler,
1109
+ .args = args,
783
1110
  .input_data = input_data,
784
1111
  };
785
1112
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
@@ -789,7 +1116,7 @@ static VALUE
789
1116
  prot_input_to_output(VALUE arg)
790
1117
  {
791
1118
  struct i2o_data * data = (struct i2o_data *)arg;
792
- 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);
793
1120
  }
794
1121
 
795
1122
  static VALUE
@@ -799,31 +1126,20 @@ try_input_to_storage(VALUE arg)
799
1126
  return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
800
1127
  }
801
1128
 
802
- static VALUE
803
- rescue_input_to_storage(VALUE arg)
804
- {
805
- return uncompilable;
806
- }
807
-
808
- static VALUE
809
- prot_input_to_storage(VALUE arg)
810
- {
811
- struct i2s_data * data = (struct i2s_data *)arg;
812
- return rb_rescue2(
813
- try_input_to_storage, (VALUE)data,
814
- rescue_input_to_storage, Qnil,
815
- rb_eBootsnap_CompileCache_Uncompilable, 0);
816
- }
817
-
818
1129
  static int
819
- 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)
820
1131
  {
821
- int state;
822
- struct i2s_data i2s_data = {
823
- .handler = handler,
824
- .input_data = input_data,
825
- .pathval = pathval,
826
- };
827
- *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
828
- 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
+ }
829
1145
  }