bootsnap 1.6.0 → 1.7.3

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: '029d63ba428f470d2cd2ef194d857e20689e7c8e377e2eaaaab83570f366034a'
4
- data.tar.gz: 36a55f9d12dddef6ea3c4739f586c9236d7f4bc677a8b6747dfd7465d46eeca2
3
+ metadata.gz: 7b27cdcd835cd67efea9340d2e09899a3fa553ded267992672183d3bce9cd7de
4
+ data.tar.gz: 73d228cbe15a69fb1a26d76f6375fe564f4de4db6e7f111e6a4d0ec31e09b0a1
5
5
  SHA512:
6
- metadata.gz: 131ec17c4e4912f387c18778250e689467ebfcc80e91eb884237307af1a57a3c89d4fb20c772fbc0330123a796d631861a9cf145bd32c54183077bbc97d7fa6f
7
- data.tar.gz: d0c92454c8a5d8b16a25908fd0ae134fc817c5977958bde8424baca2bb6c96e60eb648c9f770bf7c7f58a3dd98871d4e7b0e3a0950c269100fded45ca9fddfa3
6
+ metadata.gz: e967771cc3387e0297d1654a824f7406e2db1395766dc6df5d2069b78129a71737a497e4681c8988f62ce308e646f967137929266004dbc0e3fbf212da53d3f0
7
+ data.tar.gz: ba6e782a7c4da7d8762cea5501359077b66b68f6b0cbb00e8533557cfd5194ef71bc5016c2273a8ba6ad0aa8e5f434f449c3d0cc8252fde215032807cfd75a9f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.7.3
4
+
5
+ * Disable YAML precompilation when encountering YAML tags. (#351)
6
+
7
+ # 1.7.2
8
+
9
+ * Fix compatibility with msgpack < 1. (#349)
10
+
11
+ # 1.7.1
12
+
13
+ * Warn Ruby 2.5 users if they turn ISeq caching on. (#327, #244)
14
+ * Disable ISeq caching for the whole 2.5.x series again.
15
+ * Better handle hashing of Ruby strings. (#318)
16
+
17
+ # 1.7.0
18
+
19
+ * Fix detection of YAML files in gems.
20
+ * Adds an instrumentation API to monitor cache misses.
21
+ * Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
22
+ * Deprecate the `disable_trace` option.
23
+ * Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
24
+
3
25
  # 1.6.0
4
26
 
5
27
  * Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Bootsnap [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](https://github.com/Shopify/bootsnap/actions)
2
2
 
3
- Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`,
3
+ Bootsnap is a library that plugs into Ruby, with optional support for `YAML`,
4
4
  to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
5
5
 
6
6
  #### Performance
@@ -11,7 +11,7 @@ to optimize and cache expensive computations. See [How Does This Work](#how-does
11
11
  - The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
12
12
  dropping from around 25s to 6.5s.
13
13
  * In Shopify core (a large app), about 25% of this gain can be attributed to `compile_cache_*`
14
- features; 75% to path caching, and ~1% to `disable_trace`. This is fairly representative.
14
+ features; 75% to path caching. This is fairly representative.
15
15
 
16
16
  ## Usage
17
17
 
@@ -29,7 +29,7 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
29
29
  require 'bootsnap/setup'
30
30
  ```
31
31
 
32
- Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
32
+ Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
33
33
  and that directory *must* be writable. Rails will fail to
34
34
  boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
35
35
  unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
@@ -54,15 +54,11 @@ Bootsnap.setup(
54
54
  cache_dir: 'tmp/cache', # Path to your cache
55
55
  development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
56
56
  load_path_cache: true, # Optimize the LOAD_PATH with a cache
57
- autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
58
- disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
59
57
  compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
60
58
  compile_cache_yaml: true # Compile YAML into a cache
61
59
  )
62
60
  ```
63
61
 
64
- **Note that `disable_trace` will break debuggers and tracing.**
65
-
66
62
  **Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
67
63
  'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
68
64
  will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
@@ -72,12 +68,39 @@ speeds up the loading of individual source files, Spring keeps a copy of a pre-b
72
68
  on hand to completely skip parts of the boot process the next time it's needed. The two tools work
73
69
  well together, and are both included in a newly-generated Rails applications by default.
74
70
 
71
+ ### Environment variables
72
+
73
+ `require 'bootsnap/setup'` behavior can be changed using environment variables:
74
+
75
+ - `BOOTSNAP_CACHE_DIR` allows to define the cache location.
76
+ - `DISABLE_BOOTSNAP` allows to entirely disable bootsnap.
77
+ - `DISABLE_BOOTSNAP_LOAD_PATH_CACHE` allows to disable load path caching.
78
+ - `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
79
+ - `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR.
80
+
75
81
  ### Environments
76
82
 
77
83
  All Bootsnap features are enabled in development, test, production, and all other environments according to the configuration in the setup. At Shopify, we use this gem safely in all environments without issue.
78
84
 
79
85
  If you would like to disable any feature for a certain environment, we suggest changing the configuration to take into account the appropriate ENV var or configuration according to your needs.
80
86
 
87
+ ### Instrumentation
88
+
89
+ Bootsnap cache misses can be monitored though a callback:
90
+
91
+ ```ruby
92
+ Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
93
+ ```
94
+
95
+ `event` is either `:miss` or `:stale`. You can also call `Bootsnap.log!` as a shortcut to
96
+ log all events to STDERR.
97
+
98
+ To turn instrumentation back off you can set it to nil:
99
+
100
+ ```ruby
101
+ Bootsnap.instrumentation = nil
102
+ ```
103
+
81
104
  ## How does this work?
82
105
 
83
106
  Bootsnap optimizes methods to cache results of expensive computations, and can be grouped
@@ -85,8 +108,6 @@ into two broad categories:
85
108
 
86
109
  * [Path Pre-Scanning](#path-pre-scanning)
87
110
  * `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans.
88
- * `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are
89
- overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`.
90
111
  * [Compilation caching](#compilation-caching)
91
112
  * `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode
92
113
  compilation.
@@ -125,10 +146,6 @@ open y/foo.rb
125
146
  ...
126
147
  ```
127
148
 
128
- Exactly the same strategy is employed for methods that traverse
129
- `ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to
130
- `Bootsnap.setup`.
131
-
132
149
  The following diagram flowcharts the overrides that make the `*_path_cache` features work.
133
150
 
134
151
  ![Flowchart explaining
@@ -14,6 +14,7 @@
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>
@@ -34,6 +35,10 @@
34
35
 
35
36
  #define MAX_CREATE_TEMPFILE_ATTEMPT 3
36
37
 
38
+ #ifndef RB_UNLIKELY
39
+ #define RB_UNLIKELY(x) (x)
40
+ #endif
41
+
37
42
  /*
38
43
  * An instance of this key is written as the first 64 bytes of each cache file.
39
44
  * The mtime and size members track whether the file contents have changed, and
@@ -88,15 +93,19 @@ static VALUE rb_mBootsnap_CompileCache;
88
93
  static VALUE rb_mBootsnap_CompileCache_Native;
89
94
  static VALUE rb_eBootsnap_CompileCache_Uncompilable;
90
95
  static ID uncompilable;
96
+ static ID instrumentation_method;
97
+ static VALUE sym_miss;
98
+ static VALUE sym_stale;
99
+ static bool instrumentation_enabled = false;
91
100
 
92
101
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
102
+ static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
93
103
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
94
104
  static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
95
105
  static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
96
106
 
97
107
  /* 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]);
108
+ static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
100
109
  static int bs_read_key(int fd, struct bs_cache_key * key);
101
110
  static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
102
111
  static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
@@ -148,7 +157,15 @@ Init_bootsnap(void)
148
157
  current_ruby_platform = get_ruby_platform();
149
158
 
150
159
  uncompilable = rb_intern("__bootsnap_uncompilable__");
160
+ instrumentation_method = rb_intern("_instrument");
161
+
162
+ sym_miss = ID2SYM(rb_intern("miss"));
163
+ rb_global_variable(&sym_miss);
151
164
 
165
+ sym_stale = ID2SYM(rb_intern("stale"));
166
+ rb_global_variable(&sym_stale);
167
+
168
+ rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
152
169
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
153
170
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
154
171
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
@@ -158,6 +175,13 @@ Init_bootsnap(void)
158
175
  umask(current_umask);
159
176
  }
160
177
 
178
+ static VALUE
179
+ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
180
+ {
181
+ instrumentation_enabled = RTEST(enabled);
182
+ return enabled;
183
+ }
184
+
161
185
  /*
162
186
  * Bootsnap's ruby code registers a hook that notifies us via this function
163
187
  * when compile_option changes. These changes invalidate all existing caches.
@@ -186,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
186
210
  * - 32 bits doesn't feel collision-resistant enough; 64 is nice.
187
211
  */
188
212
  static uint64_t
189
- fnv1a_64_iter(uint64_t h, const char *str)
213
+ fnv1a_64_iter_cstr(uint64_t h, const char *str)
190
214
  {
191
215
  unsigned char *s = (unsigned char *)str;
192
216
 
@@ -199,7 +223,21 @@ fnv1a_64_iter(uint64_t h, const char *str)
199
223
  }
200
224
 
201
225
  static uint64_t
202
- fnv1a_64(const char *str)
226
+ fnv1a_64_iter(uint64_t h, const VALUE str)
227
+ {
228
+ unsigned char *s = (unsigned char *)RSTRING_PTR(str);
229
+ unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
230
+
231
+ while (s < str_end) {
232
+ h ^= (uint64_t)*s++;
233
+ h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
234
+ }
235
+
236
+ return h;
237
+ }
238
+
239
+ static uint64_t
240
+ fnv1a_64(const VALUE str)
203
241
  {
204
242
  uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
205
243
  return fnv1a_64_iter(h, str);
@@ -220,7 +258,7 @@ get_ruby_revision(void)
220
258
  } else {
221
259
  uint64_t hash;
222
260
 
223
- hash = fnv1a_64(StringValueCStr(ruby_revision));
261
+ hash = fnv1a_64(ruby_revision);
224
262
  return (uint32_t)(hash >> 32);
225
263
  }
226
264
  }
@@ -240,19 +278,19 @@ get_ruby_platform(void)
240
278
  VALUE ruby_platform;
241
279
 
242
280
  ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
243
- hash = fnv1a_64(RSTRING_PTR(ruby_platform));
281
+ hash = fnv1a_64(ruby_platform);
244
282
 
245
283
  #ifdef _WIN32
246
284
  return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
247
285
  #elif defined(__GLIBC__)
248
- hash = fnv1a_64_iter(hash, gnu_get_libc_version());
286
+ hash = fnv1a_64_iter_cstr(hash, gnu_get_libc_version());
249
287
  return (uint32_t)(hash >> 32);
250
288
  #else
251
289
  struct utsname utsname;
252
290
 
253
291
  /* Not worth crashing if this fails; lose extra cache invalidation potential */
254
292
  if (uname(&utsname) >= 0) {
255
- hash = fnv1a_64_iter(hash, utsname.version);
293
+ hash = fnv1a_64_iter_cstr(hash, utsname.version);
256
294
  }
257
295
 
258
296
  return (uint32_t)(hash >> 32);
@@ -267,13 +305,13 @@ get_ruby_platform(void)
267
305
  * The path will look something like: <cachedir>/12/34567890abcdef
268
306
  */
269
307
  static void
270
- bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
308
+ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
271
309
  {
272
310
  uint64_t hash = fnv1a_64(path);
273
311
  uint8_t first_byte = (hash >> (64 - 8));
274
312
  uint64_t remainder = hash & 0x00ffffffffffffff;
275
313
 
276
- sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
314
+ sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
277
315
  }
278
316
 
279
317
  /*
@@ -319,7 +357,7 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
319
357
  char cache_path[MAX_CACHEPATH_SIZE];
320
358
 
321
359
  /* generate cache path to cache_path */
322
- bs_cache_path(cachedir, path, &cache_path);
360
+ bs_cache_path(cachedir, path_v, &cache_path);
323
361
 
324
362
  return bs_fetch(path, path_v, cache_path, handler, args);
325
363
  }
@@ -346,7 +384,7 @@ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
346
384
  char cache_path[MAX_CACHEPATH_SIZE];
347
385
 
348
386
  /* generate cache path to cache_path */
349
- bs_cache_path(cachedir, path, &cache_path);
387
+ bs_cache_path(cachedir, path_v, &cache_path);
350
388
 
351
389
  return bs_precompile(path, path_v, cache_path, handler);
352
390
  }
@@ -386,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
386
424
  }
387
425
 
388
426
  #define ERROR_WITH_ERRNO -1
389
- #define CACHE_MISSING_OR_INVALID -2
427
+ #define CACHE_MISS -2
428
+ #define CACHE_STALE -3
390
429
 
391
430
  /*
392
431
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -394,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
394
433
  *
395
434
  * Possible return values:
396
435
  * - 0 (OK, key was loaded)
397
- * - CACHE_MISSING_OR_INVALID (-2)
398
436
  * - ERROR_WITH_ERRNO (-1, errno is set)
437
+ * - CACHE_MISS (-2)
438
+ * - CACHE_STALE (-3)
399
439
  */
400
440
  static int
401
441
  bs_read_key(int fd, struct bs_cache_key * key)
402
442
  {
403
443
  ssize_t nread = read(fd, key, KEY_SIZE);
404
444
  if (nread < 0) return ERROR_WITH_ERRNO;
405
- if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID;
445
+ if (nread < KEY_SIZE) return CACHE_STALE;
406
446
  return 0;
407
447
  }
408
448
 
@@ -412,7 +452,8 @@ bs_read_key(int fd, struct bs_cache_key * key)
412
452
  *
413
453
  * Possible return values:
414
454
  * - 0 (OK, key was loaded)
415
- * - CACHE_MISSING_OR_INVALID (-2)
455
+ * - CACHE_MISS (-2)
456
+ * - CACHE_STALE (-3)
416
457
  * - ERROR_WITH_ERRNO (-1, errno is set)
417
458
  */
418
459
  static int
@@ -423,7 +464,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
423
464
  fd = open(path, O_RDONLY);
424
465
  if (fd < 0) {
425
466
  *errno_provenance = "bs_fetch:open_cache_file:open";
426
- if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
467
+ if (errno == ENOENT) return CACHE_MISS;
427
468
  return ERROR_WITH_ERRNO;
428
469
  }
429
470
  #ifdef _WIN32
@@ -478,7 +519,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
478
519
  goto done;
479
520
  }
480
521
  if (nread != data_size) {
481
- ret = CACHE_MISSING_OR_INVALID;
522
+ ret = CACHE_STALE;
482
523
  goto done;
483
524
  }
484
525
 
@@ -672,14 +713,22 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
672
713
 
673
714
  /* Open the cache key if it exists, and read its cache key in */
674
715
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
675
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
716
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
676
717
  /* This is ok: valid_cache remains false, we re-populate it. */
718
+ if (RB_UNLIKELY(instrumentation_enabled)) {
719
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
720
+ }
677
721
  } else if (cache_fd < 0) {
678
722
  goto fail_errno;
679
723
  } else {
680
724
  /* True if the cache existed and no invalidating changes have occurred since
681
725
  * it was generated. */
682
726
  valid_cache = cache_key_equal(&current_key, &cached_key);
727
+ if (RB_UNLIKELY(instrumentation_enabled)) {
728
+ if (!valid_cache) {
729
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
730
+ }
731
+ }
683
732
  }
684
733
 
685
734
  if (valid_cache) {
@@ -688,10 +737,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
688
737
  cache_fd, (ssize_t)cached_key.data_size, handler, args,
689
738
  &output_data, &exception_tag, &errno_provenance
690
739
  );
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 */
740
+ if (exception_tag != 0) goto raise;
741
+ else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
742
+ else if (res == ERROR_WITH_ERRNO) goto fail_errno;
743
+ else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
695
744
  }
696
745
  close(cache_fd);
697
746
  cache_fd = -1;
@@ -778,7 +827,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
778
827
 
779
828
  /* Open the cache key if it exists, and read its cache key in */
780
829
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
781
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
830
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
782
831
  /* This is ok: valid_cache remains false, we re-populate it. */
783
832
  } else if (cache_fd < 0) {
784
833
  goto fail;
@@ -1,19 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
  require("mkmf")
3
- $CFLAGS << ' -O3 '
4
- $CFLAGS << ' -std=c99'
5
3
 
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'
4
+ if RUBY_ENGINE == 'ruby'
5
+ $CFLAGS << ' -O3 '
6
+ $CFLAGS << ' -std=c99'
13
7
 
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
8
+ # ruby.h has some -Wpedantic fails in some cases
9
+ # (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'
15
+
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
19
+ end
18
20
 
19
- create_makefile("bootsnap/bootsnap")
21
+ create_makefile("bootsnap/bootsnap")
22
+ else
23
+ File.write("Makefile", dummy_makefile($srcdir).join(""))
24
+ end
data/lib/bootsnap.rb CHANGED
@@ -8,25 +8,60 @@ require_relative('bootsnap/compile_cache')
8
8
  module Bootsnap
9
9
  InvalidConfiguration = Class.new(StandardError)
10
10
 
11
+ class << self
12
+ attr_reader :logger
13
+ end
14
+
15
+ def self.log!
16
+ self.logger = $stderr.method(:puts)
17
+ end
18
+
19
+ def self.logger=(logger)
20
+ @logger = logger
21
+ if logger.respond_to?(:debug)
22
+ self.instrumentation = ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
23
+ else
24
+ self.instrumentation = ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
25
+ end
26
+ end
27
+
28
+ def self.instrumentation=(callback)
29
+ @instrumentation = callback
30
+ self.instrumentation_enabled = !!callback
31
+ end
32
+
33
+ def self._instrument(event, path)
34
+ @instrumentation.call(event, path)
35
+ end
36
+
11
37
  def self.setup(
12
38
  cache_dir:,
13
39
  development_mode: true,
14
40
  load_path_cache: true,
15
- autoload_paths_cache: true,
16
- disable_trace: false,
41
+ autoload_paths_cache: nil,
42
+ disable_trace: nil,
17
43
  compile_cache_iseq: true,
18
44
  compile_cache_yaml: true
19
45
  )
20
- if autoload_paths_cache && !load_path_cache
21
- raise(InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'")
46
+ unless autoload_paths_cache.nil?
47
+ warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
48
+ "If you use Zeitwerk this option is useless, and if you are still using the classic autoloader " \
49
+ "upgrading is recommended."
22
50
  end
23
51
 
24
- setup_disable_trace if disable_trace
52
+ unless disable_trace.nil?
53
+ warn "[DEPRECATED] Bootsnap's `disable_trace:` option is deprecated and will be removed. " \
54
+ "If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
55
+ end
56
+
57
+ if compile_cache_iseq && !iseq_cache_supported?
58
+ warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \
59
+ "to turn `compile_cache_iseq` off on Ruby 2.5"
60
+ end
25
61
 
26
62
  Bootsnap::LoadPathCache.setup(
27
63
  cache_path: cache_dir + '/bootsnap/load-path-cache',
28
64
  development_mode: development_mode,
29
- active_support: autoload_paths_cache
30
65
  ) if load_path_cache
31
66
 
32
67
  Bootsnap::CompileCache.setup(
@@ -36,14 +71,51 @@ module Bootsnap
36
71
  )
37
72
  end
38
73
 
39
- def self.setup_disable_trace
40
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
41
- warn(
42
- "from #{caller_locations(1, 1)[0]}: The 'disable_trace' method is not allowed with this Ruby version. " \
43
- "current: #{RUBY_VERSION}, allowed version: < 2.5.0",
74
+ def self.iseq_cache_supported?
75
+ return @iseq_cache_supported if defined? @iseq_cache_supported
76
+
77
+ ruby_version = Gem::Version.new(RUBY_VERSION)
78
+ @iseq_cache_supported = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
79
+ end
80
+
81
+ def self.default_setup
82
+ env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
83
+ development_mode = ['', nil, 'development'].include?(env)
84
+
85
+ unless ENV['DISABLE_BOOTSNAP']
86
+ cache_dir = ENV['BOOTSNAP_CACHE_DIR']
87
+ unless cache_dir
88
+ config_dir_frame = caller.detect do |line|
89
+ line.include?('/config/')
90
+ end
91
+
92
+ unless config_dir_frame
93
+ $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
94
+ $stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
95
+ $stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
96
+
97
+ raise("couldn't infer bootsnap cache directory")
98
+ end
99
+
100
+ path = config_dir_frame.split(/:\d+:/).first
101
+ path = File.dirname(path) until File.basename(path) == 'config'
102
+ app_root = File.dirname(path)
103
+
104
+ cache_dir = File.join(app_root, 'tmp', 'cache')
105
+ end
106
+
107
+
108
+ setup(
109
+ cache_dir: cache_dir,
110
+ development_mode: development_mode,
111
+ load_path_cache: !ENV['DISABLE_BOOTSNAP_LOAD_PATH_CACHE'],
112
+ compile_cache_iseq: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'] && iseq_cache_supported?,
113
+ compile_cache_yaml: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'],
44
114
  )
45
- else
46
- RubyVM::InstructionSequence.compile_option = { trace_instruction: false }
115
+
116
+ if ENV['BOOTSNAP_LOG']
117
+ log!
118
+ end
47
119
  end
48
120
  end
49
121
  end
data/lib/bootsnap/cli.rb CHANGED
@@ -60,7 +60,7 @@ module Bootsnap
60
60
 
61
61
  # Gems that include YAML files usually don't put them in `lib/`.
62
62
  # So we look at the gem root.
63
- gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gem\/[^/]+}
63
+ gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems\/[^/]+}
64
64
  gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
65
65
  precompile_yaml_files(gem_paths, exclude: gem_exclude)
66
66
  end
@@ -121,7 +121,7 @@ module Bootsnap
121
121
  if !exclude || !exclude.match?(path)
122
122
  list_files(path, '**/*.{yml,yaml}').each do |yaml_file|
123
123
  # We ignore hidden files to not match the various .ci.yml files
124
- if !yaml_file.include?('/.') && (!exclude || !exclude.match?(yaml_file))
124
+ if !File.basename(yaml_file).start_with?('.') && (!exclude || !exclude.match?(yaml_file))
125
125
  @work_pool.push(:yaml, yaml_file)
126
126
  end
127
127
  end
@@ -8,8 +8,7 @@ module Bootsnap
8
8
  attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
9
9
 
10
10
  def input_to_storage(contents, _)
11
- raise(Uncompilable) if contents.index("!ruby/object")
12
- obj = ::YAML.load(contents)
11
+ obj = strict_load(contents)
13
12
  msgpack_factory.dump(obj)
14
13
  rescue NoMethodError, RangeError
15
14
  # The object included things that we can't serialize
@@ -27,6 +26,13 @@ module Bootsnap
27
26
  ::YAML.load(data, **(kwargs || {}))
28
27
  end
29
28
 
29
+ def strict_load(payload, *args)
30
+ ast = ::YAML.parse(payload)
31
+ return ast unless ast
32
+ strict_visitor.create(*args).visit(ast)
33
+ end
34
+ ruby2_keywords :strict_load if respond_to?(:ruby2_keywords, true)
35
+
30
36
  def precompile(path, cache_dir: YAML.cache_dir)
31
37
  Bootsnap::CompileCache::Native.precompile(
32
38
  cache_dir,
@@ -51,22 +57,25 @@ module Bootsnap
51
57
  # see: https://github.com/msgpack/msgpack-ruby/pull/122
52
58
  factory = MessagePack::Factory.new
53
59
  factory.register_type(0x00, Symbol)
54
- factory.register_type(
55
- MessagePack::Timestamp::TYPE, # or just -1
56
- Time,
57
- packer: MessagePack::Time::Packer,
58
- unpacker: MessagePack::Time::Unpacker
59
- )
60
60
 
61
- marshal_fallback = {
62
- packer: ->(value) { Marshal.dump(value) },
63
- unpacker: ->(payload) { Marshal.load(payload) },
64
- }
65
- {
66
- Date => 0x01,
67
- Regexp => 0x02,
68
- }.each do |type, code|
69
- factory.register_type(code, type, marshal_fallback)
61
+ if defined? MessagePack::Timestamp
62
+ factory.register_type(
63
+ MessagePack::Timestamp::TYPE, # or just -1
64
+ Time,
65
+ packer: MessagePack::Time::Packer,
66
+ unpacker: MessagePack::Time::Unpacker
67
+ )
68
+
69
+ marshal_fallback = {
70
+ packer: ->(value) { Marshal.dump(value) },
71
+ unpacker: ->(payload) { Marshal.load(payload) },
72
+ }
73
+ {
74
+ Date => 0x01,
75
+ Regexp => 0x02,
76
+ }.each do |type, code|
77
+ factory.register_type(code, type, marshal_fallback)
78
+ end
70
79
  end
71
80
 
72
81
  self.msgpack_factory = factory
@@ -83,6 +92,17 @@ module Bootsnap
83
92
  end
84
93
  self.supported_options.freeze
85
94
  end
95
+
96
+ def strict_visitor
97
+ self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
98
+ def visit(target)
99
+ if target.tag
100
+ raise Uncompilable, "YAML tags are not supported: #{target.tag}"
101
+ end
102
+ super
103
+ end
104
+ end
105
+ end
86
106
  end
87
107
 
88
108
  module Patch
@@ -96,7 +116,7 @@ module Bootsnap
96
116
  begin
97
117
  ::Bootsnap::CompileCache::Native.fetch(
98
118
  Bootsnap::CompileCache::YAML.cache_dir,
99
- path,
119
+ File.realpath(path),
100
120
  ::Bootsnap::CompileCache::YAML,
101
121
  kwargs,
102
122
  )
@@ -28,10 +28,9 @@ module Bootsnap
28
28
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
29
29
 
30
30
  class << self
31
- attr_reader(:load_path_cache, :autoload_paths_cache,
32
- :loaded_features_index, :realpath_cache)
31
+ attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
33
32
 
34
- def setup(cache_path:, development_mode:, active_support: true)
33
+ def setup(cache_path:, development_mode:)
35
34
  unless supported?
36
35
  warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
37
36
  return
@@ -45,18 +44,6 @@ module Bootsnap
45
44
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
46
45
  require_relative('load_path_cache/core_ext/kernel_require')
47
46
  require_relative('load_path_cache/core_ext/loaded_features')
48
-
49
- if active_support
50
- # this should happen after setting up the initial cache because it
51
- # loads a lot of code. It's better to do after +require+ is optimized.
52
- require('active_support/dependencies')
53
- @autoload_paths_cache = Cache.new(
54
- store,
55
- ::ActiveSupport::Dependencies.autoload_paths,
56
- development_mode: development_mode
57
- )
58
- require_relative('load_path_cache/core_ext/active_support')
59
- end
60
47
  end
61
48
 
62
49
  def supported?
@@ -10,8 +10,8 @@ module Bootsnap
10
10
  def initialize(store, path_obj, development_mode: false)
11
11
  @development_mode = development_mode
12
12
  @store = store
13
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
14
- @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
+ @mutex = Mutex.new
14
+ @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
15
15
  @has_relative_paths = nil
16
16
  reinitialize
17
17
  end
@@ -178,25 +178,42 @@ module Bootsnap
178
178
 
179
179
  if DLEXT2
180
180
  def search_index(f)
181
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
181
+ try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
182
182
  end
183
183
 
184
184
  def maybe_append_extension(f)
185
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
185
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
186
186
  end
187
187
  else
188
188
  def search_index(f)
189
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
189
+ try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
190
190
  end
191
191
 
192
192
  def maybe_append_extension(f)
193
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
193
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
194
194
  end
195
195
  end
196
196
 
197
- def try_index(f)
198
- if (p = @index[f])
199
- "#{p}/#{f}"
197
+ s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
198
+ if s.respond_to?(:-@)
199
+ if (-s).equal?(s) && (-s.dup).equal?(s)
200
+ def try_index(f)
201
+ if (p = @index[f])
202
+ -(File.join(p, f).freeze)
203
+ end
204
+ end
205
+ else
206
+ def try_index(f)
207
+ if (p = @index[f])
208
+ -File.join(p, f).untaint
209
+ end
210
+ end
211
+ end
212
+ else
213
+ def try_index(f)
214
+ if (p = @index[f])
215
+ File.join(p, f)
216
+ end
200
217
  end
201
218
  end
202
219
 
@@ -26,7 +26,7 @@ module Bootsnap
26
26
  class LoadedFeaturesIndex
27
27
  def initialize
28
28
  @lfi = {}
29
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
29
+ @mutex = Mutex.new
30
30
 
31
31
  # In theory the user could mutate $LOADED_FEATURES and invalidate our
32
32
  # cache. If this ever comes up in practice — or if you, the
@@ -33,10 +33,10 @@ module Bootsnap
33
33
  requirables = []
34
34
  walk(path, nil) do |relative_path, absolute_path, is_directory|
35
35
  if is_directory
36
- dirs << relative_path
36
+ dirs << os_path(relative_path)
37
37
  !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
38
  elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
39
- requirables << relative_path
39
+ requirables << os_path(relative_path)
40
40
  end
41
41
  end
42
42
  [requirables, dirs]
@@ -45,7 +45,7 @@ module Bootsnap
45
45
  def walk(absolute_dir_path, relative_dir_path, &block)
46
46
  Dir.foreach(absolute_dir_path) do |name|
47
47
  next if name.start_with?('.')
48
- relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
48
+ relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
49
49
 
50
50
  absolute_path = "#{absolute_dir_path}/#{name}"
51
51
  if File.directory?(absolute_path)
@@ -57,6 +57,17 @@ module Bootsnap
57
57
  end
58
58
  end
59
59
  end
60
+
61
+ if RUBY_VERSION >= '3.1'
62
+ def os_path(path)
63
+ path.freeze
64
+ end
65
+ else
66
+ def os_path(path)
67
+ path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
68
+ path.freeze
69
+ end
70
+ end
60
71
  end
61
72
  end
62
73
  end
@@ -12,8 +12,7 @@ module Bootsnap
12
12
 
13
13
  def initialize(store_path)
14
14
  @store_path = store_path
15
- # TODO: Remove conditional once Ruby 2.2 support is dropped.
16
- @txn_mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new
15
+ @txn_mutex = Mutex.new
17
16
  @dirty = false
18
17
  load_data
19
18
  end
@@ -63,12 +62,18 @@ module Bootsnap
63
62
 
64
63
  def load_data
65
64
  @data = begin
66
- MessagePack.load(File.binread(@store_path))
67
- # handle malformed data due to upgrade incompatibility
68
- rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
69
- {}
70
- rescue ArgumentError => e
71
- e.message =~ /negative array size/ ? {} : raise
65
+ File.open(@store_path, encoding: Encoding::BINARY) do |io|
66
+ MessagePack.load(io)
67
+ end
68
+ # handle malformed data due to upgrade incompatibility
69
+ rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
70
+ {}
71
+ rescue ArgumentError => error
72
+ if error.message =~ /negative array size/
73
+ {}
74
+ else
75
+ raise
76
+ end
72
77
  end
73
78
  end
74
79
 
@@ -80,7 +85,9 @@ module Bootsnap
80
85
  exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
81
86
  # `encoding:` looks redundant wrt `binwrite`, but necessary on windows
82
87
  # because binary is part of mode.
83
- File.binwrite(tmp, MessagePack.dump(@data), mode: exclusive_write, encoding: Encoding::BINARY)
88
+ File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
89
+ MessagePack.dump(@data, io, freeze: true)
90
+ end
84
91
  FileUtils.mv(tmp, @store_path)
85
92
  rescue Errno::EEXIST
86
93
  retry
@@ -1,39 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative('../bootsnap')
3
3
 
4
- env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
5
- development_mode = ['', nil, 'development'].include?(env)
6
-
7
- cache_dir = ENV['BOOTSNAP_CACHE_DIR']
8
- unless cache_dir
9
- config_dir_frame = caller.detect do |line|
10
- line.include?('/config/')
11
- end
12
-
13
- unless config_dir_frame
14
- $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
15
- $stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
16
- $stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
17
-
18
- raise("couldn't infer bootsnap cache directory")
19
- end
20
-
21
- path = config_dir_frame.split(/:\d+:/).first
22
- path = File.dirname(path) until File.basename(path) == 'config'
23
- app_root = File.dirname(path)
24
-
25
- cache_dir = File.join(app_root, 'tmp', 'cache')
26
- end
27
-
28
- ruby_version = Gem::Version.new(RUBY_VERSION)
29
- iseq_cache_enabled = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
30
-
31
- Bootsnap.setup(
32
- cache_dir: cache_dir,
33
- development_mode: development_mode,
34
- load_path_cache: true,
35
- autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
36
- disable_trace: false,
37
- compile_cache_iseq: iseq_cache_enabled,
38
- compile_cache_yaml: true,
39
- )
4
+ Bootsnap.default_setup
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Bootsnap
3
- VERSION = "1.6.0"
3
+ VERSION = "1.7.3"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-27 00:00:00.000000000 Z
11
+ date: 2021-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -121,7 +121,6 @@ files:
121
121
  - lib/bootsnap/load_path_cache.rb
122
122
  - lib/bootsnap/load_path_cache/cache.rb
123
123
  - lib/bootsnap/load_path_cache/change_observer.rb
124
- - lib/bootsnap/load_path_cache/core_ext/active_support.rb
125
124
  - lib/bootsnap/load_path_cache/core_ext/kernel_require.rb
126
125
  - lib/bootsnap/load_path_cache/core_ext/loaded_features.rb
127
126
  - lib/bootsnap/load_path_cache/loaded_features_index.rb
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
- module Bootsnap
3
- module LoadPathCache
4
- module CoreExt
5
- module ActiveSupport
6
- def self.without_bootsnap_cache
7
- prev = Thread.current[:without_bootsnap_cache] || false
8
- Thread.current[:without_bootsnap_cache] = true
9
- yield
10
- ensure
11
- Thread.current[:without_bootsnap_cache] = prev
12
- end
13
-
14
- def self.allow_bootsnap_retry(allowed)
15
- prev = Thread.current[:without_bootsnap_retry] || false
16
- Thread.current[:without_bootsnap_retry] = !allowed
17
- yield
18
- ensure
19
- Thread.current[:without_bootsnap_retry] = prev
20
- end
21
-
22
- module ClassMethods
23
- def autoload_paths=(o)
24
- super
25
- Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o)
26
- end
27
-
28
- def search_for_file(path)
29
- return super if Thread.current[:without_bootsnap_cache]
30
- begin
31
- Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
32
- rescue Bootsnap::LoadPathCache::ReturnFalse
33
- nil # doesn't really apply here
34
- rescue Bootsnap::LoadPathCache::FallbackScan
35
- nil # doesn't really apply here
36
- end
37
- end
38
-
39
- def autoloadable_module?(path_suffix)
40
- Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix)
41
- end
42
-
43
- def remove_constant(const)
44
- CoreExt::ActiveSupport.without_bootsnap_cache { super }
45
- end
46
-
47
- def require_or_load(*)
48
- CoreExt::ActiveSupport.allow_bootsnap_retry(true) do
49
- super
50
- end
51
- end
52
-
53
- # If we can't find a constant using the patched implementation of
54
- # search_for_file, try again with the default implementation.
55
- #
56
- # These methods call search_for_file, and we want to modify its
57
- # behaviour. The gymnastics here are a bit awkward, but it prevents
58
- # 200+ lines of monkeypatches.
59
- def load_missing_constant(from_mod, const_name)
60
- CoreExt::ActiveSupport.allow_bootsnap_retry(false) do
61
- super
62
- end
63
- rescue NameError => e
64
- raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
65
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
66
-
67
- # This function can end up called recursively, we only want to
68
- # retry at the top-level.
69
- raise(e) if Thread.current[:without_bootsnap_retry]
70
- # If we already had cache disabled, there's no use retrying
71
- raise(e) if Thread.current[:without_bootsnap_cache]
72
- # NoMethodError is a NameError, but we only want to handle actual
73
- # NameError instances.
74
- raise(e) unless e.class == NameError
75
- # We can only confidently handle cases when *this* constant fails
76
- # to load, not other constants referred to by it.
77
- raise(e) unless e.name == const_name
78
- # If the constant was actually loaded, something else went wrong?
79
- raise(e) if from_mod.const_defined?(const_name)
80
- CoreExt::ActiveSupport.without_bootsnap_cache { super }
81
- end
82
-
83
- # Signature has changed a few times over the years; easiest to not
84
- # reiterate it with version polymorphism here...
85
- def depend_on(*)
86
- super
87
- rescue LoadError => e
88
- raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
89
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
90
-
91
- # If we already had cache disabled, there's no use retrying
92
- raise(e) if Thread.current[:without_bootsnap_cache]
93
- CoreExt::ActiveSupport.without_bootsnap_cache { super }
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end
100
-
101
- module ActiveSupport
102
- module Dependencies
103
- class << self
104
- prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
105
- end
106
- end
107
- end