bootsnap 1.5.1 → 1.7.1
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 +31 -13
- data/ext/bootsnap/bootsnap.c +176 -36
- data/ext/bootsnap/extconf.rb +19 -14
- data/lib/bootsnap.rb +87 -15
- data/lib/bootsnap/cli.rb +112 -17
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/compile_cache/iseq.rb +9 -1
- data/lib/bootsnap/compile_cache/yaml.rb +37 -16
- data/lib/bootsnap/load_path_cache.rb +2 -15
- data/lib/bootsnap/setup.rb +1 -36
- data/lib/bootsnap/version.rb +1 -1
- metadata +3 -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: b8814f0b4b0ea307767f1a2c10ba813cdf2c28748b2175015d73c52f7754574c
|
4
|
+
data.tar.gz: 32958adc9d09fc2cff2764efd545e6c3c670a7ca453894f34c2dfae48f7b4467
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b8dc0aba09934dc35d6011142b8edcb63043219770993ee9db6117124157001d3f62bc5dd72bad8a9cd9538e90e36f85884262378deea054a38e461b670e211
|
7
|
+
data.tar.gz: 1e5f2337755ac7f7f52dc155612d2c76c3b17828f2c775e4dedc606cae3d280a16232a9ae1dbee00987cb5846d64277e37079589c96e3d264aca615318d5ce1e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
* Warn Ruby 2.5 users if they turn ISeq caching on. (#327, #244)
|
4
|
+
* Disable ISeq caching for the whole 2.5.x series again.
|
5
|
+
* Better handle hashing of Ruby strings. (#318)
|
6
|
+
|
7
|
+
# 1.7.0
|
8
|
+
|
9
|
+
* Fix detection of YAML files in gems.
|
10
|
+
* Adds an instrumentation API to monitor cache misses.
|
11
|
+
* Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
|
12
|
+
* Deprecate the `disable_trace` option.
|
13
|
+
* Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
|
14
|
+
|
15
|
+
# 1.6.0
|
16
|
+
|
17
|
+
* Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
|
18
|
+
* `bootsnap precompile` CLI use multiple processes to complete faster. (#341)
|
19
|
+
* `bootsnap precompile` CLI also precompile YAML files. (#340)
|
20
|
+
* Changed the load path cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-load-path-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/load-path-cache` for ease of use. (#334)
|
21
|
+
* Changed the compile cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-compile-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/compile-cache` for ease of use. (#334)
|
22
|
+
|
1
23
|
# 1.5.1
|
2
24
|
|
3
25
|
* Workaround a Ruby bug in InstructionSequence.compile_file. (#332)
|
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,8 @@ 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
|
32
|
+
Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
|
33
|
+
and that directory *must* be writable. Rails will fail to
|
33
34
|
boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
|
34
35
|
unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
|
35
36
|
|
@@ -53,15 +54,11 @@ Bootsnap.setup(
|
|
53
54
|
cache_dir: 'tmp/cache', # Path to your cache
|
54
55
|
development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
|
55
56
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
56
|
-
autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
|
57
|
-
disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
|
58
57
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
59
58
|
compile_cache_yaml: true # Compile YAML into a cache
|
60
59
|
)
|
61
60
|
```
|
62
61
|
|
63
|
-
**Note that `disable_trace` will break debuggers and tracing.**
|
64
|
-
|
65
62
|
**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
|
66
63
|
'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
|
67
64
|
will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
|
@@ -71,12 +68,39 @@ speeds up the loading of individual source files, Spring keeps a copy of a pre-b
|
|
71
68
|
on hand to completely skip parts of the boot process the next time it's needed. The two tools work
|
72
69
|
well together, and are both included in a newly-generated Rails applications by default.
|
73
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
|
+
|
74
81
|
### Environments
|
75
82
|
|
76
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.
|
77
84
|
|
78
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.
|
79
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
|
+
|
80
104
|
## How does this work?
|
81
105
|
|
82
106
|
Bootsnap optimizes methods to cache results of expensive computations, and can be grouped
|
@@ -84,8 +108,6 @@ into two broad categories:
|
|
84
108
|
|
85
109
|
* [Path Pre-Scanning](#path-pre-scanning)
|
86
110
|
* `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans.
|
87
|
-
* `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are
|
88
|
-
overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`.
|
89
111
|
* [Compilation caching](#compilation-caching)
|
90
112
|
* `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode
|
91
113
|
compilation.
|
@@ -124,10 +146,6 @@ open y/foo.rb
|
|
124
146
|
...
|
125
147
|
```
|
126
148
|
|
127
|
-
Exactly the same strategy is employed for methods that traverse
|
128
|
-
`ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to
|
129
|
-
`Bootsnap.setup`.
|
130
|
-
|
131
149
|
The following diagram flowcharts the overrides that make the `*_path_cache` features work.
|
132
150
|
|
133
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
|
@@ -70,7 +75,7 @@ struct bs_cache_key {
|
|
70
75
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
71
76
|
|
72
77
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
73
|
-
static const uint32_t current_version =
|
78
|
+
static const uint32_t current_version = 3;
|
74
79
|
|
75
80
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
76
81
|
* new OS ABI, etc. */
|
@@ -88,17 +93,23 @@ 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);
|
105
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
95
106
|
|
96
107
|
/* Helpers */
|
97
|
-
static
|
98
|
-
static void bs_cache_path(const char * cachedir, const char * path, const char * extra, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
108
|
+
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
99
109
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
100
110
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
101
111
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
112
|
+
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
102
113
|
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
103
114
|
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
104
115
|
static uint32_t get_ruby_revision(void);
|
@@ -146,15 +157,31 @@ Init_bootsnap(void)
|
|
146
157
|
current_ruby_platform = get_ruby_platform();
|
147
158
|
|
148
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);
|
164
|
+
|
165
|
+
sym_stale = ID2SYM(rb_intern("stale"));
|
166
|
+
rb_global_variable(&sym_stale);
|
149
167
|
|
168
|
+
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
150
169
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
151
170
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
171
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
152
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
153
173
|
|
154
174
|
current_umask = umask(0777);
|
155
175
|
umask(current_umask);
|
156
176
|
}
|
157
177
|
|
178
|
+
static VALUE
|
179
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
180
|
+
{
|
181
|
+
instrumentation_enabled = RTEST(enabled);
|
182
|
+
return enabled;
|
183
|
+
}
|
184
|
+
|
158
185
|
/*
|
159
186
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
160
187
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -183,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
183
210
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
184
211
|
*/
|
185
212
|
static uint64_t
|
186
|
-
|
213
|
+
fnv1a_64_iter_cstr(uint64_t h, const char *str)
|
187
214
|
{
|
188
215
|
unsigned char *s = (unsigned char *)str;
|
189
216
|
|
@@ -196,7 +223,21 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
196
223
|
}
|
197
224
|
|
198
225
|
static uint64_t
|
199
|
-
|
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)
|
200
241
|
{
|
201
242
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
202
243
|
return fnv1a_64_iter(h, str);
|
@@ -217,7 +258,7 @@ get_ruby_revision(void)
|
|
217
258
|
} else {
|
218
259
|
uint64_t hash;
|
219
260
|
|
220
|
-
hash = fnv1a_64(
|
261
|
+
hash = fnv1a_64(ruby_revision);
|
221
262
|
return (uint32_t)(hash >> 32);
|
222
263
|
}
|
223
264
|
}
|
@@ -237,19 +278,19 @@ get_ruby_platform(void)
|
|
237
278
|
VALUE ruby_platform;
|
238
279
|
|
239
280
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
240
|
-
hash = fnv1a_64(
|
281
|
+
hash = fnv1a_64(ruby_platform);
|
241
282
|
|
242
283
|
#ifdef _WIN32
|
243
284
|
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
244
285
|
#elif defined(__GLIBC__)
|
245
|
-
hash =
|
286
|
+
hash = fnv1a_64_iter_cstr(hash, gnu_get_libc_version());
|
246
287
|
return (uint32_t)(hash >> 32);
|
247
288
|
#else
|
248
289
|
struct utsname utsname;
|
249
290
|
|
250
291
|
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
251
292
|
if (uname(&utsname) >= 0) {
|
252
|
-
hash =
|
293
|
+
hash = fnv1a_64_iter_cstr(hash, utsname.version);
|
253
294
|
}
|
254
295
|
|
255
296
|
return (uint32_t)(hash >> 32);
|
@@ -264,17 +305,13 @@ get_ruby_platform(void)
|
|
264
305
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
265
306
|
*/
|
266
307
|
static void
|
267
|
-
bs_cache_path(const char * cachedir, const
|
308
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
268
309
|
{
|
269
310
|
uint64_t hash = fnv1a_64(path);
|
270
|
-
if (extra) {
|
271
|
-
hash ^= fnv1a_64(extra);
|
272
|
-
}
|
273
|
-
|
274
311
|
uint8_t first_byte = (hash >> (64 - 8));
|
275
312
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
276
313
|
|
277
|
-
sprintf(*cache_path, "%s/%
|
314
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
278
315
|
}
|
279
316
|
|
280
317
|
/*
|
@@ -318,18 +355,39 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
|
|
318
355
|
char * cachedir = RSTRING_PTR(cachedir_v);
|
319
356
|
char * path = RSTRING_PTR(path_v);
|
320
357
|
char cache_path[MAX_CACHEPATH_SIZE];
|
321
|
-
char * extra = NULL;
|
322
|
-
if (!NIL_P(args)) {
|
323
|
-
VALUE args_serial = rb_marshal_dump(args, Qnil);
|
324
|
-
extra = RSTRING_PTR(args_serial);
|
325
|
-
}
|
326
358
|
|
327
359
|
/* generate cache path to cache_path */
|
328
|
-
bs_cache_path(cachedir,
|
360
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
329
361
|
|
330
362
|
return bs_fetch(path, path_v, cache_path, handler, args);
|
331
363
|
}
|
332
364
|
|
365
|
+
/*
|
366
|
+
* Entrypoint for Bootsnap::CompileCache::Native.precompile.
|
367
|
+
* Similar to fetch, but it only generate the cache if missing
|
368
|
+
* and doesn't return the content.
|
369
|
+
*/
|
370
|
+
static VALUE
|
371
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
372
|
+
{
|
373
|
+
FilePathValue(path_v);
|
374
|
+
|
375
|
+
Check_Type(cachedir_v, T_STRING);
|
376
|
+
Check_Type(path_v, T_STRING);
|
377
|
+
|
378
|
+
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
379
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
380
|
+
}
|
381
|
+
|
382
|
+
char * cachedir = RSTRING_PTR(cachedir_v);
|
383
|
+
char * path = RSTRING_PTR(path_v);
|
384
|
+
char cache_path[MAX_CACHEPATH_SIZE];
|
385
|
+
|
386
|
+
/* generate cache path to cache_path */
|
387
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
388
|
+
|
389
|
+
return bs_precompile(path, path_v, cache_path, handler);
|
390
|
+
}
|
333
391
|
/*
|
334
392
|
* Open the file we want to load/cache and generate a cache key for it if it
|
335
393
|
* was loaded.
|
@@ -366,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
366
424
|
}
|
367
425
|
|
368
426
|
#define ERROR_WITH_ERRNO -1
|
369
|
-
#define
|
427
|
+
#define CACHE_MISS -2
|
428
|
+
#define CACHE_STALE -3
|
370
429
|
|
371
430
|
/*
|
372
431
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -374,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
374
433
|
*
|
375
434
|
* Possible return values:
|
376
435
|
* - 0 (OK, key was loaded)
|
377
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
378
436
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
437
|
+
* - CACHE_MISS (-2)
|
438
|
+
* - CACHE_STALE (-3)
|
379
439
|
*/
|
380
440
|
static int
|
381
441
|
bs_read_key(int fd, struct bs_cache_key * key)
|
382
442
|
{
|
383
443
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
384
444
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
385
|
-
if (nread < KEY_SIZE) return
|
445
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
386
446
|
return 0;
|
387
447
|
}
|
388
448
|
|
@@ -392,7 +452,8 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
392
452
|
*
|
393
453
|
* Possible return values:
|
394
454
|
* - 0 (OK, key was loaded)
|
395
|
-
* -
|
455
|
+
* - CACHE_MISS (-2)
|
456
|
+
* - CACHE_STALE (-3)
|
396
457
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
397
458
|
*/
|
398
459
|
static int
|
@@ -403,7 +464,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
403
464
|
fd = open(path, O_RDONLY);
|
404
465
|
if (fd < 0) {
|
405
466
|
*errno_provenance = "bs_fetch:open_cache_file:open";
|
406
|
-
if (errno == ENOENT) return
|
467
|
+
if (errno == ENOENT) return CACHE_MISS;
|
407
468
|
return ERROR_WITH_ERRNO;
|
408
469
|
}
|
409
470
|
#ifdef _WIN32
|
@@ -458,7 +519,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
458
519
|
goto done;
|
459
520
|
}
|
460
521
|
if (nread != data_size) {
|
461
|
-
ret =
|
522
|
+
ret = CACHE_STALE;
|
462
523
|
goto done;
|
463
524
|
}
|
464
525
|
|
@@ -652,14 +713,22 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
652
713
|
|
653
714
|
/* Open the cache key if it exists, and read its cache key in */
|
654
715
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
655
|
-
if (cache_fd ==
|
716
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
656
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
|
+
}
|
657
721
|
} else if (cache_fd < 0) {
|
658
722
|
goto fail_errno;
|
659
723
|
} else {
|
660
724
|
/* True if the cache existed and no invalidating changes have occurred since
|
661
725
|
* it was generated. */
|
662
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
|
+
}
|
663
732
|
}
|
664
733
|
|
665
734
|
if (valid_cache) {
|
@@ -668,10 +737,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
668
737
|
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
669
738
|
&output_data, &exception_tag, &errno_provenance
|
670
739
|
);
|
671
|
-
if (exception_tag != 0)
|
672
|
-
else if (res ==
|
673
|
-
else if (res == ERROR_WITH_ERRNO)
|
674
|
-
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 */
|
675
744
|
}
|
676
745
|
close(cache_fd);
|
677
746
|
cache_fd = -1;
|
@@ -740,6 +809,79 @@ invalid_type_storage_data:
|
|
740
809
|
#undef CLEANUP
|
741
810
|
}
|
742
811
|
|
812
|
+
static VALUE
|
813
|
+
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
814
|
+
{
|
815
|
+
struct bs_cache_key cached_key, current_key;
|
816
|
+
char * contents = NULL;
|
817
|
+
int cache_fd = -1, current_fd = -1;
|
818
|
+
int res, valid_cache = 0, exception_tag = 0;
|
819
|
+
const char * errno_provenance = NULL;
|
820
|
+
|
821
|
+
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
822
|
+
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
823
|
+
|
824
|
+
/* Open the source file and generate a cache key for it */
|
825
|
+
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
826
|
+
if (current_fd < 0) goto fail;
|
827
|
+
|
828
|
+
/* Open the cache key if it exists, and read its cache key in */
|
829
|
+
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
830
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
831
|
+
/* This is ok: valid_cache remains false, we re-populate it. */
|
832
|
+
} else if (cache_fd < 0) {
|
833
|
+
goto fail;
|
834
|
+
} else {
|
835
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
836
|
+
* it was generated. */
|
837
|
+
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
838
|
+
}
|
839
|
+
|
840
|
+
if (valid_cache) {
|
841
|
+
goto succeed;
|
842
|
+
}
|
843
|
+
|
844
|
+
close(cache_fd);
|
845
|
+
cache_fd = -1;
|
846
|
+
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
847
|
+
|
848
|
+
/* Read the contents of the source file into a buffer */
|
849
|
+
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
|
850
|
+
input_data = rb_str_new(contents, current_key.size);
|
851
|
+
|
852
|
+
/* Try to compile the input_data using input_to_storage(input_data) */
|
853
|
+
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
854
|
+
if (exception_tag != 0) goto fail;
|
855
|
+
|
856
|
+
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
857
|
+
* to cache anything; just return false */
|
858
|
+
if (storage_data == uncompilable) {
|
859
|
+
goto fail;
|
860
|
+
}
|
861
|
+
/* If storage_data isn't a string, we can't cache it */
|
862
|
+
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
863
|
+
|
864
|
+
/* Write the cache key and storage_data to the cache directory */
|
865
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
866
|
+
if (res < 0) goto fail;
|
867
|
+
|
868
|
+
goto succeed;
|
869
|
+
|
870
|
+
#define CLEANUP \
|
871
|
+
if (contents != NULL) xfree(contents); \
|
872
|
+
if (current_fd >= 0) close(current_fd); \
|
873
|
+
if (cache_fd >= 0) close(cache_fd);
|
874
|
+
|
875
|
+
succeed:
|
876
|
+
CLEANUP;
|
877
|
+
return Qtrue;
|
878
|
+
fail:
|
879
|
+
CLEANUP;
|
880
|
+
return Qfalse;
|
881
|
+
#undef CLEANUP
|
882
|
+
}
|
883
|
+
|
884
|
+
|
743
885
|
/*****************************************************************************/
|
744
886
|
/********************* Handler Wrappers **************************************/
|
745
887
|
/*****************************************************************************
|
@@ -771,7 +913,6 @@ struct i2o_data {
|
|
771
913
|
|
772
914
|
struct i2s_data {
|
773
915
|
VALUE handler;
|
774
|
-
VALUE args;
|
775
916
|
VALUE input_data;
|
776
917
|
VALUE pathval;
|
777
918
|
};
|
@@ -789,7 +930,7 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
|
|
789
930
|
int state;
|
790
931
|
struct s2o_data s2o_data = {
|
791
932
|
.handler = handler,
|
792
|
-
.args
|
933
|
+
.args = args,
|
793
934
|
.storage_data = storage_data,
|
794
935
|
};
|
795
936
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -818,7 +959,7 @@ static VALUE
|
|
818
959
|
try_input_to_storage(VALUE arg)
|
819
960
|
{
|
820
961
|
struct i2s_data * data = (struct i2s_data *)arg;
|
821
|
-
return rb_funcall(data->handler, rb_intern("input_to_storage"),
|
962
|
+
return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
|
822
963
|
}
|
823
964
|
|
824
965
|
static VALUE
|
@@ -843,7 +984,6 @@ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval,
|
|
843
984
|
int state;
|
844
985
|
struct i2s_data i2s_data = {
|
845
986
|
.handler = handler,
|
846
|
-
.args = args,
|
847
987
|
.input_data = input_data,
|
848
988
|
.pathval = pathval,
|
849
989
|
};
|