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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +30 -13
- data/ext/bootsnap/bootsnap.c +73 -24
- data/ext/bootsnap/extconf.rb +19 -14
- data/lib/bootsnap.rb +85 -13
- data/lib/bootsnap/cli.rb +2 -2
- data/lib/bootsnap/compile_cache/yaml.rb +38 -18
- data/lib/bootsnap/load_path_cache.rb +2 -15
- data/lib/bootsnap/load_path_cache/cache.rb +26 -9
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +1 -1
- data/lib/bootsnap/load_path_cache/path_scanner.rb +14 -3
- data/lib/bootsnap/load_path_cache/store.rb +16 -9
- data/lib/bootsnap/setup.rb +1 -36
- data/lib/bootsnap/version.rb +1 -1
- metadata +2 -3
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b27cdcd835cd67efea9340d2e09899a3fa553ded267992672183d3bce9cd7de
|
4
|
+
data.tar.gz: 73d228cbe15a69fb1a26d76f6375fe564f4de4db6e7f111e6a4d0ec31e09b0a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 `
|
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
|
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
|
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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 =
|
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 =
|
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
|
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/%
|
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,
|
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,
|
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
|
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
|
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
|
-
* -
|
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
|
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 =
|
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 ==
|
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(¤t_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)
|
692
|
-
else if (res ==
|
693
|
-
else if (res == ERROR_WITH_ERRNO)
|
694
|
-
else if (!NIL_P(output_data))
|
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 ==
|
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;
|
data/ext/bootsnap/extconf.rb
CHANGED
@@ -1,19 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require("mkmf")
|
3
|
-
$CFLAGS << ' -O3 '
|
4
|
-
$CFLAGS << ' -std=c99'
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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:
|
16
|
-
disable_trace:
|
41
|
+
autoload_paths_cache: nil,
|
42
|
+
disable_trace: nil,
|
17
43
|
compile_cache_iseq: true,
|
18
44
|
compile_cache_yaml: true
|
19
45
|
)
|
20
|
-
|
21
|
-
|
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
|
-
|
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.
|
40
|
-
if
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
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/)?
|
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.
|
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
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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, :
|
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
|
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 =
|
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(
|
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(
|
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(
|
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(
|
193
|
+
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
|
-
|
198
|
-
|
199
|
-
|
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 =
|
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 ?
|
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
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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.
|
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
|
data/lib/bootsnap/setup.rb
CHANGED
@@ -1,39 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative('../bootsnap')
|
3
3
|
|
4
|
-
|
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
|
data/lib/bootsnap/version.rb
CHANGED
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.
|
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-
|
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
|