bootsnap 1.6.0 → 1.15.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.
@@ -14,16 +14,11 @@
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>
20
21
  #include <sys/stat.h>
21
- #ifndef _WIN32
22
- #include <sys/utsname.h>
23
- #endif
24
- #ifdef __GLIBC__
25
- #include <gnu/libc-version.h>
26
- #endif
27
22
 
28
23
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
29
24
  * 981 for the cache dir */
@@ -34,6 +29,10 @@
34
29
 
35
30
  #define MAX_CREATE_TEMPFILE_ATTEMPT 3
36
31
 
32
+ #ifndef RB_UNLIKELY
33
+ #define RB_UNLIKELY(x) (x)
34
+ #endif
35
+
37
36
  /*
38
37
  * An instance of this key is written as the first 64 bytes of each cache file.
39
38
  * The mtime and size members track whether the file contents have changed, and
@@ -70,7 +69,7 @@ struct bs_cache_key {
70
69
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
71
70
 
72
71
  /* Effectively a schema version. Bumping invalidates all previous caches */
73
- static const uint32_t current_version = 3;
72
+ static const uint32_t current_version = 4;
74
73
 
75
74
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
76
75
  * new OS ABI, etc. */
@@ -86,17 +85,22 @@ static mode_t current_umask;
86
85
  static VALUE rb_mBootsnap;
87
86
  static VALUE rb_mBootsnap_CompileCache;
88
87
  static VALUE rb_mBootsnap_CompileCache_Native;
89
- static VALUE rb_eBootsnap_CompileCache_Uncompilable;
90
- static ID uncompilable;
88
+ static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
89
+ static ID instrumentation_method;
90
+ static VALUE sym_miss;
91
+ static VALUE sym_stale;
92
+ static bool instrumentation_enabled = false;
93
+ static bool readonly = false;
91
94
 
92
95
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
96
+ static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
97
+ static VALUE bs_readonly_set(VALUE self, VALUE enabled);
93
98
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
94
99
  static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
95
100
  static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
96
101
 
97
102
  /* Helpers */
98
- static uint64_t fnv1a_64(const char *str);
99
- static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
103
+ static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
100
104
  static int bs_read_key(int fd, struct bs_cache_key * key);
101
105
  static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
102
106
  static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
@@ -111,10 +115,8 @@ static uint32_t get_ruby_platform(void);
111
115
  * exception.
112
116
  */
113
117
  static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
114
- static VALUE prot_storage_to_output(VALUE arg);
115
118
  static VALUE prot_input_to_output(VALUE arg);
116
119
  static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
117
- static VALUE prot_input_to_storage(VALUE arg);
118
120
  static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
119
121
  struct s2o_data;
120
122
  struct i2o_data;
@@ -129,6 +131,12 @@ bs_rb_coverage_running(VALUE self)
129
131
  return RTEST(cov) ? Qtrue : Qfalse;
130
132
  }
131
133
 
134
+ static VALUE
135
+ bs_rb_get_path(VALUE self, VALUE fname)
136
+ {
137
+ return rb_get_path(fname);
138
+ }
139
+
132
140
  /*
133
141
  * Ruby C extensions are initialized by calling Init_<extname>.
134
142
  *
@@ -140,15 +148,27 @@ void
140
148
  Init_bootsnap(void)
141
149
  {
142
150
  rb_mBootsnap = rb_define_module("Bootsnap");
151
+
152
+ rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
153
+
143
154
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
144
155
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
145
- rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
156
+ rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
157
+ rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
146
158
 
147
159
  current_ruby_revision = get_ruby_revision();
148
160
  current_ruby_platform = get_ruby_platform();
149
161
 
150
- uncompilable = rb_intern("__bootsnap_uncompilable__");
162
+ instrumentation_method = rb_intern("_instrument");
163
+
164
+ sym_miss = ID2SYM(rb_intern("miss"));
165
+ rb_global_variable(&sym_miss);
166
+
167
+ sym_stale = ID2SYM(rb_intern("stale"));
168
+ rb_global_variable(&sym_stale);
151
169
 
170
+ rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
171
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
152
172
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
153
173
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
154
174
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
@@ -158,6 +178,20 @@ Init_bootsnap(void)
158
178
  umask(current_umask);
159
179
  }
160
180
 
181
+ static VALUE
182
+ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
183
+ {
184
+ instrumentation_enabled = RTEST(enabled);
185
+ return enabled;
186
+ }
187
+
188
+ static VALUE
189
+ bs_readonly_set(VALUE self, VALUE enabled)
190
+ {
191
+ readonly = RTEST(enabled);
192
+ return enabled;
193
+ }
194
+
161
195
  /*
162
196
  * Bootsnap's ruby code registers a hook that notifies us via this function
163
197
  * when compile_option changes. These changes invalidate all existing caches.
@@ -175,22 +209,13 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
175
209
  return Qnil;
176
210
  }
177
211
 
178
- /*
179
- * We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
180
- * it has several nice properties:
181
- *
182
- * - Tiny implementation
183
- * - No external dependency
184
- * - Solid performance
185
- * - Solid randomness
186
- * - 32 bits doesn't feel collision-resistant enough; 64 is nice.
187
- */
188
212
  static uint64_t
