bootsnap 1.18.3 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -8
- data/LICENSE.txt +2 -1
- data/README.md +20 -7
- data/ext/bootsnap/bootsnap.c +123 -15
- data/ext/bootsnap/extconf.rb +2 -1
- data/lib/bootsnap/cli/worker_pool.rb +72 -0
- data/lib/bootsnap/cli.rb +14 -40
- data/lib/bootsnap/compile_cache/iseq.rb +5 -3
- data/lib/bootsnap/compile_cache/yaml.rb +3 -1
- data/lib/bootsnap/compile_cache.rb +5 -10
- data/lib/bootsnap/explicit_require.rb +5 -0
- data/lib/bootsnap/load_path_cache/cache.rb +11 -14
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +9 -4
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +1 -1
- data/lib/bootsnap/load_path_cache/path.rb +34 -27
- data/lib/bootsnap/load_path_cache/path_scanner.rb +78 -32
- data/lib/bootsnap/load_path_cache/store.rb +3 -1
- data/lib/bootsnap/rake.rb +14 -0
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +31 -14
- metadata +9 -13
- data/ext/bootsnap/bootsnap.h +0 -6
- data/lib/bootsnap/compile_cache/json.rb +0 -89
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e0ad269f816c24dc901b6544acb59c5ebd65b22432f39ee69bc3387b9c2b04d
|
|
4
|
+
data.tar.gz: 700f525d90d4e77421ce83e38f8731981a48cc82a8b9a937c29f661972f78d7f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5859d7fd4b81f1969c55e12b33a957b34b1dc2825d03bc3f7ab60a57d34153cbcdda7a60626ea758fd66877b1b74a58d17b276aa2055f41086488d6ced5317b
|
|
7
|
+
data.tar.gz: c09f2cd48da0ba3bb5e9d3c995ffb86b8b16aec987c311162fc79ca648a66f355e4051d6ae4c5ee486d5ad2d2bc3af32090e15776e1f45129c1ea8c8e6c811ed
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Unreleased
|
|
2
2
|
|
|
3
|
+
# 1.23.0
|
|
4
|
+
|
|
5
|
+
* Require Ruby 2.7.
|
|
6
|
+
* Fix support for absolute paths in `BOOTSNAP_IGNORE_DIRECTORIES`.
|
|
7
|
+
|
|
8
|
+
# 1.22.0
|
|
9
|
+
|
|
10
|
+
* Better fix for the `opendir` crash.
|
|
11
|
+
* Add `bootsnap/rake` for cleaning the bootsnap cache as part of `rake clobber`.
|
|
12
|
+
|
|
13
|
+
# 1.21.1
|
|
14
|
+
|
|
15
|
+
* Prevent a Ruby crash while scanning load path if `opendir` fails without setting `errno`.
|
|
16
|
+
According to the C spec this should not happen, but according to user reports, it did.
|
|
17
|
+
|
|
18
|
+
# 1.21.0
|
|
19
|
+
|
|
20
|
+
* Fix the `require` decorator to handle `Bootsnap.unload_cache!` being called.
|
|
21
|
+
* Minor optimization: Eagerly clear cache buffers to appease the GC.
|
|
22
|
+
|
|
23
|
+
# 1.20.1
|
|
24
|
+
|
|
25
|
+
* Handle broken symlinks in load path scanning code.
|
|
26
|
+
Should fix `Errno::ENOENT fstatat` issues some users have encountered after upgrading to 1.20.0.
|
|
27
|
+
|
|
28
|
+
# 1.20.0
|
|
29
|
+
|
|
30
|
+
* Optimized load path scanning with a C extension. Should be about 2x faster on supported platforms.
|
|
31
|
+
|
|
32
|
+
# 1.19.0
|
|
33
|
+
|
|
34
|
+
* Remove JSON parsing cache. Recent versions of the `json` gem are as fast as `msgpack` if not faster.
|
|
35
|
+
|
|
36
|
+
# 1.18.6
|
|
37
|
+
|
|
38
|
+
* Fix cgroup CPU limits detection in CLI.
|
|
39
|
+
|
|
40
|
+
# 1.18.5
|
|
41
|
+
|
|
42
|
+
* Attempt to detect a QEMU bug that can cause `bootsnap precompile` to hang forever when building ARM64 docker images
|
|
43
|
+
from x86_64 machines. See #495.
|
|
44
|
+
* Improve CLI to detect cgroup CPU limits and avoid spawning too many worker processes.
|
|
45
|
+
|
|
46
|
+
# 1.18.4
|
|
47
|
+
|
|
48
|
+
* Allow using bootsnap without bundler. See #488.
|
|
49
|
+
* Fix startup failure if the cache directory points to a broken symlink.
|
|
50
|
+
|
|
3
51
|
# 1.18.3
|
|
4
52
|
|
|
5
53
|
* Fix the cache corruption issue in the revalidation feature. See #474.
|
|
@@ -32,7 +80,7 @@
|
|
|
32
80
|
|
|
33
81
|
# 1.17.0
|
|
34
82
|
|
|
35
|
-
* Ensure `$LOAD_PATH.dup` is Ractor shareable to fix
|
|
83
|
+
* Ensure `$LOAD_PATH.dup` is Ractor shareable to fix a conflict with `did_you_mean`.
|
|
36
84
|
* Allow to ignore directories using absolute paths.
|
|
37
85
|
* Support YAML and JSON CompileCache on TruffleRuby.
|
|
38
86
|
* Support LoadPathCache on TruffleRuby.
|
|
@@ -90,7 +138,7 @@
|
|
|
90
138
|
|
|
91
139
|
* Get rid of the `Kernel.require_relative` decorator by resolving `$LOAD_PATH` members to their real path.
|
|
92
140
|
This way we handle symlinks in `$LOAD_PATH` much more efficiently. See #402 for the detailed explanation.
|
|
93
|
-
|
|
141
|
+
|
|
94
142
|
* Drop support for Ruby 2.3 (to allow getting rid of the `Kernel.require_relative` decorator).
|
|
95
143
|
|
|
96
144
|
# 1.10.3
|
|
@@ -216,7 +264,7 @@
|
|
|
216
264
|
* Adds an instrumentation API to monitor cache misses.
|
|
217
265
|
* Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
|
|
218
266
|
* Deprecate the `disable_trace` option.
|
|
219
|
-
* Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
|
|
267
|
+
* Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
|
|
220
268
|
|
|
221
269
|
# 1.6.0
|
|
222
270
|
|
|
@@ -236,12 +284,12 @@
|
|
|
236
284
|
|
|
237
285
|
# 1.4.9
|
|
238
286
|
|
|
239
|
-
* [Windows support](https://github.com/
|
|
240
|
-
* [Fix potential crash](https://github.com/
|
|
287
|
+
* [Windows support](https://github.com/rails/bootsnap/pull/319)
|
|
288
|
+
* [Fix potential crash](https://github.com/rails/bootsnap/pull/322)
|
|
241
289
|
|
|
242
290
|
# 1.4.8
|
|
243
291
|
|
|
244
|
-
* [Prevent FallbackScan from polluting exception cause](https://github.com/
|
|
292
|
+
* [Prevent FallbackScan from polluting exception cause](https://github.com/rails/bootsnap/pull/314)
|
|
245
293
|
|
|
246
294
|
# 1.4.7
|
|
247
295
|
|
|
@@ -254,7 +302,7 @@
|
|
|
254
302
|
required if a different file with the same name was already being required
|
|
255
303
|
|
|
256
304
|
Example:
|
|
257
|
-
|
|
305
|
+
|
|
258
306
|
require 'foo'
|
|
259
307
|
require 'foo.en'
|
|
260
308
|
|
|
@@ -308,7 +356,7 @@
|
|
|
308
356
|
|
|
309
357
|
# 1.3.0
|
|
310
358
|
|
|
311
|
-
* Handle cases where load path entries are symlinked (https://github.com/
|
|
359
|
+
* Handle cases where load path entries are symlinked (https://github.com/rails/bootsnap/pull/136)
|
|
312
360
|
|
|
313
361
|
# 1.2.1
|
|
314
362
|
|
data/LICENSE.txt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2017-
|
|
3
|
+
Copyright (c) 2017-2025 Shopify, Inc.
|
|
4
|
+
Copyright (c) 2025-present Rails Foundation
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Bootsnap [](https://github.com/rails/bootsnap/actions)
|
|
2
2
|
|
|
3
3
|
Bootsnap is a library that plugs into Ruby, with optional support for `YAML` and `JSON`,
|
|
4
4
|
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
|
@@ -41,7 +41,7 @@ getting progressively slower, this is almost certainly the cause.**
|
|
|
41
41
|
It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
|
|
42
42
|
important to load Bootsnap as early as possible to get maximum performance improvement.
|
|
43
43
|
|
|
44
|
-
You can see how this require works [here](https://github.com/
|
|
44
|
+
You can see how this require works [here](https://github.com/rails/bootsnap/blob/main/lib/bootsnap/setup.rb).
|
|
45
45
|
|
|
46
46
|
If you are not using Rails, or if you are but want more control over things, add this to your
|
|
47
47
|
application setup immediately after `require 'bundler/setup'` (i.e. as early as possible: the sooner
|
|
@@ -57,13 +57,12 @@ Bootsnap.setup(
|
|
|
57
57
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
|
58
58
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
|
59
59
|
compile_cache_yaml: true, # Compile YAML into a cache
|
|
60
|
-
compile_cache_json: true, # Compile JSON into a cache
|
|
61
60
|
readonly: true, # Use the caches but don't update them on miss or stale entries.
|
|
62
61
|
)
|
|
63
62
|
```
|
|
64
63
|
|
|
65
64
|
**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
|
|
66
|
-
'bootsnap')` using [this trick](https://github.com/
|
|
65
|
+
'bootsnap')` using [this trick](https://github.com/rails/bootsnap/wiki/Bootlib::Require). This
|
|
67
66
|
will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
|
|
68
67
|
|
|
69
68
|
Note: Bootsnap and [Spring](https://github.com/rails/spring) are orthogonal tools. While Bootsnap
|
|
@@ -170,7 +169,7 @@ The only directories considered "stable" are things under the Ruby install prefi
|
|
|
170
169
|
"volatile".
|
|
171
170
|
|
|
172
171
|
In addition to the [`Bootsnap::LoadPathCache::Cache`
|
|
173
|
-
source](https://github.com/
|
|
172
|
+
source](https://github.com/rails/bootsnap/blob/main/lib/bootsnap/load_path_cache/cache.rb),
|
|
174
173
|
this diagram may help clarify how entry resolution works:
|
|
175
174
|
|
|
176
175
|

|
|
@@ -188,7 +187,7 @@ result too, raising a `LoadError` without touching the filesystem at all.
|
|
|
188
187
|
|
|
189
188
|
Ruby has complex grammar and parsing it is not a particularly cheap operation. Since 1.9, Ruby has
|
|
190
189
|
translated ruby source to an internal bytecode format, which is then executed by the Ruby VM. Since
|
|
191
|
-
2.3.0, Ruby [exposes an API](https://ruby-
|
|
190
|
+
2.3.0, Ruby [exposes an API](https://docs.ruby-lang.org/en/master/RubyVM/InstructionSequence.html) that
|
|
192
191
|
allows caching that bytecode. This allows us to bypass the relatively-expensive compilation step on
|
|
193
192
|
subsequent loads of the same file.
|
|
194
193
|
|
|
@@ -331,9 +330,23 @@ To do so you can use the `bootsnap precompile` command.
|
|
|
331
330
|
Example:
|
|
332
331
|
|
|
333
332
|
```bash
|
|
334
|
-
$ bundle exec bootsnap precompile --gemfile app/ lib/
|
|
333
|
+
$ bundle exec bootsnap precompile --gemfile app/ lib/ config/
|
|
335
334
|
```
|
|
336
335
|
|
|
336
|
+
## Known issues
|
|
337
|
+
|
|
338
|
+
### QEMU environments
|
|
339
|
+
|
|
340
|
+
When building cross-platform Docker images, QEMU is often used for emulation and can be the source of a limitation that causes forked processes to hang. While Bootsnap includes automatic detection for this issue (as of [PR #501](https://github.com/rails/bootsnap/pull/501)), the detection may not always be sufficient.
|
|
341
|
+
|
|
342
|
+
If you encounter hangs during precompilation in QEMU-based environments (such as when using Docker buildx for cross-platform builds), you can work around this by disabling parallelization with the `-j 0` option:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
$ bundle exec bootsnap precompile -j 0 --gemfile app/ lib/ config/
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
See [Issue #495](https://github.com/rails/bootsnap/issues/495) for more details about this QEMU-related issue.
|
|
349
|
+
|
|
337
350
|
## When not to use Bootsnap
|
|
338
351
|
|
|
339
352
|
*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
|
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* here.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
#include "bootsnap.h"
|
|
15
14
|
#include "ruby.h"
|
|
16
15
|
#include <stdint.h>
|
|
17
16
|
#include <stdbool.h>
|
|
@@ -20,10 +19,15 @@
|
|
|
20
19
|
#include <fcntl.h>
|
|
21
20
|
#include <unistd.h>
|
|
22
21
|
#include <sys/stat.h>
|
|
22
|
+
#include <dirent.h>
|
|
23
|
+
|
|
24
|
+
#ifndef RBIMPL_ATTR_NORETURN
|
|
25
|
+
#define RBIMPL_ATTR_NORETURN()
|
|
26
|
+
#endif
|
|
23
27
|
|
|
24
28
|
#ifdef __APPLE__
|
|
25
29
|
// The symbol is present, however not in the headers
|
|
26
|
-
// See: https://github.com/
|
|
30
|
+
// See: https://github.com/rails/bootsnap/issues/470
|
|
27
31
|
extern int fdatasync(int);
|
|
28
32
|
#endif
|
|
29
33
|
|
|
@@ -82,7 +86,7 @@ struct bs_cache_key {
|
|
|
82
86
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
|
83
87
|
|
|
84
88
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
|
85
|
-
static const uint32_t current_version =
|
|
89
|
+
static const uint32_t current_version = 6;
|
|
86
90
|
|
|
87
91
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
|
88
92
|
* new OS ABI, etc. */
|
|
@@ -96,7 +100,6 @@ static mode_t current_umask;
|
|
|
96
100
|
|
|
97
101
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
|
98
102
|
static VALUE rb_mBootsnap;
|
|
99
|
-
static VALUE rb_mBootsnap_CompileCache;
|
|
100
103
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
|
101
104
|
static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
|
102
105
|
static ID instrumentation_method;
|
|
@@ -146,20 +149,119 @@ struct s2o_data;
|
|
|
146
149
|
struct i2o_data;
|
|
147
150
|
struct i2s_data;
|
|
148
151
|
|
|
149
|
-
/* https://bugs.ruby-lang.org/issues/13667 */
|
|
150
|
-
extern VALUE rb_get_coverages(void);
|
|
151
152
|
static VALUE
|
|
152
|
-
|
|
153
|
+
bs_rb_get_path(VALUE self, VALUE fname)
|
|
154
|
+
{
|
|
155
|
+
return rb_get_path(fname);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#ifdef HAVE_FSTATAT
|
|
159
|
+
|
|
160
|
+
RBIMPL_ATTR_NORETURN()
|
|
161
|
+
static void
|
|
162
|
+
bs_syserr_fail_path(const char *func_name, int n, VALUE path)
|
|
153
163
|
{
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s", func_name, RSTRING_PTR(path)));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
RBIMPL_ATTR_NORETURN()
|
|
168
|
+
static void
|
|
169
|
+
bs_syserr_fail_dir_entry(const char *func_name, int n, VALUE dir, const char *d_name)
|
|
170
|
+
{
|
|
171
|
+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s/%s", func_name, RSTRING_PTR(dir), d_name));
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
static VALUE
|
|
159
|
-
|
|
175
|
+
bs_rb_scan_dir(VALUE self, VALUE abspath)
|
|
160
176
|
{
|
|
161
|
-
|
|
177
|
+
Check_Type(abspath, T_STRING);
|
|
178
|
+
|
|
179
|
+
VALUE dirs = rb_ary_new();
|
|
180
|
+
VALUE requirables = rb_ary_new();
|
|
181
|
+
VALUE result = rb_ary_new_from_args(2, requirables, dirs);
|
|
182
|
+
|
|
183
|
+
DIR *dirp = opendir(RSTRING_PTR(abspath));
|
|
184
|
+
if (dirp == NULL) {
|
|
185
|
+
if (errno == ENOTDIR || errno == ENOENT) {
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
bs_syserr_fail_path("opendir", errno, abspath);
|
|
190
|
+
return Qundef;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
struct dirent *entry;
|
|
194
|
+
struct stat st;
|
|
195
|
+
int dfd = -1;
|
|
196
|
+
|
|
197
|
+
while (1) {
|
|
198
|
+
errno = 0;
|
|
199
|
+
|
|
200
|
+
entry = readdir(dirp);
|
|
201
|
+
if (entry == NULL) break;
|
|
202
|
+
|
|
203
|
+
if (entry->d_name[0] == '.') continue;
|
|
204
|
+
|
|
205
|
+
if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
|
|
206
|
+
// Note: the original implementation of LoadPathCache did follow symlink.
|
|
207
|
+
// So this is replicated here, but I'm not sure it's a good idea.
|
|
208
|
+
if (dfd < 0) {
|
|
209
|
+
dfd = dirfd(dirp);
|
|
210
|
+
if (dfd < 0) {
|
|
211
|
+
int err = errno;
|
|
212
|
+
closedir(dirp);
|
|
213
|
+
bs_syserr_fail_path("dirfd", err, abspath);
|
|
214
|
+
return Qundef;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (fstatat(dfd, entry->d_name, &st, 0)) {
|
|
219
|
+
if (errno == ENOENT) continue; // Broken symlink
|
|
220
|
+
|
|
221
|
+
int err = errno;
|
|
222
|
+
closedir(dirp);
|
|
223
|
+
bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
|
|
224
|
+
return Qundef;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (S_ISREG(st.st_mode)) {
|
|
228
|
+
entry->d_type = DT_REG;
|
|
229
|
+
} else if (S_ISDIR(st.st_mode)) {
|
|
230
|
+
entry->d_type = DT_DIR;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (entry->d_type == DT_DIR) {
|
|
235
|
+
rb_ary_push(dirs, rb_utf8_str_new_cstr(entry->d_name));
|
|
236
|
+
continue;
|
|
237
|
+
} else if (entry->d_type == DT_REG) {
|
|
238
|
+
size_t len = strlen(entry->d_name);
|
|
239
|
+
bool is_requirable = (
|
|
240
|
+
// Comparing 4B allows compiler to optimize this into a single 32b integer comparison.
|
|
241
|
+
(len > 3 && memcmp(entry->d_name + (len - 3), ".rb", 4) == 0)
|
|
242
|
+
|| (len > DLEXT_MAXLEN && memcmp(entry->d_name + (len - DLEXT_MAXLEN), DLEXT, DLEXT_MAXLEN + 1) == 0)
|
|
243
|
+
#ifdef DLEXT2
|
|
244
|
+
|| (len > DLEXT2_MAXLEN && memcmp(entry->d_name + (len - DLEXT2_MAXLEN), DLEXT2, DLEXT2_MAXLEN + 1) == 0)
|
|
245
|
+
#endif
|
|
246
|
+
);
|
|
247
|
+
if (is_requirable) {
|
|
248
|
+
rb_ary_push(requirables, rb_utf8_str_new(entry->d_name, len));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (errno) {
|
|
254
|
+
int err = errno;
|
|
255
|
+
closedir(dirp);
|
|
256
|
+
bs_syserr_fail_path("readdir", err, abspath);
|
|
257
|
+
} else if (closedir(dirp)) {
|
|
258
|
+
bs_syserr_fail_path("closedir", errno, abspath);
|
|
259
|
+
return Qundef;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return result;
|
|
162
263
|
}
|
|
264
|
+
#endif
|
|
163
265
|
|
|
164
266
|
/*
|
|
165
267
|
* Ruby C extensions are initialized by calling Init_<extname>.
|
|
@@ -175,7 +277,14 @@ Init_bootsnap(void)
|
|
|
175
277
|
|
|
176
278
|
rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
|
|
177
279
|
|
|
178
|
-
|
|
280
|
+
#ifdef HAVE_FSTATAT
|
|
281
|
+
VALUE rb_mBootsnap_LoadPathCache = rb_define_module_under(rb_mBootsnap, "LoadPathCache");
|
|
282
|
+
VALUE rb_mBootsnap_LoadPathCache_Native = rb_define_module_under(rb_mBootsnap_LoadPathCache, "Native");
|
|
283
|
+
|
|
284
|
+
rb_define_singleton_method(rb_mBootsnap_LoadPathCache_Native, "scan_dir", bs_rb_scan_dir, 1);
|
|
285
|
+
#endif
|
|
286
|
+
|
|
287
|
+
VALUE rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
|
|
179
288
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
|
180
289
|
rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
|
|
181
290
|
rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
|
|
@@ -193,7 +302,6 @@ Init_bootsnap(void)
|
|
|
193
302
|
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
|
194
303
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
|
195
304
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
|
|
196
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
|
197
305
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
|
198
306
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
|
199
307
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
|
@@ -922,9 +1030,9 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
922
1030
|
goto succeed; /* output_data is now the correct return. */
|
|
923
1031
|
|
|
924
1032
|
#define CLEANUP \
|
|
925
|
-
if (status != Qfalse) bs_instrumentation(status, path_v); \
|
|
926
1033
|
if (current_fd >= 0) close(current_fd); \
|
|
927
|
-
if (cache_fd >= 0) close(cache_fd);
|
|
1034
|
+
if (cache_fd >= 0) close(cache_fd); \
|
|
1035
|
+
if (status != Qfalse) bs_instrumentation(status, path_v);
|
|
928
1036
|
|
|
929
1037
|
succeed:
|
|
930
1038
|
CLEANUP;
|
data/ext/bootsnap/extconf.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "mkmf"
|
|
|
4
4
|
|
|
5
5
|
if %w[ruby truffleruby].include?(RUBY_ENGINE)
|
|
6
6
|
have_func "fdatasync", "unistd.h"
|
|
7
|
+
have_func "fstatat", "sys/stat.h"
|
|
7
8
|
|
|
8
9
|
unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
|
|
9
10
|
append_cppflags ["-D_GNU_SOURCE"] # Needed of O_NOATIME
|
|
@@ -12,7 +13,7 @@ if %w[ruby truffleruby].include?(RUBY_ENGINE)
|
|
|
12
13
|
append_cflags ["-O3", "-std=c99"]
|
|
13
14
|
|
|
14
15
|
# ruby.h has some -Wpedantic fails in some cases
|
|
15
|
-
# (e.g. https://github.com/
|
|
16
|
+
# (e.g. https://github.com/rails/bootsnap/issues/15)
|
|
16
17
|
unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
|
|
17
18
|
append_cflags([
|
|
18
19
|
"-Wall",
|
|
@@ -1,16 +1,88 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "etc"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
require "io/wait" unless IO.method_defined?(:wait_readable)
|
|
6
|
+
|
|
3
7
|
module Bootsnap
|
|
4
8
|
class CLI
|
|
5
9
|
class WorkerPool
|
|
6
10
|
class << self
|
|
7
11
|
def create(size:, jobs:)
|
|
12
|
+
size ||= default_size
|
|
8
13
|
if size > 0 && Process.respond_to?(:fork)
|
|
9
14
|
new(size: size, jobs: jobs)
|
|
10
15
|
else
|
|
11
16
|
Inline.new(jobs: jobs)
|
|
12
17
|
end
|
|
13
18
|
end
|
|
19
|
+
|
|
20
|
+
def default_size
|
|
21
|
+
nprocessors = Etc.nprocessors
|
|
22
|
+
size = [nprocessors, cpu_quota&.to_i || nprocessors].min
|
|
23
|
+
case size
|
|
24
|
+
when 0, 1
|
|
25
|
+
0
|
|
26
|
+
else
|
|
27
|
+
if fork_defunct?
|
|
28
|
+
$stderr.puts "warning: faulty fork(2) detected, probably in cross platform docker builds. " \
|
|
29
|
+
"Disabling parallel compilation."
|
|
30
|
+
0
|
|
31
|
+
else
|
|
32
|
+
size
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def cpu_quota
|
|
38
|
+
if RbConfig::CONFIG["target_os"].include?("linux")
|
|
39
|
+
if File.exist?("/sys/fs/cgroup/cpu.max")
|
|
40
|
+
# cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
|
|
41
|
+
cpu_max = File.read("/sys/fs/cgroup/cpu.max")
|
|
42
|
+
return nil if cpu_max.start_with?("max ") # no limit
|
|
43
|
+
|
|
44
|
+
max, period = cpu_max.split.map(&:to_f)
|
|
45
|
+
max / period
|
|
46
|
+
elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
|
|
47
|
+
# cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
|
|
48
|
+
max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
|
|
49
|
+
# If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions
|
|
50
|
+
# https://docs.kernel.org/scheduler/sched-bwc.html#management
|
|
51
|
+
return nil if max <= 0
|
|
52
|
+
|
|
53
|
+
period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f
|
|
54
|
+
max / period
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def fork_defunct?
|
|
60
|
+
return true unless ::Process.respond_to?(:fork)
|
|
61
|
+
|
|
62
|
+
# Ref: https://github.com/rails/bootsnap/issues/495
|
|
63
|
+
# The second forked process will hang on some QEMU environments
|
|
64
|
+
r, w = IO.pipe
|
|
65
|
+
pids = 2.times.map do
|
|
66
|
+
::Process.fork do
|
|
67
|
+
exit!(true)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
w.close
|
|
71
|
+
r.wait_readable(1) # Wait at most 1s
|
|
72
|
+
|
|
73
|
+
defunct = false
|
|
74
|
+
|
|
75
|
+
pids.each do |pid|
|
|
76
|
+
_pid, status = ::Process.wait2(pid, ::Process::WNOHANG)
|
|
77
|
+
if status.nil? # Didn't exit in 1s
|
|
78
|
+
defunct = true
|
|
79
|
+
Process.kill(:KILL, pid)
|
|
80
|
+
::Process.wait2(pid)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
defunct
|
|
85
|
+
end
|
|
14
86
|
end
|
|
15
87
|
|
|
16
88
|
class Inline
|
data/lib/bootsnap/cli.rb
CHANGED
|
@@ -4,7 +4,6 @@ require "bootsnap"
|
|
|
4
4
|
require "bootsnap/cli/worker_pool"
|
|
5
5
|
require "optparse"
|
|
6
6
|
require "fileutils"
|
|
7
|
-
require "etc"
|
|
8
7
|
|
|
9
8
|
module Bootsnap
|
|
10
9
|
class CLI
|
|
@@ -21,7 +20,7 @@ module Bootsnap
|
|
|
21
20
|
|
|
22
21
|
attr_reader :cache_dir, :argv
|
|
23
22
|
|
|
24
|
-
attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :
|
|
23
|
+
attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :jobs
|
|
25
24
|
|
|
26
25
|
def initialize(argv)
|
|
27
26
|
@argv = argv
|
|
@@ -29,35 +28,31 @@ module Bootsnap
|
|
|
29
28
|
self.compile_gemfile = false
|
|
30
29
|
self.exclude = nil
|
|
31
30
|
self.verbose = false
|
|
32
|
-
self.jobs =
|
|
31
|
+
self.jobs = nil
|
|
33
32
|
self.iseq = true
|
|
34
33
|
self.yaml = true
|
|
35
|
-
self.json = true
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
def precompile_command(*sources)
|
|
39
|
-
require "bootsnap/compile_cache
|
|
40
|
-
require "bootsnap/compile_cache/yaml"
|
|
41
|
-
require "bootsnap/compile_cache/json"
|
|
37
|
+
require "bootsnap/compile_cache"
|
|
42
38
|
|
|
43
39
|
fix_default_encoding do
|
|
44
|
-
Bootsnap::CompileCache
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
Bootsnap::CompileCache.setup(
|
|
41
|
+
cache_dir: cache_dir,
|
|
42
|
+
iseq: iseq,
|
|
43
|
+
yaml: yaml,
|
|
44
|
+
revalidation: true,
|
|
45
|
+
)
|
|
49
46
|
|
|
50
47
|
@work_pool = WorkerPool.create(size: jobs, jobs: {
|
|
51
48
|
ruby: method(:precompile_ruby),
|
|
52
49
|
yaml: method(:precompile_yaml),
|
|
53
|
-
json: method(:precompile_json),
|
|
54
50
|
})
|
|
55
51
|
@work_pool.spawn
|
|
56
52
|
|
|
57
53
|
main_sources = sources.map { |d| File.expand_path(d) }
|
|
58
54
|
precompile_ruby_files(main_sources)
|
|
59
55
|
precompile_yaml_files(main_sources)
|
|
60
|
-
precompile_json_files(main_sources)
|
|
61
56
|
|
|
62
57
|
if compile_gemfile
|
|
63
58
|
# Gems that include JSON or YAML files usually don't put them in `lib/`.
|
|
@@ -71,7 +66,6 @@ module Bootsnap
|
|
|
71
66
|
|
|
72
67
|
precompile_ruby_files(gem_paths, exclude: gem_exclude)
|
|
73
68
|
precompile_yaml_files(gem_paths, exclude: gem_exclude)
|
|
74
|
-
precompile_json_files(gem_paths, exclude: gem_exclude)
|
|
75
69
|
end
|
|
76
70
|
|
|
77
71
|
if (exitstatus = @work_pool.shutdown)
|
|
@@ -146,29 +140,6 @@ module Bootsnap
|
|
|
146
140
|
end
|
|
147
141
|
end
|
|
148
142
|
|
|
149
|
-
def precompile_json_files(load_paths, exclude: self.exclude)
|
|
150
|
-
return unless json
|
|
151
|
-
|
|
152
|
-
load_paths.each do |path|
|
|
153
|
-
if !exclude || !exclude.match?(path)
|
|
154
|
-
list_files(path, "**/*.json").each do |json_file|
|
|
155
|
-
# We ignore hidden files to not match the various .config.json files
|
|
156
|
-
if !File.basename(json_file).start_with?(".") && (!exclude || !exclude.match?(json_file))
|
|
157
|
-
@work_pool.push(:json, json_file)
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def precompile_json(*json_files)
|
|
165
|
-
Array(json_files).each do |json_file|
|
|
166
|
-
if CompileCache::JSON.precompile(json_file) && verbose
|
|
167
|
-
$stderr.puts(json_file)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
143
|
def precompile_ruby_files(load_paths, exclude: self.exclude)
|
|
173
144
|
return unless iseq
|
|
174
145
|
|
|
@@ -222,6 +193,9 @@ module Bootsnap
|
|
|
222
193
|
|
|
223
194
|
def parser
|
|
224
195
|
@parser ||= OptionParser.new do |opts|
|
|
196
|
+
opts.version = Bootsnap::VERSION
|
|
197
|
+
opts.program_name = "bootsnap"
|
|
198
|
+
|
|
225
199
|
opts.banner = "Usage: bootsnap COMMAND [ARGS]"
|
|
226
200
|
opts.separator ""
|
|
227
201
|
opts.separator "GLOBAL OPTIONS"
|
|
@@ -274,9 +248,9 @@ module Bootsnap
|
|
|
274
248
|
opts.on("--no-yaml", help) { self.yaml = false }
|
|
275
249
|
|
|
276
250
|
help = <<~HELP
|
|
277
|
-
Disable JSON precompilation.
|
|
251
|
+
Disable JSON precompilation. Deprecated.
|
|
278
252
|
HELP
|
|
279
|
-
opts.on("--no-json", help) {
|
|
253
|
+
opts.on("--no-json", help) { $stderr.puts("The --no-json option is deprecated and now a noop.") }
|
|
280
254
|
end
|
|
281
255
|
end
|
|
282
256
|
end
|
|
@@ -50,7 +50,9 @@ module Bootsnap
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def self.storage_to_output(binary, _args)
|
|
53
|
-
RubyVM::InstructionSequence.load_from_binary(binary)
|
|
53
|
+
iseq = RubyVM::InstructionSequence.load_from_binary(binary)
|
|
54
|
+
binary.clear
|
|
55
|
+
iseq
|
|
54
56
|
rescue RuntimeError => error
|
|
55
57
|
if error.message == "broken binary format"
|
|
56
58
|
$stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
|
|
@@ -84,7 +86,7 @@ module Bootsnap
|
|
|
84
86
|
module InstructionSequenceMixin
|
|
85
87
|
def load_iseq(path)
|
|
86
88
|
# Having coverage enabled prevents iseq dumping/loading.
|
|
87
|
-
return nil if defined?(Coverage) &&
|
|
89
|
+
return nil if defined?(Coverage) && Coverage.running?
|
|
88
90
|
|
|
89
91
|
Bootsnap::CompileCache::ISeq.fetch(path.to_s)
|
|
90
92
|
rescue RuntimeError => error
|
|
@@ -95,7 +97,7 @@ module Bootsnap
|
|
|
95
97
|
end
|
|
96
98
|
|
|
97
99
|
def compile_option=(hash)
|
|
98
|
-
super
|
|
100
|
+
super
|
|
99
101
|
Bootsnap::CompileCache::ISeq.compile_option_updated
|
|
100
102
|
end
|
|
101
103
|
end
|
|
@@ -170,7 +170,9 @@ module Bootsnap
|
|
|
170
170
|
unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
|
|
171
171
|
unpacker.feed(data)
|
|
172
172
|
_safe_loaded = unpacker.unpack
|
|
173
|
-
unpacker.unpack
|
|
173
|
+
result = unpacker.unpack
|
|
174
|
+
data.clear
|
|
175
|
+
result
|
|
174
176
|
end
|
|
175
177
|
|
|
176
178
|
def input_to_output(data, kwargs)
|