bootsnap 1.17.0 → 1.26.0

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.
@@ -11,55 +11,65 @@
11
11
  * here.
12
12
  */
13
13
 
14
- #include "bootsnap.h"
15
14
  #include "ruby.h"
16
15
  #include <stdint.h>
17
16
  #include <stdbool.h>
18
17
  #include <sys/types.h>
19
18
  #include <errno.h>
20
19
  #include <fcntl.h>
20
+ #include <unistd.h>
21
21
  #include <sys/stat.h>
22
+ #include <dirent.h>
23
+
24
+ #ifndef RBIMPL_ATTR_NORETURN
25
+ #define RBIMPL_ATTR_NORETURN()
26
+ #endif
27
+
28
+ #ifdef __APPLE__
29
+ // The symbol is present, however not in the headers
30
+ // See: https://github.com/rails/bootsnap/issues/470
31
+ extern int fdatasync(int);
32
+ #endif
33
+
34
+ #ifndef O_NOATIME
35
+ #define O_NOATIME 0
36
+ #endif
22
37
 
23
38
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
24
39
  * 981 for the cache dir */
25
40
  #define MAX_CACHEPATH_SIZE 1000
26
41
  #define MAX_CACHEDIR_SIZE 981
27
42
 
28
- #define KEY_SIZE 64
43
+ #define KEY_SIZE 32
29
44
 
30
45
  #define MAX_CREATE_TEMPFILE_ATTEMPT 3
31
46
 
32
47
  #ifndef RB_UNLIKELY
33
- #define RB_UNLIKELY(x) (x)
48
+ #define RB_UNLIKELY(x) (x)
34
49
  #endif
35
50
 
36
51
  /*
37
- * An instance of this key is written as the first 64 bytes of each cache file.
52
+ * An instance of this key is written as the first `KEY_SIZE` bytes of each cache file.
38
53
  * The mtime and size members track whether the file contents have changed, and
39
- * the version, ruby_platform, compile_option, and ruby_revision members track
40
- * changes to the environment that could invalidate compile results without
54
+ * the ruby_version_digest (bootsnap_cache_version + RUBY_DESCRIPTION) and compile_option
55
+ * members track changes to the environment that could invalidate compile results without
41
56
  * file contents having changed. The data_size member is not truly part of the
42
57
  * "key". Really, this could be called a "header" with the first six members
43
58
  * being an embedded "key" struct and an additional data_size member.
44
59
  *
45
60
  * The data_size indicates the remaining number of bytes in the cache file
46
61
  * after the header (the size of the cached artifact).
47
- *
48
- * After data_size, the struct is padded to 64 bytes.
49
62
  */
50
63
  struct bs_cache_key {
51
- uint32_t version;
52
- uint32_t ruby_platform;
53
- uint32_t compile_option;
54
- uint32_t ruby_revision;
55
- uint64_t size;
64
+ uint64_t ruby_version_digest;
56
65
  uint64_t mtime;
57
- uint64_t data_size; /* not used for equality */
58
- uint8_t pad[24];
66
+ uint64_t digest;
67
+ uint32_t size;
68
+ uint32_t data_size;
59
69
  } __attribute__((packed));
60
70
 
61
71
  /*
62
- * If the struct padding isn't correct to pad the key to 64 bytes, refuse to
72
+ * If the struct padding isn't correct to pad the key to `KEY_SIZE` bytes, refuse to
63
73
  * compile.
64
74
  */
65
75
  #define STATIC_ASSERT(X) STATIC_ASSERT2(X,__LINE__)
@@ -69,46 +79,57 @@ struct bs_cache_key {
69
79
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
70
80
 
71
81
  /* Effectively a schema version. Bumping invalidates all previous caches */
72
- static const uint32_t current_version = 4;
73
-
74
- /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
75
- * new OS ABI, etc. */
76
- static uint32_t current_ruby_platform;
77
- /* Invalidates cache when switching ruby versions */
78
- static uint32_t current_ruby_revision;
79
- /* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
80
- static uint32_t current_compile_option_crc32 = 0;
82
+ static const uint32_t bootsnap_cache_version = 8;
83
+
84
+ /* Invalidates cache when switching ruby version, platform or ABI */
85
+ static uint64_t base_ruby_version_digest = 0;
86
+
87
+ // `base_ruby_version_digest` combined with RubyVM::InstructionSequence.compile_option
88
+ // Invalidates cache when RubyVM::InstructionSequence.compile_option changes
89
+ static uint64_t current_ruby_version_digest = 0;
90
+
81
91
  /* Current umask */
82
92
  static mode_t current_umask;
83
93
 
84
94
  /* Bootsnap::CompileCache::{Native, Uncompilable} */
85
95
  static VALUE rb_mBootsnap;
86
- static VALUE rb_mBootsnap_CompileCache;
87
96
  static VALUE rb_mBootsnap_CompileCache_Native;
88
97
  static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
89
98
  static ID instrumentation_method;
90
- static VALUE sym_miss;
91
- static VALUE sym_stale;
99
+ static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated;
92
100
  static bool instrumentation_enabled = false;
93
101
  static bool readonly = false;
102
+ static bool revalidation = false;
103
+ static bool perm_issue = false;
104
+
105
+ static ID id_storage_to_output, id_input_to_output, id_input_to_storage;
94
106
 
95
107
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
96
108
  static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
97
109
  static VALUE bs_readonly_set(VALUE self, VALUE enabled);
110
+ static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
98
111
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
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);
112
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args);
113
+ static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler);
101
114
 
