bootsnap 1.4.0 → 1.10.3

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