bootsnap 1.12.0 → 1.18.4

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.
@@ -18,8 +18,19 @@
18
18
  #include <sys/types.h>
19
19
  #include <errno.h>
20
20
  #include <fcntl.h>
21
+ #include <unistd.h>
21
22
  #include <sys/stat.h>
22
23
 
24
+ #ifdef __APPLE__
25
+ // The symbol is present, however not in the headers
26
+ // See: https://github.com/Shopify/bootsnap/issues/470
27
+ extern int fdatasync(int);
28
+ #endif
29
+
30
+ #ifndef O_NOATIME
31
+ #define O_NOATIME 0
32
+ #endif
33
+
23
34
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
24
35
  * 981 for the cache dir */
25
36
  #define MAX_CACHEPATH_SIZE 1000
@@ -30,7 +41,7 @@
30
41
  #define MAX_CREATE_TEMPFILE_ATTEMPT 3
31
42
 
32
43
  #ifndef RB_UNLIKELY
33
- #define RB_UNLIKELY(x) (x)
44
+ #define RB_UNLIKELY(x) (x)
34
45
  #endif
35
46
 
36
47
  /*
@@ -54,8 +65,10 @@ struct bs_cache_key {
54
65
  uint32_t ruby_revision;
55
66
  uint64_t size;
56
67
  uint64_t mtime;
57
- uint64_t data_size; /* not used for equality */
58
- uint8_t pad[24];
68
+ uint64_t data_size; //
69
+ uint64_t digest;
70
+ uint8_t digest_set;
71
+ uint8_t pad[15];
59
72
  } __attribute__((packed));
60
73
 
61
74
  /*
@@ -69,7 +82,7 @@ struct bs_cache_key {
69
82
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
70
83
 
71
84
  /* Effectively a schema version. Bumping invalidates all previous caches */
72
- static const uint32_t current_version = 4;
85
+ static const uint32_t current_version = 6;
73
86
 
74
87
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
75
88
  * new OS ABI, etc. */
@@ -87,23 +100,36 @@ static VALUE rb_mBootsnap_CompileCache;
87
100
  static VALUE rb_mBootsnap_CompileCache_Native;
88
101
  static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
89
102
  static ID instrumentation_method;
90
- static VALUE sym_miss;
91
- static VALUE sym_stale;
103
+ static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated;
92
104
  static bool instrumentation_enabled = false;
105
+ static bool readonly = false;
106
+ static bool revalidation = false;
107
+ static bool perm_issue = false;
93
108
 
94
109
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
95
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);
96
113
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
97
114
  static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
98
115
  static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
99
116
 
100
117
  /* Helpers */
118
+ enum cache_status {
119
+ miss,
120
+ hit,
121
+ stale,
122
+ };
101
123
  static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
102
124
  static int bs_read_key(int fd, struct bs_cache_key * key);
103
- static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
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);
104
130
  static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
105
131
  static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
106
- static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
132
+ static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
107
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);
108
134
  static uint32_t get_ruby_revision(void);
109
135
  static uint32_t get_ruby_platform(void);
@@ -120,15 +146,6 @@ struct s2o_data;
120
146
  struct i2o_data;
121
147
  struct i2s_data;
122
148
 
123
- /* https://bugs.ruby-lang.org/issues/13667 */
124
- extern VALUE rb_get_coverages(void);
125
- static VALUE
126
- bs_rb_coverage_running(VALUE self)
127
- {
128
- VALUE cov = rb_get_coverages();
129
- return RTEST(cov) ? Qtrue : Qfalse;
130
- }
131
-
132
149
  static VALUE
133
150
  bs_rb_get_path(VALUE self, VALUE fname)
134
151
  {
@@ -159,14 +176,14 @@ Init_bootsnap(void)
159
176
 
160
177
  instrumentation_method = rb_intern("_instrument");
161
178
 
179
+ sym_hit = ID2SYM(rb_intern("hit"));
162
180
  sym_miss = ID2SYM(rb_intern("miss"));
163
- rb_global_variable(&sym_miss);
164
-
165
181
  sym_stale = ID2SYM(rb_intern("stale"));
166
- rb_global_variable(&sym_stale);
182
+ sym_revalidated = ID2SYM(rb_intern("revalidated"));
167
183
 
168
184
  rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
169
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
185
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
186
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
170
187
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
171
188
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
172
189
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
@@ -182,6 +199,28 @@ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
182
199
  return enabled;
183
200
  }
184
201
 