102
115
  /* Helpers */
103
- static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
116
+ enum cache_status {
117
+ miss,
118
+ hit,
119
+ stale,
120
+ };
121
+ static void bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE]);
104
122
  static int bs_read_key(int fd, struct bs_cache_key * key);
105
- static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
123
+ static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
124
+ static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
125
+ static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance);
126
+
127
+ static void bs_cache_key_digest(struct bs_cache_key * key, const VALUE input_data);
106
128
  static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
107
129
  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);
130
+ static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
109
131
  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);
110
- static uint32_t get_ruby_revision(void);
111
- static uint32_t get_ruby_platform(void);
132
+ static uint64_t get_ruby_version_digest(void);
112
133
 
113
134
  /*
114
135
  * Helper functions to call ruby methods on handler object without crashing on
@@ -116,26 +137,125 @@ static uint32_t get_ruby_platform(void);
116
137
  */
117
138
  static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
118
139
  static VALUE prot_input_to_output(VALUE arg);
119
- static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
140
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * output_data, int * exception_tag);
120
141
  static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
121
142
  struct s2o_data;
122
143
  struct i2o_data;
123
144
  struct i2s_data;
124
145
 
125
- /* https://bugs.ruby-lang.org/issues/13667 */
126
- extern VALUE rb_get_coverages(void);
127
146
  static VALUE
128
- bs_rb_coverage_running(VALUE self)
147
+ bs_rb_get_path(VALUE self, VALUE fname)
148
+ {
149
+ return rb_get_path(fname);
150
+ }
151
+
152
+ #ifdef HAVE_FSTATAT
153
+
154
+ RBIMPL_ATTR_NORETURN()
155
+ static void
156
+ bs_syserr_fail_path(const char *func_name, int n, VALUE path)
157
+ {
158
+ rb_syserr_fail_str(n, rb_sprintf("%s @ %s", func_name, RSTRING_PTR(path)));
159
+ }
160
+
161
+ RBIMPL_ATTR_NORETURN()
162
+ static void
163
+ bs_syserr_fail_dir_entry(const char *func_name, int n, VALUE dir, const char *d_name)
129
164
  {
130
- VALUE cov = rb_get_coverages();
131
- return RTEST(cov) ? Qtrue : Qfalse;
165
+ rb_syserr_fail_str(n, rb_sprintf("%s @ %s/%s", func_name, RSTRING_PTR(dir), d_name));
132
166
  }
133
167
 
134
168
  static VALUE
135
- bs_rb_get_path(VALUE self, VALUE fname)
169
+ bs_rb_scan_dir(VALUE self, VALUE abspath)
136
170
  {
137
- return rb_get_path(fname);
171
+ Check_Type(abspath, T_STRING);
172
+
173
+ VALUE dirs = rb_ary_new();
174
+ VALUE requirables = rb_ary_new();
175
+ VALUE result = rb_ary_new_from_args(2, requirables, dirs);
176
+
177
+ DIR *dirp = opendir(RSTRING_PTR(abspath));
178
+ if (dirp == NULL) {
179
+ if (errno == ENOTDIR || errno == ENOENT) {
180
+ return result;
181
+ }
182
+
183
+ bs_syserr_fail_path("opendir", errno, abspath);
184
+ return Qundef;
185
+ }
186
+
187
+ struct dirent *entry;
188
+ struct stat st;
189
+ int dfd = -1;
190
+
191
+ while (1) {
192
+ errno = 0;
193
+
194
+ entry = readdir(dirp);
195
+ if (entry == NULL) break;
196
+
197
+ if (entry->d_name[0] == '.') continue;
198
+
199
+ if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
200
+ // Note: the original implementation of LoadPathCache did follow symlink.
201
+ // So this is replicated here, but I'm not sure it's a good idea.
202
+ if (dfd < 0) {
203
+ dfd = dirfd(dirp);
204
+ if (dfd < 0) {
205
+ int err = errno;
206
+ closedir(dirp);
207
+ bs_syserr_fail_path("dirfd", err, abspath);
208
+ return Qundef;
209
+ }
210
+ }
211
+
212
+ if (fstatat(dfd, entry->d_name, &st, 0)) {
213
+ if (errno == ENOENT) continue; // Broken symlink
214
+
215
+ int err = errno;
216
+ closedir(dirp);
217
+ bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
218
+ return Qundef;
219
+ }
220
+
221
+ if (S_ISREG(st.st_mode)) {
222
+ entry->d_type = DT_REG;
223
+ } else if (S_ISDIR(st.st_mode)) {
224
+ entry->d_type = DT_DIR;
225
+ }
226
+ }
227
+
228
+ if (entry->d_type == DT_DIR) {
229
+ rb_ary_push(dirs, rb_utf8_str_new_cstr(entry->d_name));
230
+ continue;
231
+ } else if (entry->d_type == DT_REG) {
232
+ size_t len = strlen(entry->d_name);
233
+ bool is_requirable = (
234
+ // Comparing 4B allows compiler to optimize this into a single 32b integer comparison.
235
+ (len > 3 && memcmp(entry->d_name + (len - 3), ".rb", 4) == 0)
236
+ || (len > DLEXT_MAXLEN && memcmp(entry->d_name + (len - DLEXT_MAXLEN), DLEXT, DLEXT_MAXLEN + 1) == 0)
237
+ #ifdef DLEXT2
238
+ || (len > DLEXT2_MAXLEN && memcmp(entry->d_name + (len - DLEXT2_MAXLEN), DLEXT2, DLEXT2_MAXLEN + 1) == 0)
239
+ #endif
240
+ );
241
+ if (is_requirable) {
242
+ rb_ary_push(requirables, rb_utf8_str_new(entry->d_name, len));
243
+ }
244
+ }
245
+ }
246
+
247
+ if (errno) {
248
+ int err = errno;
249
+ closedir(dirp);
250
+ bs_syserr_fail_path("readdir", err, abspath);
251
+ } else if (closedir(dirp)) {
252
+ bs_syserr_fail_path("closedir", errno, abspath);
253
+ return Qundef;
254
+ }
255
+
256
+ return result;
138
257
  }