189
- fnv1a_64_iter(uint64_t h, const char *str)
213
+ fnv1a_64_iter(uint64_t h, const VALUE str)
190
214
  {
191
- unsigned char *s = (unsigned char *)str;
215
+ unsigned char *s = (unsigned char *)RSTRING_PTR(str);
216
+ unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
192
217
 
193
- while (*s) {
218
+ while (s < str_end) {
194
219
  h ^= (uint64_t)*s++;
195
220
  h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
196
221
  }
@@ -199,7 +224,7 @@ fnv1a_64_iter(uint64_t h, const char *str)
199
224
  }
200
225
 
201
226
  static uint64_t
202
- fnv1a_64(const char *str)
227
+ fnv1a_64(const VALUE str)
203
228
  {
204
229
  uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
205
230
  return fnv1a_64_iter(h, str);
@@ -220,7 +245,7 @@ get_ruby_revision(void)
220
245
  } else {
221
246
  uint64_t hash;
222
247
 
223
- hash = fnv1a_64(StringValueCStr(ruby_revision));
248
+ hash = fnv1a_64(ruby_revision);
224
249
  return (uint32_t)(hash >> 32);
225
250
  }
226
251
  }
@@ -228,10 +253,6 @@ get_ruby_revision(void)
228
253
  /*
229
254
  * When ruby's version doesn't change, but it's recompiled on a different OS
230
255
  * (or OS version), we need to invalidate the cache.
231
- *
232
- * We actually factor in some extra information here, to be extra confident
233
- * that we don't try to re-use caches that will not be compatible, by factoring
234
- * in utsname.version.
235
256
  */
236
257
  static uint32_t
237
258
  get_ruby_platform(void)
@@ -240,23 +261,8 @@ get_ruby_platform(void)
240
261
  VALUE ruby_platform;
241
262
 
242
263
  ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
243
- hash = fnv1a_64(RSTRING_PTR(ruby_platform));
244
-
245
- #ifdef _WIN32
246
- return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
247
- #elif defined(__GLIBC__)
248
- hash = fnv1a_64_iter(hash, gnu_get_libc_version());
249
- return (uint32_t)(hash >> 32);
250
- #else
251
- struct utsname utsname;
252
-
253
- /* Not worth crashing if this fails; lose extra cache invalidation potential */
254
- if (uname(&utsname) >= 0) {
255
- hash = fnv1a_64_iter(hash, utsname.version);
256
- }
257
-
264
+ hash = fnv1a_64(ruby_platform);
258
265
  return (uint32_t)(hash >> 32);
259
- #endif
260
266
  }
261
267
 
262
268
  /*
@@ -267,13 +273,13 @@ get_ruby_platform(void)
267
273
  * The path will look something like: <cachedir>/12/34567890abcdef
268
274
  */
269
275
  static void
270
- bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
276
+ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
271
277
  {
272
278
  uint64_t hash = fnv1a_64(path);
273
279
  uint8_t first_byte = (hash >> (64 - 8));
274
280
  uint64_t remainder = hash & 0x00ffffffffffffff;
275
281
 
276
- sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
282
+ sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
277
283
  }
278
284
 