202
+ static inline void
203
+ bs_instrumentation(VALUE event, VALUE path)
204
+ {
205
+ if (RB_UNLIKELY(instrumentation_enabled)) {
206
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
207
+ }
208
+ }
209
+
210
+ static VALUE
211
+ bs_readonly_set(VALUE self, VALUE enabled)
212
+ {
213
+ readonly = RTEST(enabled);
214
+ return enabled;
215
+ }
216
+
217
+ static VALUE
218
+ bs_revalidation_set(VALUE self, VALUE enabled)
219
+ {
220
+ revalidation = RTEST(enabled);
221
+ return enabled;
222
+ }
223
+
185
224
  /*
186
225
  * Bootsnap's ruby code registers a hook that notifies us via this function
187
226
  * when compile_option changes. These changes invalidate all existing caches.
@@ -280,17 +319,59 @@ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_C
280
319
  * The data_size member is not compared, as it serves more of a "header"
281
320
  * function.
282
321
  */
283
- static int
284
- cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
322
+ static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
323
+ struct bs_cache_key *k2) {
324
+ if (k1->version == k2->version &&
325
+ k1->ruby_platform == k2->ruby_platform &&
326
+ k1->compile_option == k2->compile_option &&
327
+ k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) {
328
+ if (k1->mtime == k2->mtime) {
329
+ return hit;
330
+ }
331
+ if (revalidation) {
332
+ return stale;
333
+ }
334
+ }
335
+ return miss;
336
+ }
337
+
338
+ static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
339
+ struct bs_cache_key *cached_key,
340
+ const VALUE input_data)
341
+ {
342
+ bs_cache_key_digest(current_key, input_data);
343
+ return current_key->digest == cached_key->digest;
344
+ }
345
+
346
+ static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
285
347
  {
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
- );
348
+ old_key->mtime = current_key->mtime;
349
+ lseek(cache_fd, 0, SEEK_SET);
350
+ ssize_t nwrite = write(cache_fd, old_key, KEY_SIZE);
351
+ if (nwrite < 0) {
352
+ *errno_provenance = "update_cache_key:write";
353
+ return -1;
354
+ }
355
+
356
+ #ifdef HAVE_FDATASYNC
357
+ if (fdatasync(cache_fd) < 0) {
358
+ *errno_provenance = "update_cache_key:fdatasync";
359
+ return -1;
360
+ }
361
+ #endif
362
+
363
+ return 0;
364
+ }
365
+
366
+ /*
367
+ * Fills the cache key digest.
368
+ */
369
+ static void bs_cache_key_digest(struct bs_cache_key *key,
370
+ const VALUE input_data) {
371
+ if (key->digest_set)
372
+ return;
373
+ key->digest = fnv1a_64(input_data);
374
+ key->digest_set = 1;
294
375
  }
295
376
 
296
377
  /*
@@ -346,17 +427,34 @@ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
346
427
 
347
428
  return bs_precompile(path, path_v, cache_path, handler);
348
429
  }
430
+
431
+ static int bs_open_noatime(const char *path, int flags) {
432
+ int fd = 1;
433
+ if (!perm_issue) {
434
+ fd = open(path, flags | O_NOATIME);
435
+ if (fd < 0 && errno == EPERM) {
436
+ errno = 0;
437
+ perm_issue = true;
438
+ }
439
+ }
440
+
441
+ if (perm_issue) {
442
+ fd = open(path, flags);
443
+ }
444
+ return fd;
445
+ }
446
+
349
447
  /*
350
448
  * Open the file we want to load/cache and generate a cache key for it if it
351
449
  * was loaded.
352
450
  */
353
451
  static int