258
+ #endif
139
259
 
140
260
  /*
141
261
  * Ruby C extensions are initialized by calling Init_<extname>.
@@ -149,29 +269,38 @@ Init_bootsnap(void)
149
269
  {
150
270
  rb_mBootsnap = rb_define_module("Bootsnap");
151
271
 
272
+ id_storage_to_output = rb_intern("storage_to_output");
273
+ id_input_to_output = rb_intern("input_to_output");
274
+ id_input_to_storage = rb_intern("input_to_storage");
275
+
152
276
  rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
153
277
 
154
- rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
278
+ #ifdef HAVE_FSTATAT
279
+ VALUE rb_mBootsnap_LoadPathCache = rb_define_module_under(rb_mBootsnap, "LoadPathCache");
280
+ VALUE rb_mBootsnap_LoadPathCache_Native = rb_define_module_under(rb_mBootsnap_LoadPathCache, "Native");
281
+
282
+ rb_define_singleton_method(rb_mBootsnap_LoadPathCache_Native, "scan_dir", bs_rb_scan_dir, 1);
283
+ #endif
284
+
285
+ VALUE rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
155
286
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
156
287
  rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
157
288
  rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
158
289
 
159
- current_ruby_revision = get_ruby_revision();
160
- current_ruby_platform = get_ruby_platform();
290
+ base_ruby_version_digest = get_ruby_version_digest();
161
291
 
162
292
  instrumentation_method = rb_intern("_instrument");
163
293
 
294
+ sym_hit = ID2SYM(rb_intern("hit"));
164
295
  sym_miss = ID2SYM(rb_intern("miss"));
165
- rb_global_variable(&sym_miss);
166
-
167
296
  sym_stale = ID2SYM(rb_intern("stale"));
168
- rb_global_variable(&sym_stale);
297
+ sym_revalidated = ID2SYM(rb_intern("revalidated"));
169
298
 
170
299
  rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
171
300
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
172
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
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);
301
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
302
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 5);
303
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 4);
175
304
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
176
305
 
177
306
  current_umask = umask(0777);
@@ -185,6 +314,20 @@ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
185
314
  return enabled;
186
315
  }
187
316
 
317
+ static inline void
318
+ bs_instrumentation(VALUE event, VALUE path)
319
+ {
320
+ if (RB_UNLIKELY(instrumentation_enabled)) {
321
+ // If the funcall raises and rescues an error we will lose the errinfo
322
+ // that exists now so we save it before and restore it after.
323
+ VALUE saved_errinfo = rb_errinfo();
324
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
325
+ // If the ruby code in instrumentation_method raised we won't reach this point.
326
+ // If it didn't raise it _might_ have rescued so we should restore errinfo.
327
+ rb_set_errinfo(saved_errinfo);
328
+ }
329
+ }
330
+
188
331
  static VALUE
189
332
  bs_readonly_set(VALUE self, VALUE enabled)
190
333
  {
@@ -192,28 +335,17 @@ bs_readonly_set(VALUE self, VALUE enabled)
192
335
  return enabled;
193
336
  }
194
337
 
195
- /*
196
- * Bootsnap's ruby code registers a hook that notifies us via this function
197
- * when compile_option changes. These changes invalidate all existing caches.
198
- *
199
- * Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
200
- * can be represented by a uint.
201
- */
202
338
  static VALUE