279
285
  /*
@@ -319,7 +325,7 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
319
325
  char cache_path[MAX_CACHEPATH_SIZE];
320
326
 
321
327
  /* generate cache path to cache_path */
322
- bs_cache_path(cachedir, path, &cache_path);
328
+ bs_cache_path(cachedir, path_v, &cache_path);
323
329
 
324
330
  return bs_fetch(path, path_v, cache_path, handler, args);
325
331
  }
@@ -346,7 +352,7 @@ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
346
352
  char cache_path[MAX_CACHEPATH_SIZE];
347
353
 
348
354
  /* generate cache path to cache_path */
349
- bs_cache_path(cachedir, path, &cache_path);
355
+ bs_cache_path(cachedir, path_v, &cache_path);
350
356
 
351
357
  return bs_precompile(path, path_v, cache_path, handler);
352
358
  }
@@ -386,7 +392,9 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
386
392
  }
387
393
 
388
394
  #define ERROR_WITH_ERRNO -1
389
- #define CACHE_MISSING_OR_INVALID -2
395
+ #define CACHE_MISS -2
396
+ #define CACHE_STALE -3
397
+ #define CACHE_UNCOMPILABLE -4
390
398
 
391
399
  /*
392
400
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -394,15 +402,16 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
394
402
  *
395
403
  * Possible return values:
396
404
  * - 0 (OK, key was loaded)
397
- * - CACHE_MISSING_OR_INVALID (-2)
398
405
  * - ERROR_WITH_ERRNO (-1, errno is set)
406
+ * - CACHE_MISS (-2)
407
+ * - CACHE_STALE (-3)
399
408
  */
400
409
  static int
401
410
  bs_read_key(int fd, struct bs_cache_key * key)
402
411
  {
403
412
  ssize_t nread = read(fd, key, KEY_SIZE);
404
413
  if (nread < 0) return ERROR_WITH_ERRNO;
405
- if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID;
414
+ if (nread < KEY_SIZE) return CACHE_STALE;
406
415
  return 0;
407
416
  }
408
417
 
@@ -412,7 +421,8 @@ bs_read_key(int fd, struct bs_cache_key * key)
412
421
  *
413
422
  * Possible return values:
414
423
  * - 0 (OK, key was loaded)
415
- * - CACHE_MISSING_OR_INVALID (-2)
424
+ * - CACHE_MISS (-2)
425
+ * - CACHE_STALE (-3)
416
426
  * - ERROR_WITH_ERRNO (-1, errno is set)
417
427
  */
418
428
  static int
@@ -423,8 +433,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
423
433
  fd = open(path, O_RDONLY);
424
434
  if (fd < 0) {
425
435
  *errno_provenance = "bs_fetch:open_cache_file:open";
426
- if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
427
- return ERROR_WITH_ERRNO;
436
+ return CACHE_MISS;
428
437
  }
429
438
  #ifdef _WIN32
430
439
  setmode(fd, O_BINARY);
@@ -467,24 +476,28 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
467
476
  if (data_size > 100000000000) {
468
477
  *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
469
478
  errno = EINVAL; /* because wtf? */
470
- ret = -1;
479
+ ret = ERROR_WITH_ERRNO;
471
480
  goto done;
472
481
  }
473
482
  data = ALLOC_N(char, data_size);
474
483
  nread = read(fd, data, data_size);
475
484
  if (nread < 0) {
476
485
  *errno_provenance = "bs_fetch:fetch_cached_data:read";
477
- ret = -1;
486
+ ret = ERROR_WITH_ERRNO;
478
487
  goto done;
479
488
  }
480
489
  if (nread != data_size) {
481
- ret = CACHE_MISSING_OR_INVALID;
490
+ ret = CACHE_STALE;
482
491
  goto done;
483
492
  }
484
493
 
485
494
  storage_data = rb_str_new(data, data_size);
486
495
 
487
496
  *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
497
+ if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
498
+ ret = CACHE_UNCOMPILABLE;
499
+ goto done;
500
+ }
488
501
  ret = 0;
489
502
  done:
490
503
  if (data != NULL) xfree(data);
@@ -672,14 +685,22 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
672
685
 
673
686
  /* Open the cache key if it exists, and read its cache key in */
