bootsnap 1.6.0 → 1.15.0

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