203
- bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
339
+ bs_revalidation_set(VALUE self, VALUE enabled)
204
340
  {
205
- if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) {
206
- Check_Type(crc32_v, T_FIXNUM);
207
- }
208
- current_compile_option_crc32 = NUM2UINT(crc32_v);
209
- return Qnil;
341
+ revalidation = RTEST(enabled);
342
+ return enabled;
210
343
  }
211
344
 
212
345
  static uint64_t
213
- fnv1a_64_iter(uint64_t h, const VALUE str)
346
+ fnv1a_64_iter(uint64_t h, const unsigned char *s, size_t len)
214
347
  {
215
- unsigned char *s = (unsigned char *)RSTRING_PTR(str);
216
- unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
348
+ const unsigned char *str_end = s + len;
217
349
 
218
350
  while (s < str_end) {
219
351
  h ^= (uint64_t)*s++;
@@ -224,45 +356,43 @@ fnv1a_64_iter(uint64_t h, const VALUE str)
224
356
  }
225
357
 
226
358
  static uint64_t
227
- fnv1a_64(const VALUE str)
359
+ fnv1a_64_iter_str(uint64_t h, const VALUE str)
360
+ {
361
+ Check_Type(str, T_STRING);
362
+ return fnv1a_64_iter(h, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str));
363
+ }
364
+
365
+ static uint64_t
366
+ fnv1a_64_str(const VALUE str)
228
367
  {
229
368
  uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
230
- return fnv1a_64_iter(h, str);
369
+ return fnv1a_64_iter_str(h, str);
231
370
  }
232
371
 
233
372
  /*
234
- * Ruby's revision may be Integer or String. CRuby 2.7 or later uses
235
- * Git commit ID as revision. It's String.
373
+ * Bootsnap's ruby code registers a hook that notifies us via this function
374
+ * when compile_option changes. These changes invalidate all existing caches.
375
+ *
376
+ * Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
377
+ * can be represented by a uint.
236
378
  */
237
- static uint32_t
238
- get_ruby_revision(void)
379
+ static VALUE
380
+ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
239
381
  {
240
- VALUE ruby_revision;
241
-
242
- ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
243
- if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
244
- return FIX2INT(ruby_revision);
245
- } else {
246
- uint64_t hash;
247
-
248
- hash = fnv1a_64(ruby_revision);
249
- return (uint32_t)(hash >> 32);
382
+ if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) {
383
+ Check_Type(crc32_v, T_FIXNUM);
250
384
  }
385
+ uint32_t crc32 = (uint32_t)NUM2UINT(crc32_v);
386
+ current_ruby_version_digest = fnv1a_64_iter(base_ruby_version_digest, (unsigned char *)&crc32, sizeof(crc32));
387
+ return Qnil;
251
388
  }
252
389
 
253
- /*
254
- * When ruby's version doesn't change, but it's recompiled on a different OS
255
- * (or OS version), we need to invalidate the cache.
256
- */
257
- static uint32_t
258
- get_ruby_platform(void)
390
+ static uint64_t
391
+ get_ruby_version_digest(void)
259
392
  {
260
- uint64_t hash;
261
- VALUE ruby_platform;
262
-
263
- ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
264
- hash = fnv1a_64(ruby_platform);
265
- return (uint32_t)(hash >> 32);
393
+ uint64_t hash = fnv1a_64_str(rb_const_get(rb_mBootsnap, rb_intern("RUBY_CACHE_KEY")));
394
+ hash = fnv1a_64_iter(hash, (unsigned char *)&bootsnap_cache_version, sizeof(bootsnap_cache_version));
395
+ return hash;
266
396
  }
267
397
 
268
398
  /*
@@ -271,15 +401,35 @@ get_ruby_platform(void)
271
401
  * be stored.
272
402
  *
273
403
  * The path will look something like: <cachedir>/12/34567890abcdef
404
+ *
405
+ * `path_v` must already have been through FilePathValue(): the callers read it
406
+ * with RSTRING_PTR() too, so the conversion has to happen in the frame that
407
+ * owns the variable.
274
408
  */
275
409
  static void
276
- bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
410
+ bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE])
277
411
  {
278
- uint64_t hash = fnv1a_64(path);
412
+ Check_Type(cachedir_v, T_STRING);
413
+ Check_Type(path_v, T_STRING);
414
+
415
+ long namespace_len = 0;
416
+ if (!NIL_P(namespace_v)) {
417
+ Check_Type(namespace_v, T_STRING);
418
+ namespace_len = RSTRING_LEN(namespace_v);
419
+ }
420
+
421
+ if (RSTRING_LEN(cachedir_v) + namespace_len > MAX_CACHEDIR_SIZE) {
422
+ rb_raise(rb_eArgError, "cachedir too long");
423
+ }
424
+
425
+ const char * cachedir = RSTRING_PTR(cachedir_v);
426
+ const char * namespace = NIL_P(namespace_v) ? "" : RSTRING_PTR(namespace_v);
427
+
428
+ uint64_t hash = fnv1a_64_str(path_v);
279
429
  uint8_t first_byte = (hash >> (64 - 8));
280
430
  uint64_t remainder = hash & 0x00ffffffffffffff;
281
431
 
282
- sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
432
+ sprintf(*cache_path, "%s%s/%02"PRIx8"/%014"PRIx64, cachedir, namespace, first_byte, remainder);
283
433
  }
