bootsnap 1.9.1 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00c1b5b2c37c6a351bb7656ef17ba7f7e6143fd773ceebd6ee09d9a55e25b75a
4
- data.tar.gz: d6d6d1d68d97e4f2c50c61f0b928714cf94f29ecab6604bb0092517779212738
3
+ metadata.gz: 1bf1eca00971c561ca1e15941903dcf193ec8fc84c68736177a3aa72558c536b
4
+ data.tar.gz: bcd5596d1b4b00905af82cf25693fb72e71897685e024b27311353eef6f588bf
5
5
  SHA512:
6
- metadata.gz: e1ade83cee1e4a917317cecd34107493424a5174b8bf92cc74db47743d4d84931de24533979480a23aa4ddb496cae8ce1f3cf30a2fbed3996b885a68138d16f0
7
- data.tar.gz: 23e4f6c77307b30526b880bfd12983b7e66c9539556acd3bc05a2fa3fd6884a142c83dfaa590846651afad243c0bf69c96d994ca8492e5539d706ba3e6cf713f
6
+ metadata.gz: 0d3e37e56d994647ac88a1c9b83f087f137d32acfc09bfba1e71a85ce254697b1dcf6c9e1c90b5c71728ce0f3c0a63ee86680455a8f95003d237671435042859
7
+ data.tar.gz: 270bf8fc609981d25441c9c4bd975348130177564f2e1a0744f18720d7f59cda3c3da24bea0c07f2498a185308647b05715e1b1dd5a6045e653600ab2f5907a1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,128 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.16.0
4
+
5
+ * Use `RbConfig::CONFIG["rubylibdir"]` instead of `RbConfig::CONFIG["libdir"]` to check for stdlib files. See #431.
6
+ * Fix the cached version of `YAML.load_file` being slightly more permissive than the default `Psych` one. See #434.
7
+ `Date` and `Time` values are now properly rejected, as well as aliases.
8
+ If this causes a regression in your application, it is recommended to load *trusted* YAML files with `YAML.unsafe_load_file`.
9
+
10
+ # 1.15.0
11
+
12
+ * Add a readonly mode, for environments in which the updated cache wouldn't be persisted. See #428 and #423.
13
+
14
+ # 1.14.0
15
+
16
+ * Require Ruby 2.6.
17
+ * Add a way to skip directories during load path scanning.
18
+ If you have large non-ruby directories in the middle of your load path, it can severely slow down scanning.
19
+ Typically this is a problem with `node_modules`. See #277.
20
+ * Fix `Bootsnap.unload_cache!`, it simply wouldn't work at all becaue of a merge mistake. See #421.
21
+
22
+ # 1.13.0
23
+
24
+ * Stop decorating `Kernel.load`. This used to be very useful in development because the Rails "classic" autoloader
25
+ was using `Kernel.load` in dev and `Kernel.require` in production. But Zeitwerk is now the default, and it doesn't
26
+ use `Kernel.load` at all.
27
+
28
+ People still using the classic autoloader might want to stick to `bootsnap 1.12`.
29
+
30
+ * Add `Bootsnap.unload_cache!`. Applications can call it at the end of their boot sequence when they know
31
+ no more code will be loaded to reclaim a bit of memory.
32
+
33
+ # 1.12.0
34
+
35
+ * `bootsnap precompile` CLI will now also precompile `Rakefile` and `.rake` files.
36
+
37
+ * Stop decorating `Module#autoload` as it was only useful for supporting Ruby 2.2 and older.
38
+
39
+ * Remove `uname` and other patform specific version from the cache keys. `RUBY_PLATFORM + RUBY_REVISION` should be
40
+ enough to ensure bytecode compatibility. This should improve caching for alpine based setups. See #409.
41
+
42
+ # 1.11.1
43
+
44
+ * Fix the `can't modify frozen Hash` error on load path cache mutation. See #411.
45
+
46
+ # 1.11.0
47
+
48
+ * Drop dependency on `fileutils`.
49
+
50
+ * Better respect `Kernel#require` duck typing. While it almost never comes up in practice, `Kernel#require`
51
+ follow a fairly intricate duck-typing protocol on its argument implemented as `rb_get_path(VALUE)` in MRI.
52
+ So when applicable we bind `rb_get_path` and use it for improved compatibility. See #396 and #406.
53
+
54
+ * Get rid of the `Kernel.require_relative` decorator by resolving `$LOAD_PATH` members to their real path.
55
+ This way we handle symlinks in `$LOAD_PATH` much more efficiently. See #402 for the detailed explanation.
56
+
57
+ * Drop support for Ruby 2.3 (to allow getting rid of the `Kernel.require_relative` decorator).
58
+
59
+ # 1.10.3
60
+
61
+ * Fix Regexp and Date type support in YAML compile cache. (#400)
62
+
63
+ * Improve the YAML compile cache to support `UTF-8` symbols. (#398, #399)
64
+ [The default `MessagePack` symbol serializer assumes all symbols are ASCII](https://github.com/msgpack/msgpack-ruby/pull/211),
65
+ because of this, non-ASCII compatible symbol would be restored with `ASCII_8BIT` encoding (AKA `BINARY`).
66
+ Bootsnap now properly cache them in `UTF-8`.
67
+
68
+ Note that the above only apply for actual YAML symbols (e..g `--- :foo`).
69
+ The issue is still present for string keys parsed with `YAML.load_file(..., symbolize_names: true)`, that is a bug
70
+ in `msgpack` that will hopefully be solved soon, see: https://github.com/msgpack/msgpack-ruby/pull/246
71
+
72
+ * Entirely disable the YAML compile cache if `Encoding.default_internal` is set to an encoding not supported by `msgpack`. (#398)
73
+ `Psych` coerce strings to `Encoding.default_internal`, but `MessagePack` doesn't. So in this scenario we can't provide
74
+ YAML caching at all without returning the strings in the wrong encoding.
75
+ This never came up in practice but might as well be safe.
76
+
77
+ # 1.10.2
78
+
79
+ * Reduce the `Kernel.require` extra stack frames some more. Now bootsnap should only add one extra frame per `require` call.
80
+
81
+ * Better check `freeze` option support in JSON compile cache.
82
+ Previously `JSON.load_file(..., freeze: true)` would be cached even when the msgpack version is missing support for it.
83
+
84
+ # 1.10.1
85
+
86
+ * Fix `Kernel#autoload`'s fallback path always being executed.
87
+
88
+ * Consider `unlink` failing with `ENOENT` as a success.
89
+
90
+ # 1.10.0
91
+
92
+ * Delay requiring `FileUtils`. (#285)
93
+ `FileUtils` can be installed as a gem, so it's best to wait for bundler to have setup the load path before requiring it.
94
+
95
+ * Improve support of Psych 4. (#392)
96
+ Since `1.8.0`, `YAML.load_file` was no longer cached when Psych 4 was used. This is because `load_file` loads
97
+ in safe mode by default, so the Bootsnap cache could defeat that safety.
98
+ Now when precompiling YAML files, Bootsnap first try to parse them in safe mode, and if it can't fallback to unsafe mode,
99
+ and the cache contains a flag that records whether it was generated in safe mode or not.
100
+ `YAML.unsafe_load_file` will use safe caches just fine, but `YAML.load_file` will fallback to uncached YAML parsing
101
+ if the cache was generated using unsafe parsing.
102
+
103
+ * Minimize the Kernel.require extra stack frames. (#393)
104
+ This should reduce the noise generated by bootsnap on `LoadError`.
105
+
106
+ # 1.9.4
107
+
108
+ * Ignore absolute paths in the loaded feature index. (#385)
109
+ This fixes a compatibility issue with Zeitwerk when Zeitwerk is loaded before bootsnap. It also should
110
+ reduce the memory usage and improve load performance of Zeitwerk managed files.
111
+
112
+ * Automatically invalidate the load path cache whenever the Ruby version change. (#387)
113
+ This is to avoid issues in case the same installation path is re-used for subsequent ruby patch releases.
114
+
115
+ # 1.9.3
116
+
117
+ * Only disable the compile cache for source files impacted by [Ruby 3.0.3 [Bug 18250]](https://bugs.ruby-lang.org/issues/18250).
118
+ This should keep the performance loss to a minimum.
119
+
120
+ # 1.9.2
121
+
122
+ * Disable compile cache if [Ruby 3.0.3's ISeq cache bug](https://bugs.ruby-lang.org/issues/18250) is detected.
123
+ AKA `iseq.rb:13 to_binary: wrong argument type false (expected Symbol)`
124
+ * Fix `Kernel.load` behavior: before `load 'a'` would load `a.rb` (and other tried extensions) and wouldn't load `a` unless `development_mode: true`, now only `a` would be loaded and files with extensions wouldn't be.
125
+
3
126
  # 1.9.1
4
127
 
5
128
  * Removed a forgotten debug statement in JSON precompilation.
@@ -14,7 +137,7 @@
14
137
 
15
138
  # 1.8.0
16
139
 
17
- * Improve support for Pysch 4. (#368)
140
+ * Improve support for Psych 4. (#368)
18
141
 
19
142
  # 1.7.7
20
143
 
@@ -32,8 +155,8 @@
32
155
 
33
156
  # 1.7.4
34
157
 
35
- * Stop raising errors when encoutering various file system errors. The cache is now best effort,
36
- if somehow it can't be saved, bootsnapp will gracefully fallback to the original operation (e.g. `Kernel.require`).
158
+ * Stop raising errors when encountering various file system errors. The cache is now best effort,
159
+ if somehow it can't be saved, bootsnap will gracefully fallback to the original operation (e.g. `Kernel.require`).
37
160
  (#353, #177, #262)
38
161
 
39
162
  # 1.7.3
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Shopify, Inc.
3
+ Copyright (c) 2017-present Shopify, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -41,7 +41,7 @@ getting progressively slower, this is almost certainly the cause.**
41
41
  It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
42
42
  important to load Bootsnap as early as possible to get maximum performance improvement.
43
43
 
44
- You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb).
44
+ You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/setup.rb).
45
45
 
46
46
  If you are not using Rails, or if you are but want more control over things, add this to your
47
47
  application setup immediately after `require 'bundler/setup'` (i.e. as early as possible: the sooner
@@ -52,10 +52,12 @@ require 'bootsnap'
52
52
  env = ENV['RAILS_ENV'] || "development"
53
53
  Bootsnap.setup(
54
54
  cache_dir: 'tmp/cache', # Path to your cache
55
+ ignore_directories: ['node_modules'], # Directory names to skip.
55
56
  development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
56
57
  load_path_cache: true, # Optimize the LOAD_PATH with a cache
57
58
  compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
58
- compile_cache_yaml: true # Compile YAML into a cache
59
+ compile_cache_yaml: true, # Compile YAML into a cache
60
+ readonly: true, # Use the caches but don't update them on miss or stale entries.
59
61
  )
60
62
  ```
61
63
 
@@ -66,7 +68,7 @@ will help optimize boot time further if you have an extremely large `$LOAD_PATH`
66
68
  Note: Bootsnap and [Spring](https://github.com/rails/spring) are orthogonal tools. While Bootsnap
67
69
  speeds up the loading of individual source files, Spring keeps a copy of a pre-booted Rails process
68
70
  on hand to completely skip parts of the boot process the next time it's needed. The two tools work
69
- well together, and are both included in a newly-generated Rails applications by default.
71
+ well together.
70
72
 
71
73
  ### Environment variables
72
74
 
@@ -76,7 +78,11 @@ well together, and are both included in a newly-generated Rails applications by
76
78
  - `DISABLE_BOOTSNAP` allows to entirely disable bootsnap.
77
79
  - `DISABLE_BOOTSNAP_LOAD_PATH_CACHE` allows to disable load path caching.
78
80
  - `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
81
+ - `BOOTSNAP_READONLY` configure bootsnap to not update the cache on miss or stale entries.
79
82
  - `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR.
83
+ - `BOOTSNAP_IGNORE_DIRECTORIES` a comma separated list of directories that shouldn't be scanned.
84
+ Useful when you have large directories of non-ruby files inside `$LOAD_PATH`.
85
+ It defaults to ignore any directory named `node_modules`.
80
86
 
81
87
  ### Environments
82
88
 
@@ -161,7 +167,7 @@ The only directories considered "stable" are things under the Ruby install prefi
161
167
  "volatile".
162
168
 
163
169
  In addition to the [`Bootsnap::LoadPathCache::Cache`
164
- source](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb),
170
+ source](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/load_path_cache/cache.rb),
165
171
  this diagram may help clarify how entry resolution works:
166
172
 
167
173
  ![How path searching works](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png)
@@ -232,9 +238,9 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
232
238
  is a cache key including several fields:
233
239
 
234
240
  * `version`, hardcoded in bootsnap. Essentially a schema version;
235
- * `ruby_platform`, A hash of `RUBY_PLATFORM` (e.g. x86_64-linux-gnu) variable and glibc version (on Linux) or OS version (`uname -v` on BSD, macOS)
241
+ * `ruby_platform`, A hash of `RUBY_PLATFORM` (e.g. x86_64-linux-gnu) variable.
236
242
  * `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
237
- * `ruby_revision`, the version of Ruby this was compiled with;
243
+ * `ruby_revision`, A hash of `RUBY_REVISION`, the exact version of Ruby;
238
244
  * `size`, the size of the source file;
239
245
  * `mtime`, the last-modification timestamp of the source file when it was compiled; and
240
246
  * `data_size`, the number of bytes following the header, which we need to read it into a buffer.
data/exe/bootsnap CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'bootsnap/cli'
4
+ require "bootsnap/cli"
5
5
  exit Bootsnap::CLI.new(ARGV).run
@@ -19,12 +19,6 @@
19
19
  #include <errno.h>
20
20
  #include <fcntl.h>
21
21
  #include <sys/stat.h>
22
- #ifndef _WIN32
23
- #include <sys/utsname.h>
24
- #endif
25
- #ifdef __GLIBC__
26
- #include <gnu/libc-version.h>
27
- #endif
28
22
 
29
23
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
30
24
  * 981 for the cache dir */
@@ -75,7 +69,7 @@ struct bs_cache_key {
75
69
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
76
70
 
77
71
  /* Effectively a schema version. Bumping invalidates all previous caches */
78
- static const uint32_t current_version = 3;
72
+ static const uint32_t current_version = 4;
79
73
 
80
74
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
81
75
  * new OS ABI, etc. */
@@ -91,15 +85,16 @@ static mode_t current_umask;
91
85
  static VALUE rb_mBootsnap;
92
86
  static VALUE rb_mBootsnap_CompileCache;
93
87
  static VALUE rb_mBootsnap_CompileCache_Native;
94
- static VALUE rb_eBootsnap_CompileCache_Uncompilable;
95
- static ID uncompilable;
88
+ static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
96
89
  static ID instrumentation_method;
97
90
  static VALUE sym_miss;
98
91
  static VALUE sym_stale;
99
92
  static bool instrumentation_enabled = false;
93
+ static bool readonly = false;
100
94
 
101
95
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
102
96
  static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
97
+ static VALUE bs_readonly_set(VALUE self, VALUE enabled);
103
98
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
104
99
  static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
105
100
  static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
@@ -120,10 +115,8 @@ static uint32_t get_ruby_platform(void);
120
115
  * exception.
121
116
  */
122
117
  static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
123
- static VALUE prot_storage_to_output(VALUE arg);
124
118
  static VALUE prot_input_to_output(VALUE arg);
125
119
  static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
126
- static VALUE prot_input_to_storage(VALUE arg);
127
120
  static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
128
121
  struct s2o_data;
129
122
  struct i2o_data;
@@ -138,6 +131,12 @@ bs_rb_coverage_running(VALUE self)
138
131
  return RTEST(cov) ? Qtrue : Qfalse;
139
132
  }
140
133
 
134
+ static VALUE
135
+ bs_rb_get_path(VALUE self, VALUE fname)
136
+ {
137
+ return rb_get_path(fname);
138
+ }
139
+
141
140
  /*
142
141
  * Ruby C extensions are initialized by calling Init_<extname>.
143
142
  *
@@ -149,14 +148,17 @@ void
149
148
  Init_bootsnap(void)
150
149
  {
151
150
  rb_mBootsnap = rb_define_module("Bootsnap");
151
+
152
+ rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
153
+
152
154
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
153
155
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
154
- 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);
155
158
 
156
159
  current_ruby_revision = get_ruby_revision();
157
160
  current_ruby_platform = get_ruby_platform();
158
161
 
159
- uncompilable = rb_intern("__bootsnap_uncompilable__");
160
162
  instrumentation_method = rb_intern("_instrument");
161
163
 
162
164
  sym_miss = ID2SYM(rb_intern("miss"));
@@ -166,6 +168,7 @@ Init_bootsnap(void)
166
168
  rb_global_variable(&sym_stale);
167
169
 
168
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);
169
172
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
170
173
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
171
174
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
@@ -182,6 +185,13 @@ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
182
185
  return enabled;
183
186
  }
184
187
 
188
+ static VALUE
189
+ bs_readonly_set(VALUE self, VALUE enabled)
190
+ {
191
+ readonly = RTEST(enabled);
192
+ return enabled;
193
+ }
194
+
185
195
  /*
186
196
  * Bootsnap's ruby code registers a hook that notifies us via this function
187
197
  * when compile_option changes. These changes invalidate all existing caches.
@@ -199,29 +209,6 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
199
209
  return Qnil;
200
210
  }
201
211
 
202
- /*
203
- * We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
204
- * it has several nice properties:
205
- *
206
- * - Tiny implementation
207
- * - No external dependency
208
- * - Solid performance
209
- * - Solid randomness
210
- * - 32 bits doesn't feel collision-resistant enough; 64 is nice.
211
- */
212
- static uint64_t
213
- fnv1a_64_iter_cstr(uint64_t h, const char *str)
214
- {
215
- unsigned char *s = (unsigned char *)str;
216
-
217
- while (*s) {
218
- h ^= (uint64_t)*s++;
219
- h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
220
- }
221
-
222
- return h;
223
- }
224
-
225
212
  static uint64_t
226
213
  fnv1a_64_iter(uint64_t h, const VALUE str)
227
214
  {
@@ -266,10 +253,6 @@ get_ruby_revision(void)
266
253
  /*
267
254
  * When ruby's version doesn't change, but it's recompiled on a different OS
268
255
  * (or OS version), we need to invalidate the cache.
269
- *
270
- * We actually factor in some extra information here, to be extra confident
271
- * that we don't try to re-use caches that will not be compatible, by factoring
272
- * in utsname.version.
273
256
  */
274
257
  static uint32_t
275
258
  get_ruby_platform(void)
@@ -279,22 +262,7 @@ get_ruby_platform(void)
279
262
 
280
263
  ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
281
264
  hash = fnv1a_64(ruby_platform);
282
-
283
- #ifdef _WIN32
284
- return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
285
- #elif defined(__GLIBC__)
286
- hash = fnv1a_64_iter_cstr(hash, gnu_get_libc_version());
287
265
  return (uint32_t)(hash >> 32);
288
- #else
289
- struct utsname utsname;
290
-
291
- /* Not worth crashing if this fails; lose extra cache invalidation potential */
292
- if (uname(&utsname) >= 0) {
293
- hash = fnv1a_64_iter_cstr(hash, utsname.version);
294
- }
295
-
296
- return (uint32_t)(hash >> 32);
297
- #endif
298
266
  }
299
267
 
300
268
  /*
@@ -426,6 +394,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
426
394
  #define ERROR_WITH_ERRNO -1
427
395
  #define CACHE_MISS -2
428
396
  #define CACHE_STALE -3
397
+ #define CACHE_UNCOMPILABLE -4
429
398
 
430
399
  /*
431
400
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -507,14 +476,14 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
507
476
  if (data_size > 100000000000) {
508
477
  *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
509
478
  errno = EINVAL; /* because wtf? */
510
- ret = -1;
479
+ ret = ERROR_WITH_ERRNO;
511
480
  goto done;
512
481
  }
513
482
  data = ALLOC_N(char, data_size);
514
483
  nread = read(fd, data, data_size);
515
484
  if (nread < 0) {
516
485
  *errno_provenance = "bs_fetch:fetch_cached_data:read";
517
- ret = -1;
486
+ ret = ERROR_WITH_ERRNO;
518
487
  goto done;
519
488
  }
520
489
  if (nread != data_size) {
@@ -525,6 +494,10 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
525
494
  storage_data = rb_str_new(data, data_size);
526
495
 
527
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
+ }
528
501
  ret = 0;
529
502
  done:
530
503
  if (data != NULL) xfree(data);
@@ -737,7 +710,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
737
710
  &output_data, &exception_tag, &errno_provenance
738
711
  );
739
712
  if (exception_tag != 0) goto raise;
740
- else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
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;
741
722
  else if (res == ERROR_WITH_ERRNO) goto fail_errno;
742
723
  else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
743
724
  }
@@ -754,7 +735,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
754
735
  if (exception_tag != 0) goto raise;
755
736
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
756
737
  * to cache anything; just return input_to_output(input_data) */
757
- if (storage_data == uncompilable) {
738
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
758
739
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
759
740
  if (exception_tag != 0) goto raise;
760
741
  goto succeed;
@@ -772,12 +753,20 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
772
753
  exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
773
754
  if (exception_tag != 0) goto raise;
774
755
 
775
- /* If output_data is nil, delete the cache entry and generate the output
776
- * using input_to_output */
777
- 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 */
778
763
  if (unlink(cache_path) < 0) {
779
- errno_provenance = "bs_fetch:unlink";
780
- 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
+ }
781
770
  }
782
771
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
783
772
  if (exception_tag != 0) goto raise;
@@ -856,7 +845,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
856
845
 
857
846
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
858
847
  * to cache anything; just return false */
859
- if (storage_data == uncompilable) {
848
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
860
849
  goto fail;
861
850
  }
862
851
  /* If storage_data isn't a string, we can't cache it */
@@ -919,7 +908,7 @@ struct i2s_data {
919
908
  };
920
909
 
921
910
  static VALUE
922
- prot_storage_to_output(VALUE arg)
911
+ try_storage_to_output(VALUE arg)
923
912
  {
924
913
  struct s2o_data * data = (struct s2o_data *)arg;
925
914
  return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
@@ -934,7 +923,7 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
934
923
  .args = args,
935
924
  .storage_data = storage_data,
936
925
  };
937
- *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);
938
927
  return state;
939
928
  }
940
929
 
@@ -963,31 +952,20 @@ try_input_to_storage(VALUE arg)
963
952
  return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
964
953
  }
965
954
 
966
- static VALUE
967
- rescue_input_to_storage(VALUE arg, VALUE e)
968
- {
969
- return uncompilable;
970
- }
971
-
972
- static VALUE
973
- prot_input_to_storage(VALUE arg)
974
- {
975
- struct i2s_data * data = (struct i2s_data *)arg;
976
- return rb_rescue2(
977
- try_input_to_storage, (VALUE)data,
978
- rescue_input_to_storage, Qnil,
979
- rb_eBootsnap_CompileCache_Uncompilable, 0);
980
- }
981
-
982
955
  static int
983
956
  bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
984
957
  {
985
- int state;
986
- struct i2s_data i2s_data = {
987
- .handler = handler,
988
- .input_data = input_data,
989
- .pathval = pathval,
990
- };
991
- *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
992
- 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
+ }
993
971
  }
@@ -1,24 +1,26 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require("mkmf")
3
4
 
4
- if RUBY_ENGINE == 'ruby'
5
- $CFLAGS << ' -O3 '
6
- $CFLAGS << ' -std=c99'
5
+ if RUBY_ENGINE == "ruby"
6
+ $CFLAGS << " -O3 "
7
+ $CFLAGS << " -std=c99"
7
8
 
8
9
  # ruby.h has some -Wpedantic fails in some cases
9
10
  # (e.g. https://github.com/Shopify/bootsnap/issues/15)
10
- unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
11
- $CFLAGS << ' -Wall'
12
- $CFLAGS << ' -Werror'
13
- $CFLAGS << ' -Wextra'
14
- $CFLAGS << ' -Wpedantic'
11
+ unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
+ $CFLAGS << " -Wall"
13
+ $CFLAGS << " -Werror"
14
+ $CFLAGS << " -Wextra"
15
+ $CFLAGS << " -Wpedantic"
15
16
 
16
- $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
17
- $CFLAGS << ' -Wno-keyword-macro' # hiding return
18
- $CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
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"
19
21
  end
20
22
 
21
23
  create_makefile("bootsnap/bootsnap")
22
24
  else
23
- File.write("Makefile", dummy_makefile($srcdir).join(""))
25
+ File.write("Makefile", dummy_makefile($srcdir).join)
24
26
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  extend(self)
4
5
 
@@ -63,6 +63,7 @@ module Bootsnap
63
63
  loop do
64
64
  job, *args = Marshal.load(@pipe_out)
65
65
  return if job == :exit
66
+
66
67
  @jobs.fetch(job).call(*args)
67
68
  end
68
69
  rescue IOError