354
- open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
452
+ open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
355
453
  {
356
454
  struct stat statbuf;
357
455
  int fd;
358
456
 
359
- fd = open(path, O_RDONLY);
457
+ fd = bs_open_noatime(path, O_RDONLY);
360
458
  if (fd < 0) {
361
459
  *errno_provenance = "bs_fetch:open_current_file:open";
362
460
  return fd;
@@ -367,7 +465,9 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
367
465
 
368
466
  if (fstat(fd, &statbuf) < 0) {
369
467
  *errno_provenance = "bs_fetch:open_current_file:fstat";
468
+ int previous_errno = errno;
370
469
  close(fd);
470
+ errno = previous_errno;
371
471
  return -1;
372
472
  }
373
473
 
@@ -377,6 +477,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
377
477
  key->ruby_revision = current_ruby_revision;
378
478
  key->size = (uint64_t)statbuf.st_size;
379
479
  key->mtime = (uint64_t)statbuf.st_mtime;
480
+ key->digest_set = false;
380
481
 
381
482
  return fd;
382
483
  }
@@ -420,7 +521,12 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
420
521
  {
421
522
  int fd, res;
422
523
 
423
- fd = open(path, O_RDONLY);
524
+ if (readonly || !revalidation) {
525
+ fd = bs_open_noatime(path, O_RDONLY);
526
+ } else {
527
+ fd = bs_open_noatime(path, O_RDWR);
528
+ }
529
+
424
530
  if (fd < 0) {
425
531
  *errno_provenance = "bs_fetch:open_cache_file:open";
426
532
  return CACHE_MISS;
@@ -457,7 +563,6 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
457
563
  static int
458
564
  fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
459
565
  {
460
- char * data = NULL;
461
566
  ssize_t nread;
462
567
  int ret;
463
568
 
@@ -469,8 +574,8 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
469
574
  ret = ERROR_WITH_ERRNO;
470
575
  goto done;
471
576
  }
472
- data = ALLOC_N(char, data_size);
473
- nread = read(fd, data, data_size);
577
+ storage_data = rb_str_buf_new(data_size);
578
+ nread = read(fd, RSTRING_PTR(storage_data), data_size);
474
579
  if (nread < 0) {
475
580
  *errno_provenance = "bs_fetch:fetch_cached_data:read";
476
581
  ret = ERROR_WITH_ERRNO;
@@ -481,7 +586,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
481
586
  goto done;
482
587
  }
483
588
 
484
- storage_data = rb_str_new(data, data_size);
589
+ rb_str_set_len(storage_data, nread);
485
590
 
486
591
  *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
487
592
  if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
@@ -490,7 +595,6 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
490
595
  }
491
596
  ret = 0;
492
597
  done:
493
- if (data != NULL) xfree(data);
494
598
  return ret;
495
599
  }
496
600
 
@@ -597,17 +701,22 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
597
701
 
598
702
 
599
703
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
600
- * long, into a buffer */
601
- static ssize_t
602
- bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
704
+ * long, returning a Ruby string on success and Qfalse on failure */
705
+ static VALUE
706
+ bs_read_contents(int fd, size_t size, const char ** errno_provenance)
603
707
  {
708
+ VALUE contents;
604
709
  ssize_t nread;
605
- *contents = ALLOC_N(char, size);
606
- nread = read(fd, *contents, size);
710
+ contents = rb_str_buf_new(size);
711
+ nread = read(fd, RSTRING_PTR(contents), size);
712
+
607
713
  if (nread < 0) {
608
714
  *errno_provenance = "bs_fetch:bs_read_contents:read";
715
+ return Qfalse;
716
+ } else {
717
+ rb_str_set_len(contents, nread);
718
+ return contents;
609
719
  }
610
- return nread;
611
720
  }
612
721
 
613
722
  /*
@@ -658,38 +767,67 @@ static VALUE
658
767
  bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
659
768
  {
660
769
  struct bs_cache_key cached_key, current_key;
661
- char * contents = NULL;
662
770
  int cache_fd = -1, current_fd = -1;
663
771
  int res, valid_cache = 0, exception_tag = 0;
664
772
  const char * errno_provenance = NULL;
665
773
 
666
- VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
774
+ VALUE status = Qfalse;
775
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
667
776
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
668
777
  VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
669
778
 
670
779
  VALUE exception; /* ruby exception object to raise instead of returning */
780
+ VALUE exception_message; /* ruby exception string to use instead of errno_provenance */
671
781
 
672
782
  /* Open the source file and generate a cache key for it */
673
783
  current_fd = open_current_file(path, &current_key, &errno_provenance);
674
- if (current_fd < 0) goto fail_errno;
784
+ if (current_fd < 0) {
785
+ exception_message = path_v;
786
+ goto fail_errno;
787
+ }
675
788
 
676
789
  /* Open the cache key if it exists, and read its cache key in */
677
790
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
678
791
  if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
679
792
  /* This is ok: valid_cache remains false, we re-populate it. */
680
- if (RB_UNLIKELY(instrumentation_enabled)) {
681
- rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
682
- }
793
+ bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
683
794
  } else if (cache_fd < 0) {
795
+ exception_message = rb_str_new_cstr(cache_path);
684
796
  goto fail_errno;
685
797
  } else {
686
798
  /* True if the cache existed and no invalidating changes have occurred since
687
799
  * it was generated. */
688
- valid_cache = cache_key_equal(&current_key, &cached_key);
689
- if (RB_UNLIKELY(instrumentation_enabled)) {
690
- if (!valid_cache) {
691
- rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
800
+
801
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
802
+ case hit:
803
+ status = sym_hit;
804
+ valid_cache = true;
805
+ break;
806
+ case miss:
807
+ valid_cache = false;
808
+ break;
809
+ case stale:
810
+ valid_cache = false;
811
+ if ((input_data = bs_read_contents(current_fd, current_key.size,
812
+ &errno_provenance)) == Qfalse) {
813
+ exception_message = path_v;
814
+ goto fail_errno;
815
+ }
816
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
817
+ if (valid_cache) {
818
+ if (!readonly) {
819
+ if (update_cache_key(&current_key, &cached_key, cache_fd, &errno_provenance)) {
820
+ exception_message = path_v;
821
+ goto fail_errno;
822
+ }
823
+ }
824
+ status = sym_revalidated;
692
825
  }
826
+ break;
827
+ };
828
+
829
+ if (!valid_cache) {
830
+ status = sym_stale;
693
831
  }
694
832
  }
695
833
 
@@ -703,13 +841,18 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
703
841
  else if (res == CACHE_UNCOMPILABLE) {
704
842
  /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
705
843
  This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
706
- if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
707
- input_data = rb_str_new(contents, current_key.size);
844
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
845
+ exception_message = path_v;
846
+ goto fail_errno;
847
+ }
708
848
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
709
849
  if (exception_tag != 0) goto raise;
710
850
  goto succeed;
711
851
  } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