284
434
 
285
435
  /*
@@ -290,17 +440,79 @@ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_C
290
440
  * The data_size member is not compared, as it serves more of a "header"
291
441
  * function.
292
442
  */
293
- static int
294
- cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
443
+ static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
444
+ struct bs_cache_key *k2) {
445
+ if (k1->ruby_version_digest == k2->ruby_version_digest &&
446
+ k1->size == k2->size) {
447
+ if (k1->mtime == k2->mtime) {
448
+ return hit;
449
+ }
450
+ if (revalidation) {
451
+ return stale;
452
+ }
453
+ }
454
+ return miss;
455
+ }
456
+
457
+ static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
458
+ struct bs_cache_key *cached_key,
459
+ const VALUE input_data)
460
+ {
461
+ bs_cache_key_digest(current_key, input_data);
462
+ return current_key->digest == cached_key->digest;
463
+ }
464
+
465
+ static ssize_t
466
+ resumable_write(int fd, const void *buf, size_t count)
467
+ {
468
+ size_t total_written = 0;
469
+ const char *ptr = buf;
470
+
471
+ while (count > 0) {
472
+ ssize_t res = write(fd, ptr, count);
473
+
474
+ if (res < 0) {
475
+ if (errno == EINTR) {
476
+ continue; // Interrupted by signal, try again
477
+ }
478
+ return -1; // Real error
479
+ }
480
+
481
+ ptr += res;
482
+ count -= res;
483
+ total_written += res;
484
+ }
485
+ return total_written;
486
+ }
487
+
488
+ static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
295
489
  {
296
- return (
297
- k1->version == k2->version &&
298
- k1->ruby_platform == k2->ruby_platform &&
299
- k1->compile_option == k2->compile_option &&
300
- k1->ruby_revision == k2->ruby_revision &&
301
- k1->size == k2->size &&
302
- k1->mtime == k2->mtime
303
- );
490
+ old_key->mtime = current_key->mtime;
491
+ lseek(cache_fd, 0, SEEK_SET);
492
+ ssize_t nwrite = resumable_write(cache_fd, old_key, KEY_SIZE);
493
+ if (nwrite < 0) {
494
+ *errno_provenance = "update_cache_key:write";
495
+ return -1;
496
+ }
497
+
498
+ #ifdef HAVE_FDATASYNC
499
+ if (fdatasync(cache_fd) < 0) {
500
+ *errno_provenance = "update_cache_key:fdatasync";
501
+ return -1;
502
+ }
503
+ #endif
504
+
505
+ return 0;
506
+ }
507
+
508
+ /*
509
+ * Fills the cache key digest.
510
+ */
511
+ static void bs_cache_key_digest(struct bs_cache_key *key,
512
+ const VALUE input_data) {
513
+ if (key->digest)
514
+ return;
515
+ key->digest = fnv1a_64_str(input_data);
304
516
  }
305
517
 
306
518
  /*
@@ -309,25 +521,16 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
309
521
  * conversions on the ruby VALUE arguments before passing them along.
310
522
  */
311
523
  static VALUE
312
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
524
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args)
313
525
  {
314
526
  FilePathValue(path_v);
315
527
 
316
- Check_Type(cachedir_v, T_STRING);
317
- Check_Type(path_v, T_STRING);
318
-
319
- if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
320
- rb_raise(rb_eArgError, "cachedir too long");
321
- }
322
-
323
- char * cachedir = RSTRING_PTR(cachedir_v);
324
- char * path = RSTRING_PTR(path_v);
325
528
  char cache_path[MAX_CACHEPATH_SIZE];
326
529
 
327
530
  /* generate cache path to cache_path */
328
- bs_cache_path(cachedir, path_v, &cache_path);
531
+ bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
329
532
 
330
- return bs_fetch(path, path_v, cache_path, handler, args);
533
+ return bs_fetch(RSTRING_PTR(path_v), path_v, cache_path, handler, args);
331
534
  }
332
535
 
333
536
  /*
@@ -336,37 +539,44 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
336
539
  * and doesn't return the content.
337
540
  */
338
541
  static VALUE
339
- bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
542
+ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler)
340
543
  {
341
544
  FilePathValue(path_v);
342
545
 
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
546
  char cache_path[MAX_CACHEPATH_SIZE];
353
-
354
547
  /* generate cache path to cache_path */
355
- bs_cache_path(cachedir, path_v, &cache_path);
548
+ bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
549
+
550
+ return bs_precompile(RSTRING_PTR(path_v), path_v, cache_path, handler);
551
+ }
552
+
553
+ static int bs_open_noatime(const char *path, int flags) {
554
+ int fd = 1;
555
+ if (!perm_issue) {
556
+ fd = open(path, flags | O_NOATIME);
557
+ if (fd < 0 && errno == EPERM) {
558
+ errno = 0;
559
+ perm_issue = true;
560
+ }
561
+ }
356
562
 
357
- return bs_precompile(path, path_v, cache_path, handler);
563
+ if (perm_issue) {
564
+ fd = open(path, flags);
565
+ }
566
+ return fd;
358
567
  }
