bootsnap 1.12.0 → 1.18.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)