712
- else if (res == ERROR_WITH_ERRNO) goto fail_errno;
852
+ else if (res == ERROR_WITH_ERRNO){
853
+ exception_message = rb_str_new_cstr(cache_path);
854
+ goto fail_errno;
855
+ }
713
856
  else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
714
857
  }
715
858
  close(cache_fd);
@@ -717,8 +860,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
717
860
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
718
861
 
719
862
  /* Read the contents of the source file into a buffer */
720
- if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
721
- input_data = rb_str_new(contents, current_key.size);
863
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
864
+ exception_message = path_v;
865
+ goto fail_errno;
866
+ }
722
867
 
723
868
  /* Try to compile the input_data using input_to_storage(input_data) */
724
869
  exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
@@ -737,6 +882,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
737
882
  * We do however ignore any failures to persist the cache, as it's better
738
883
  * to move along, than to interrupt the process.
739
884
  */
885
+ bs_cache_key_digest(&current_key, input_data);
740
886
  atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
741
887
 
742
888
  /* Having written the cache, now convert storage_data to output_data */
@@ -755,6 +901,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
755
901
  * No point raising an error */
756
902
  if (errno != ENOENT) {
757
903
  errno_provenance = "bs_fetch:unlink";
904
+ exception_message = rb_str_new_cstr(cache_path);
758
905
  goto fail_errno;
759
906
  }
760
907
  }
@@ -765,16 +912,22 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
765
912
  goto succeed; /* output_data is now the correct return. */
766
913
 
767
914
  #define CLEANUP \
768
- if (contents != NULL) xfree(contents); \
769
915
  if (current_fd >= 0) close(current_fd); \
770
- if (cache_fd >= 0) close(cache_fd);
916
+ if (cache_fd >= 0) close(cache_fd); \
917
+ if (status != Qfalse) bs_instrumentation(status, path_v);
771
918
 
772
919
  succeed:
773
920
  CLEANUP;
774
921
  return output_data;
775
922
  fail_errno:
776
923
  CLEANUP;
777
- exception = rb_syserr_new(errno, errno_provenance);
924
+ if (errno_provenance) {
925
+ exception_message = rb_str_concat(
926
+ rb_str_new_cstr(errno_provenance),
927
+ rb_str_concat(rb_str_new_cstr(": "), exception_message)
928
+ );
929
+ }
930
+ exception = rb_syserr_new_str(errno, exception_message);
778
931
  rb_exc_raise(exception);
779
932
  __builtin_unreachable();
780
933
  raise:
@@ -792,13 +945,16 @@ invalid_type_storage_data:
792
945
  static VALUE
793
946
  bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
794
947
  {
948
+ if (readonly) {
949
+ return Qfalse;
950
+ }
951
+
795
952
  struct bs_cache_key cached_key, current_key;
796
- char * contents = NULL;
797
953
  int cache_fd = -1, current_fd = -1;
798
954
  int res, valid_cache = 0, exception_tag = 0;
799
955
  const char * errno_provenance = NULL;
800
956
 
801
- VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
957
+ VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
802
958
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
803
959
 
804
960
  /* Open the source file and generate a cache key for it */
@@ -814,7 +970,26 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
814
970
  } else {
815
971
  /* True if the cache existed and no invalidating changes have occurred since
816
972
  * it was generated. */
817
- valid_cache = cache_key_equal(&current_key, &cached_key);
973
+ switch(cache_key_equal_fast_path(&current_key, &cached_key)) {
974
+ case hit:
975
+ valid_cache = true;
976
+ break;
977
+ case miss:
978
+ valid_cache = false;
979
+ break;
980
+ case stale:
981
+ valid_cache = false;
982
+ if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
983
+ goto fail;
984
+ }
985
+ valid_cache = cache_key_equal_slow_path(&current_key, &cached_key, input_data);
986
+ if (valid_cache) {
987
+ if (update_cache_key(&current_key, &cached_key, cache_fd, &errno_provenance)) {
988
+ goto fail;
989
+ }
990
+ }
991
+ break;
992
+ };
818
993
  }