568
+
359
569
  /*
360
570
  * Open the file we want to load/cache and generate a cache key for it if it
361
571
  * was loaded.
362
572
  */
363
573
  static int
364
- open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
574
+ open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
365
575
  {
366
576
  struct stat statbuf;
367
577
  int fd;
368
578
 
369
- fd = open(path, O_RDONLY);
579
+ fd = bs_open_noatime(path, O_RDONLY);
370
580
  if (fd < 0) {
371
581
  *errno_provenance = "bs_fetch:open_current_file:open";
372
582
  return fd;
@@ -383,12 +593,19 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
383
593
  return -1;
384
594
  }
385
595
 
386
- key->version = current_version;
387
- key->ruby_platform = current_ruby_platform;
388
- key->compile_option = current_compile_option_crc32;
389
- key->ruby_revision = current_ruby_revision;
390
- key->size = (uint64_t)statbuf.st_size;
391
- key->mtime = (uint64_t)statbuf.st_mtime;
596
+ key->ruby_version_digest = current_ruby_version_digest;
597
+
598
+ // We're limited to file of 4GiB or less. Hopefully that's enough for everyone.
599
+ if (statbuf.st_size > (uint32_t)-1) {
600
+ *errno_provenance = "bs_fetch:open_current_file:file_too_big";
601
+ close(fd);
602
+ errno = EFBIG;
603
+ return -1;
604
+ }
605
+
606
+ key->size = (uint32_t)statbuf.st_size;
607
+ key->mtime = (uint64_t)statbuf.st_mtime;
608
+ key->digest = 0;
392
609
 
393
610
  return fd;
394
611
  }
@@ -432,7 +649,12 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
432
649
  {
433
650
  int fd, res;
434
651
 
435
- fd = open(path, O_RDONLY);
652
+ if (readonly || !revalidation) {
653
+ fd = bs_open_noatime(path, O_RDONLY);
654
+ } else {
655
+ fd = bs_open_noatime(path, O_RDWR);
656
+ }
657
+
436
658
  if (fd < 0) {
437
659
  *errno_provenance = "bs_fetch:open_cache_file:open";
438
660
  return CACHE_MISS;
@@ -453,10 +675,10 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
453
675
 
454
676
  /*
455
677
  * The cache file is laid out like:
456
- * 0...64 : bs_cache_key
457
- * 64..-1 : cached artifact
678
+ * 0...`KEY_SIZE` : bs_cache_key
679
+ * `KEY_SIZE`..-1 : cached artifact
458
680
  *
459
- * This function takes a file descriptor whose position is pre-set to 64, and
681
+ * This function takes a file descriptor whose position is pre-set to `KEY_SIZE`, and
460
682
  * the data_size (corresponding to the remaining number of bytes) listed in the
461
683
  * cache header.
462
684
  *
@@ -532,6 +754,17 @@ mkpath(char * file_path, mode_t mode)
532
754
  return 0;
533
755
  }
534
756
 
757
+ static int
758
+ bs_fchmod(int fd, const char *path, mode_t mode)
759
+ {
760
+ #ifdef _WIN32
761
+ setmode(fd, O_BINARY);
762
+ return chmod(path, mode);
763
+ #else
764
+ return fchmod(fd, mode);
765
+ #endif
766
+ }
767
+
535
768
  /*
536
769
  * Write a cache header/key and a compiled artifact to a given cache path by
537
770
  * writing to a tmpfile and then renaming the tmpfile over top of the final
@@ -545,13 +778,18 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
545
778
  int fd, ret, attempt;
546
779
  ssize_t nwrite;
547
780
 
781
+ uint64_t data_size = RSTRING_LEN(data);
782
+ if (data_size > (uint32_t)-1) {
783
+ return 0; // Don't cache.
784
+ }
785
+
548
786
  for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
549
787
  tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
550
788
  strcat(tmp_path, ".tmp.XXXXXX");
551
789
 
552
790
  // mkstemp modifies the template to be the actual created path
553
791
  fd = mkstemp(tmp_path);
554
- if (fd > 0) break;
792
+ if (fd >= 0) break;
555
793
 
556
794
  if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
557
795
  *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
@@ -563,45 +801,42 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
563
801
  return -1;
564
802
  }
565
803
 
566
- if (chmod(tmp_path, 0644) < 0) {
804
+ if (bs_fchmod(fd, tmp_path, 0644 & ~current_umask) < 0) {
805
+ close(fd);
567
806
  *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
568
807
  return -1;
569
808
  }
570
809
 
571
- #ifdef _WIN32
572
- setmode(fd, O_BINARY);
573
- #endif
574
-
575
- key->data_size = RSTRING_LEN(data);
576
- nwrite = write(fd, key, KEY_SIZE);
810
+ key->data_size = (uint32_t)data_size;
811
+ nwrite = resumable_write(fd, key, KEY_SIZE);
577
812
  if (nwrite < 0) {
813
+ close(fd);
578
814
  *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
579
815
  return -1;
580
816
  }
581
817
  if (nwrite != KEY_SIZE) {
818
+ close(fd);
582
819
  *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
583
820
  errno = EIO; /* Lies but whatever */
584
821
  return -1;
585
822
  }
586
823
 
587
- nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
824
+ nwrite = resumable_write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
588
825
  if (nwrite < 0) return -1;
589
826
  if (nwrite != RSTRING_LEN(data)) {
827
+ close(fd);
590
828
  *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
591
829
  errno = EIO; /* Lies but whatever */
592
830
  return -1;
593
831
  }
594
832
 
595
833
  close(fd);
834
+
596
835
  ret = rename(tmp_path, path);
597
836
  if (ret < 0) {
598
837
  *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
599
838
  return -1;
600
839
  }
601
- ret = chmod(path, 0664 & ~current_umask);
602
- if (ret < 0) {
603
- *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
604
- }
605
840
  return ret;
606
841
  }