674
687
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
675
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
688
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
676
689
  /* This is ok: valid_cache remains false, we re-populate it. */
690
+ if (RB_UNLIKELY(instrumentation_enabled)) {
691
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
692
+ }
677
693
  } else if (cache_fd < 0) {
678
694
  goto fail_errno;
679
695
  } else {
680
696
  /* True if the cache existed and no invalidating changes have occurred since
681
697
  * it was generated. */
682
698
  valid_cache = cache_key_equal(&current_key, &cached_key);
699
+ if (RB_UNLIKELY(instrumentation_enabled)) {
700
+ if (!valid_cache) {
701
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
702
+ }
703
+ }
683
704
  }
684
705
 
685
706
  if (valid_cache) {
@@ -688,10 +709,18 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
688
709
  cache_fd, (ssize_t)cached_key.data_size, handler, args,
689
710
  &output_data, &exception_tag, &errno_provenance
690
711
  );
691
- if (exception_tag != 0) goto raise;
692
- else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
693
- else if (res == ERROR_WITH_ERRNO) goto fail_errno;
694
- else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
712
+ if (exception_tag != 0) goto raise;
713
+ else if (res == CACHE_UNCOMPILABLE) {
714
+ /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
715
+ This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
716
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
717
+ input_data = rb_str_new(contents, current_key.size);
718
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
719
+ if (exception_tag != 0) goto raise;
720
+ goto succeed;
721
+ } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
722
+ else if (res == ERROR_WITH_ERRNO) goto fail_errno;
723
+ else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
695
724
  }
696
725
  close(cache_fd);
697
726
  cache_fd = -1;
@@ -706,7 +735,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
706
735
  if (exception_tag != 0) goto raise;
707
736
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
708
737
  * to cache anything; just return input_to_output(input_data) */
709
- if (storage_data == uncompilable) {
738
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
710
739
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
711
740
  if (exception_tag != 0) goto raise;
712
741
  goto succeed;
@@ -714,20 +743,30 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
714
743
  /* If storage_data isn't a string, we can't cache it */
715
744
  if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
716
745
 
717
- /* Write the cache key and storage_data to the cache directory */
718
- res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
719
- if (res < 0) goto fail_errno;
746
+ /* Attempt to write the cache key and storage_data to the cache directory.
747
+ * We do however ignore any failures to persist the cache, as it's better
748
+ * to move along, than to interrupt the process.
749
+ */
750
+ atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
720
751
 
721
752
  /* Having written the cache, now convert storage_data to output_data */
722
753
  exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
723
754
  if (exception_tag != 0) goto raise;
724
755
 
725
- /* If output_data is nil, delete the cache entry and generate the output
726
- * using input_to_output */
727
- if (NIL_P(output_data)) {
756
+ if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
757
+ /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
758
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
759
+ if (exception_tag != 0) goto raise;
760
+ } else if (NIL_P(output_data)) {
761
+ /* If output_data is nil, delete the cache entry and generate the output
762
+ * using input_to_output */
728
763
  if (unlink(cache_path) < 0) {
729
- errno_provenance = "bs_fetch:unlink";
730
- goto fail_errno;
764
+ /* If the cache was already deleted, it might be that another process did it before us.
765
+ * No point raising an error */
766
+ if (errno != ENOENT) {
767
+ errno_provenance = "bs_fetch:unlink";
768
+ goto fail_errno;
769
+ }
731
770
  }
732
771
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
733
772
  if (exception_tag != 0) goto raise;
@@ -778,7 +817,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
778
817
 
779
818
  /* Open the cache key if it exists, and read its cache key in */
780
819
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
781
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
820
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
782
821
  /* This is ok: valid_cache remains false, we re-populate it. */
783
822
  } else if (cache_fd < 0) {
784
823
  goto fail;
@@ -806,7 +845,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
806
845
 
807
846
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
808
847
  * to cache anything; just return false */
809
- if (storage_data == uncompilable) {
848
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
810
849
  goto fail;
811
850
  }
812
851
  /* If storage_data isn't a string, we can't cache it */
@@ -869,7 +908,7 @@ struct i2s_data {
869
908
  };
870
909
 
871
910
  static VALUE
872
- prot_storage_to_output(VALUE arg)
911
+ try_storage_to_output(VALUE arg)
873
912
  {
874
913
  struct s2o_data * data = (struct s2o_data *)arg;
875
914
  return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
@@ -884,7 +923,7 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
884
923
  .args = args,
885
924
  .storage_data = storage_data,
886
925
  };
887
- *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
926
+ *output_data = rb_protect(try_storage_to_output, (VALUE)&s2o_data, &state);
888
927
  return state;
889
928
  }
890
929
 
@@ -913,31 +952,20 @@ try_input_to_storage(VALUE arg)
913
952
  return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
914
953
  }