819
994
 
820
995
  if (valid_cache) {
@@ -826,8 +1001,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
826
1001
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
827
1002
 
828
1003
  /* Read the contents of the source file into a buffer */
829
- if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
830
- input_data = rb_str_new(contents, current_key.size);
1004
+ if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
831
1005
 
832
1006
  /* Try to compile the input_data using input_to_storage(input_data) */
833
1007
  exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
@@ -842,13 +1016,13 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
842
1016
  if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
843
1017
 
844
1018
  /* Write the cache key and storage_data to the cache directory */
1019
+ bs_cache_key_digest(&current_key, input_data);
845
1020
  res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
846
1021
  if (res < 0) goto fail;
847
1022
 
848
1023
  goto succeed;
849
1024
 
850
1025
  #define CLEANUP \
851
- if (contents != NULL) xfree(contents); \
852
1026
  if (current_fd >= 0) close(current_fd); \
853
1027
  if (cache_fd >= 0) close(cache_fd);
854
1028
 
@@ -945,12 +1119,17 @@ try_input_to_storage(VALUE arg)
945
1119
  static int
946
1120
  bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
947
1121
  {
948
- int state;
949
- struct i2s_data i2s_data = {
950
- .handler = handler,
951
- .input_data = input_data,
952
- .pathval = pathval,
953
- };
954
- *storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
955
- return state;
1122
+ if (readonly) {
1123
+ *storage_data = rb_cBootsnap_CompileCache_UNCOMPILABLE;
1124
+ return 0;
1125
+ } else {
1126
+ int state;
1127
+ struct i2s_data i2s_data = {
1128
+ .handler = handler,
1129
+ .input_data = input_data,
1130
+ .pathval = pathval,
1131
+ };
1132
+ *storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
1133
+ return state;
1134
+ }
956
1135
  }
@@ -1,26 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require("mkmf")
3
+ require "mkmf"
4
4
 
5
- if RUBY_ENGINE == "ruby"
6
- $CFLAGS << " -O3 "
7
- $CFLAGS << " -std=c99"
5
+ if %w[ruby truffleruby].include?(RUBY_ENGINE)
6
+ have_func "fdatasync", "unistd.h"
7
+
8
+ unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
9
+ append_cppflags ["-D_GNU_SOURCE"] # Needed of O_NOATIME
10
+ end
11
+
12
+ append_cflags ["-O3", "-std=c99"]
8
13
 
9
14
  # ruby.h has some -Wpedantic fails in some cases
10
15
  # (e.g. https://github.com/Shopify/bootsnap/issues/15)
11
16
  unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
- $CFLAGS << " -Wall"
13
- $CFLAGS << " -Werror"
14
- $CFLAGS << " -Wextra"
15
- $CFLAGS << " -Wpedantic"
17
+ append_cflags([
18
+ "-Wall",
19
+ "-Werror",
20
+ "-Wextra",
21
+ "-Wpedantic",
16
22
 
17
- $CFLAGS << " -Wno-unused-parameter" # VALUE self has to be there but we don't care what it is.
18
- $CFLAGS << " -Wno-keyword-macro" # hiding return
19
- $CFLAGS << " -Wno-gcc-compat" # ruby.h 2.6.0 on macos 10.14, dunno
20
- $CFLAGS << " -Wno-compound-token-split-by-macro"
23
+ "-Wno-unused-parameter", # VALUE self has to be there but we don't care what it is.
24
+ "-Wno-keyword-macro", # hiding return
25
+ "-Wno-gcc-compat", # ruby.h 2.6.0 on macos 10.14, dunno
26
+ "-Wno-compound-token-split-by-macro",
27
+ ])
21
28
  end
22
29
 
23
30
  create_makefile("bootsnap/bootsnap")
24
31
  else
25
- File.write("Makefile", dummy_makefile($srcdir).join(""))
32
+ File.write("Makefile", dummy_makefile($srcdir).join)
26
33
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- extend(self)
4
+ extend self
5
5
 
6
6
  def bundler?
7
7
  return false unless defined?(::Bundler)