607
842
 
@@ -677,7 +912,8 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
677
912
  int res, valid_cache = 0, exception_tag = 0;
678
913
  const char * errno_provenance = NULL;
679
914
 
680
- VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
915
+ VALUE status = Qfalse;
916
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
681
917
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
682
918
  VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
683
919
 
@@ -695,20 +931,44 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
695
931
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
696
932
  if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
697
933
  /* This is ok: valid_cache remains false, we re-populate it. */
698
- if (RB_UNLIKELY(instrumentation_enabled)) {
699
- rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
700
- }
934
+ bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
701
935
  } else if (cache_fd < 0) {
702
936
  exception_message = rb_str_new_cstr(cache_path);
703
937
  goto fail_errno;
704
938
  } else {
705
939
  /* True if the cache existed and no invalidating changes have occurred since
706
940
  * it was generated. */
707
- valid_cache = cache_key_equal(&current_key, &cached_key);
708
- if (RB_UNLIKELY(instrumentation_enabled)) {
709
- if (!valid_cache) {
710
- rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
941
+
942
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
943
+ case hit:
944
+ status = sym_hit;
945
+ valid_cache = true;
946
+ break;
947
+ case miss:
948
+ valid_cache = false;
949
+ break;
950
+ case stale:
951
+ valid_cache = false;
952
+ if ((input_data = bs_read_contents(current_fd, current_key.size,
953
+ &errno_provenance)) == Qfalse) {
954
+ exception_message = path_v;
955
+ goto fail_errno;
711
956
  }
957
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
958
+ if (valid_cache) {
959
+ if (!readonly) {
960
+ if (update_cache_key(&current_key, &cached_key, cache_fd, &errno_provenance)) {
961
+ exception_message = path_v;
962
+ goto fail_errno;
963
+ }
964
+ }
965
+ status = sym_revalidated;
966
+ }
967
+ break;
968
+ };
969
+
970
+ if (!valid_cache) {
971
+ status = sym_stale;
712
972
  }
713
973
  }
714
974
 
@@ -722,11 +982,11 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
722
982
  else if (res == CACHE_UNCOMPILABLE) {
723
983
  /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
724
984
  This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
725
- if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){
985
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
726
986
  exception_message = path_v;
727
987
  goto fail_errno;
728
988
  }
729
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
989
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
730
990
  if (exception_tag != 0) goto raise;
731
991
  goto succeed;
732
992
  } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
@@ -741,7 +1001,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
741
1001
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
742
1002
 
743
1003
  /* Read the contents of the source file into a buffer */
744
- if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){
1004
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
745
1005
  exception_message = path_v;
746
1006
  goto fail_errno;
747
1007
  }
@@ -752,7 +1012,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
752
1012
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
753
1013
  * to cache anything; just return input_to_output(input_data) */
754
1014
  if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
755
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
1015
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
756
1016
  if (exception_tag != 0) goto raise;
757
1017
  goto succeed;
758
1018
  }
@@ -763,6 +1023,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
763
1023
  * We do however ignore any failures to persist the cache, as it's better
764
1024
  * to move along, than to interrupt the process.
765
1025
  */
1026
+ bs_cache_key_digest(&current_key, input_data);
766
1027
  atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
767
1028
 
768
1029
  /* Having written the cache, now convert storage_data to output_data */
@@ -771,7 +1032,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
771
1032
 
772
1033
  if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
773
1034
  /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
774
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
1035
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
775
1036
  if (exception_tag != 0) goto raise;