915
954
 
916
- static VALUE
917
- rescue_input_to_storage(VALUE arg, VALUE e)
918
- {
919
- return uncompilable;
920
- }
921
-
922
- static VALUE
923
- prot_input_to_storage(VALUE arg)
924
- {
925
- struct i2s_data * data = (struct i2s_data *)arg;
926
- return rb_rescue2(
927
- try_input_to_storage, (VALUE)data,
928
- rescue_input_to_storage, Qnil,
929
- rb_eBootsnap_CompileCache_Uncompilable, 0);
930
- }
931
-
932
955
  static int
933
956
  bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
934
957
  {
935
- int state;
936
- struct i2s_data i2s_data = {
937
- .handler = handler,
938
- .input_data = input_data,
939
- .pathval = pathval,
940
- };
941
- *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
942
- return state;
958
+ if (readonly) {
959
+ *storage_data = rb_cBootsnap_CompileCache_UNCOMPILABLE;
960
+ return 0;
961
+ } else {
962
+ int state;
963
+ struct i2s_data i2s_data = {
964
+ .handler = handler,
965
+ .input_data = input_data,
966
+ .pathval = pathval,
967
+ };
968
+ *storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
969
+ return state;
970
+ }
943
971
  }
@@ -1,19 +1,26 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require("mkmf")
3
- $CFLAGS << ' -O3 '
4
- $CFLAGS << ' -std=c99'
5
4
 
6
- # ruby.h has some -Wpedantic fails in some cases
7
- # (e.g. https://github.com/Shopify/bootsnap/issues/15)
8
- unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
9
- $CFLAGS << ' -Wall'
10
- $CFLAGS << ' -Werror'
11
- $CFLAGS << ' -Wextra'
12
- $CFLAGS << ' -Wpedantic'
5
+ if RUBY_ENGINE == "ruby"
6
+ $CFLAGS << " -O3 "
7
+ $CFLAGS << " -std=c99"
13
8
 
14
- $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
15
- $CFLAGS << ' -Wno-keyword-macro' # hiding return
16
- $CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
17
- end
9
+ # ruby.h has some -Wpedantic fails in some cases
10
+ # (e.g. https://github.com/Shopify/bootsnap/issues/15)
11
+ unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
+ $CFLAGS << " -Wall"
13
+ $CFLAGS << " -Werror"
14
+ $CFLAGS << " -Wextra"
15
+ $CFLAGS << " -Wpedantic"
18
16
 
19
- create_makefile("bootsnap/bootsnap")
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"
21
+ end
22
+
23
+ create_makefile("bootsnap/bootsnap")
24
+ else
25
+ File.write("Makefile", dummy_makefile($srcdir).join)
26
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  extend(self)
4
5
 
@@ -37,7 +37,11 @@ module Bootsnap
37
37
 
38
38
  def initialize(jobs)
39
39
  @jobs = jobs
40
- @pipe_out, @to_io = IO.pipe
40
+ @pipe_out, @to_io = IO.pipe(binmode: true)
41
+ # Set the writer encoding to binary since IO.pipe only sets it for the reader.
42
+ # https://github.com/rails/rails/issues/16514#issuecomment-52313290
43
+ @to_io.set_encoding(Encoding::BINARY)
44
+
41
45
  @pid = nil
42
46
  end
43
47
 
@@ -59,6 +63,7 @@ module Bootsnap
59
63
  loop do
60
64
  job, *args = Marshal.load(@pipe_out)
61
65
  return if job == :exit
66
+
62
67
  @jobs.fetch(job).call(*args)
63
68
  end
64
69
  rescue IOError