776
1037
  } else if (NIL_P(output_data)) {
777
1038
  /* If output_data is nil, delete the cache entry and generate the output
@@ -785,7 +1046,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
785
1046
  goto fail_errno;
786
1047
  }
787
1048
  }
788
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
1049
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
789
1050
  if (exception_tag != 0) goto raise;
790
1051
  }
791
1052
 
@@ -793,13 +1054,20 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
793
1054
 
794
1055
  #define CLEANUP \
795
1056
  if (current_fd >= 0) close(current_fd); \
796
- if (cache_fd >= 0) close(cache_fd);
1057
+ if (cache_fd >= 0) close(cache_fd); \
1058
+ if (status != Qfalse) bs_instrumentation(status, path_v);
797
1059
 
798
1060
  succeed:
799
1061
  CLEANUP;
800
1062
  return output_data;
801
1063
  fail_errno:
802
1064
  CLEANUP;
1065
+ if (errno_provenance) {
1066
+ exception_message = rb_str_concat(
1067
+ rb_str_new_cstr(errno_provenance),
1068
+ rb_str_concat(rb_str_new_cstr(": "), exception_message)
1069
+ );
1070
+ }
803
1071
  exception = rb_syserr_new_str(errno, exception_message);
804
1072
  rb_exc_raise(exception);
805
1073
  __builtin_unreachable();
@@ -818,13 +1086,16 @@ invalid_type_storage_data:
818
1086
  static VALUE
819
1087
  bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
820
1088
  {
1089
+ if (readonly) {
1090
+ return Qfalse;
1091
+ }
1092
+
821
1093
  struct bs_cache_key cached_key, current_key;
822
- char * contents = NULL;
823
1094
  int cache_fd = -1, current_fd = -1;
824
1095
  int res, valid_cache = 0, exception_tag = 0;
825
1096
  const char * errno_provenance = NULL;
826
1097
 
827
- VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
1098
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
828
1099
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
829
1100
 
830
1101
  /* Open the source file and generate a cache key for it */
@@ -840,7 +1111,26 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
840
1111
  } else {
841
1112
  /* True if the cache existed and no invalidating changes have occurred since
842
1113
  * it was generated. */
843
- valid_cache = cache_key_equal(&current_key, &cached_key);
1114
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
1115
+ case hit:
1116
+ valid_cache = true;
1117
+ break;
1118
+ case miss:
1119
+ valid_cache = false;
1120
+ break;
1121
+ case stale:
1122
+ valid_cache = false;
1123
+ if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
1124
+ goto fail;
1125
+ }
1126
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
1127
+ if (valid_cache) {
1128
+ if (update_cache_key(&current_key, &cached_key, cache_fd, &errno_provenance)) {
1129
+ goto fail;
1130
+ }
1131
+ }
1132
+ break;
1133
+ };
844
1134
  }
845
1135
 
846
1136
  if (valid_cache) {
@@ -852,7 +1142,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
852
1142
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
853
1143
 
854
1144
  /* Read the contents of the source file into a buffer */
855
- if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
1145
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
856
1146
 
857
1147
  /* Try to compile the input_data using input_to_storage(input_data) */
858
1148
  exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
@@ -867,6 +1157,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
867
1157
  if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
868
1158
 
869
1159
  /* Write the cache key and storage_data to the cache directory */
1160
+ bs_cache_key_digest(&current_key, input_data);
870
1161
  res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
871
1162
  if (res < 0) goto fail;
872
1163
 
@@ -913,6 +1204,7 @@ struct i2o_data {
913
1204
  VALUE handler;
914
1205
  VALUE args;
915
1206
  VALUE input_data;
1207
+ VALUE pathval;
916
1208
  };
917
1209
 
918
1210
  struct i2s_data {
@@ -925,7 +1217,7 @@ static VALUE
925
1217
  try_storage_to_output(VALUE arg)
926
1218
  {
927
1219
  struct s2o_data * data = (struct s2o_data *)arg;
928
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
1220
+ return rb_funcall(data->handler, id_storage_to_output, 2, data->storage_data, data->args);
929
1221
  }
930
1222
 
931
1223
  static int
@@ -942,12 +1234,13 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
942
1234
  }
943
1235
 
944
1236
  static void
945
- bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
1237
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE path_v, VALUE * output_data, int * exception_tag)
946
1238
  {
947
1239
  struct i2o_data i2o_data = {
948
1240
  .handler = handler,
949
1241
  .args = args,
950
1242
  .input_data = input_data,
1243
+ .pathval = path_v,
951
1244
  };
952
1245
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
953
1246
  }
@@ -956,14 +1249,14 @@ static VALUE
956
1249
  prot_input_to_output(VALUE arg)
957
1250
  {
958
1251
  struct i2o_data * data = (struct i2o_data *)arg;
959
- return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
1252
+ return rb_funcall(data->handler, id_input_to_output, 3, data->input_data, data->pathval, data->args);
960
1253
  }
961
1254
 
962
1255
  static VALUE
963
1256
  try_input_to_storage(VALUE arg)
964
1257
  {
965
1258
  struct i2s_data * data = (struct i2s_data *)arg;
966
- return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
1259
+ return rb_funcall(data->handler, id_input_to_storage, 2, data->input_data, data->pathval);
967
1260
  }
968
1261
 
969
1262
  static int