bootsnap 1.1.8-java → 1.5.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +89 -0
- data/README.md +46 -6
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +125 -87
- data/ext/bootsnap/extconf.rb +3 -1
- data/lib/bootsnap.rb +15 -6
- data/lib/bootsnap/bundler.rb +6 -3
- data/lib/bootsnap/cli.rb +136 -0
- data/lib/bootsnap/compile_cache.rb +32 -4
- data/lib/bootsnap/compile_cache/iseq.rb +25 -16
- data/lib/bootsnap/compile_cache/yaml.rb +75 -42
- data/lib/bootsnap/explicit_require.rb +2 -1
- data/lib/bootsnap/load_path_cache.rb +35 -9
- data/lib/bootsnap/load_path_cache/cache.rb +48 -29
- data/lib/bootsnap/load_path_cache/change_observer.rb +36 -29
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +39 -7
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +70 -53
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +18 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +148 -0
- data/lib/bootsnap/load_path_cache/path.rb +8 -7
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -39
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
- data/lib/bootsnap/load_path_cache/store.rb +20 -14
- data/lib/bootsnap/setup.rb +11 -13
- data/lib/bootsnap/version.rb +2 -1
- metadata +25 -28
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -4
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/Rakefile +0 -11
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -39
- data/dev.yml +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a6981435a732015b8043d7ffc3a63c0603b6f1de300db2610becafc93c25b91e
|
4
|
+
data.tar.gz: d977b2ee4969224edd10530a4cb4332bcaea0298f8f147e4e3aaf846cbc584ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d544e765dcc1ccd998ffd0c65ee8aeda19e0a8a37cc37a0fbcb9917384283d27902acb81c079ec7c6b3cb1fe2ffb4fa752b1b668ae34bfdfc0b695cf943159bb
|
7
|
+
data.tar.gz: 7036e0b6c86ada90a48fe5d384b029c2c2f383e3231d3bcab43cc6bb765ef49cce4dc785120bac88c293aae2f24f4e23ba6d15ba9eb5ffa953eb0f8843e5e588
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,92 @@
|
|
1
|
+
# 1.5.0
|
2
|
+
|
3
|
+
* Add a command line to statically precompile the ISeq cache. (#326)
|
4
|
+
|
5
|
+
# 1.4.9
|
6
|
+
|
7
|
+
* [Windows support](https://github.com/Shopify/bootsnap/pull/319)
|
8
|
+
* [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
|
9
|
+
|
10
|
+
# 1.4.8
|
11
|
+
|
12
|
+
* [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
|
13
|
+
|
14
|
+
# 1.4.7
|
15
|
+
|
16
|
+
* Various performance enhancements
|
17
|
+
* Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
|
18
|
+
|
19
|
+
# 1.4.6
|
20
|
+
|
21
|
+
* Fix bug that was erroneously considering that files containing `.` in the names were being
|
22
|
+
required if a different file with the same name was already being required
|
23
|
+
|
24
|
+
Example:
|
25
|
+
|
26
|
+
require 'foo'
|
27
|
+
require 'foo.en'
|
28
|
+
|
29
|
+
Before bootsnap was considering `foo.en` to be the same file as `foo`
|
30
|
+
|
31
|
+
* Use glibc as part of the ruby_platform cache key
|
32
|
+
|
33
|
+
# 1.4.5
|
34
|
+
|
35
|
+
* MRI 2.7 support
|
36
|
+
* Fixed concurrency bugs
|
37
|
+
|
38
|
+
# 1.4.4
|
39
|
+
|
40
|
+
* Disable ISeq cache in `bootsnap/setup` by default in Ruby 2.5
|
41
|
+
|
42
|
+
# 1.4.3
|
43
|
+
|
44
|
+
* Fix some cache permissions and umask issues after switch to mkstemp
|
45
|
+
|
46
|
+
# 1.4.2
|
47
|
+
|
48
|
+
* Fix bug when removing features loaded by relative path from `$LOADED_FEATURES`
|
49
|
+
* Fix bug with propagation of `NameError` up from nested calls to `require`
|
50
|
+
|
51
|
+
# 1.4.1
|
52
|
+
|
53
|
+
* Don't register change observers to frozen objects.
|
54
|
+
|
55
|
+
# 1.4.0
|
56
|
+
|
57
|
+
* When running in development mode, always fall back to a full path scan on LoadError, making
|
58
|
+
bootsnap more able to detect newly-created files. (#230)
|
59
|
+
* Respect `$LOADED_FEATURES.delete` in order to support code reloading, for integration with
|
60
|
+
Zeitwerk. (#230)
|
61
|
+
* Minor performance improvement: flow-control exceptions no longer generate backtraces.
|
62
|
+
* Better support for requiring from environments where some features are not supported (especially
|
63
|
+
JRuby). (#226)k
|
64
|
+
* More robust handling of OS errors when creating files. (#225)
|
65
|
+
|
66
|
+
# 1.3.2
|
67
|
+
|
68
|
+
* Fix Spring + Bootsnap incompatibility when there are files with similar names.
|
69
|
+
* Fix `YAML.load_file` monkey patch to keep accepting File objects as arguments.
|
70
|
+
* Fix the API for `ActiveSupport::Dependencies#autoloadable_module?`.
|
71
|
+
* Some performance improvements.
|
72
|
+
|
73
|
+
# 1.3.1
|
74
|
+
|
75
|
+
* Change load path scanning to more correctly follow symlinks.
|
76
|
+
|
77
|
+
# 1.3.0
|
78
|
+
|
79
|
+
* Handle cases where load path entries are symlinked (https://github.com/Shopify/bootsnap/pull/136)
|
80
|
+
|
81
|
+
# 1.2.1
|
82
|
+
|
83
|
+
* Fix method visibility of `Kernel#require`.
|
84
|
+
|
85
|
+
# 1.2.0
|
86
|
+
|
87
|
+
* Add `LoadedFeaturesIndex` to preserve fix a common bug related to `LOAD_PATH` modifications after
|
88
|
+
loading bootsnap.
|
89
|
+
|
1
90
|
# 1.1.8
|
2
91
|
|
3
92
|
* Don't cache YAML documents with `!ruby/object`
|
data/README.md
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
-
# Bootsnap [![
|
1
|
+
# Bootsnap [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](https://github.com/Shopify/bootsnap/actions)
|
2
2
|
|
3
3
|
Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`,
|
4
4
|
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
5
5
|
|
6
6
|
#### Performance
|
7
|
-
|
7
|
+
|
8
|
+
- [Discourse](https://github.com/discourse/discourse) reports a boot time reduction of approximately
|
9
|
+
50%, from roughly 6 to 3 seconds on one machine;
|
8
10
|
- One of our smaller internal apps also sees a reduction of 50%, from 3.6 to 1.8 seconds;
|
9
|
-
- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
|
11
|
+
- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
|
12
|
+
dropping from around 25s to 6.5s.
|
13
|
+
* In Shopify core (a large app), about 25% of this gain can be attributed to `compile_cache_*`
|
14
|
+
features; 75% to path caching, and ~1% to `disable_trace`. This is fairly representative.
|
10
15
|
|
11
16
|
## Usage
|
12
17
|
|
13
|
-
This gem works on
|
18
|
+
This gem works on macOS and Linux.
|
14
19
|
|
15
20
|
Add `bootsnap` to your `Gemfile`:
|
16
21
|
|
@@ -24,6 +29,17 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
|
|
24
29
|
require 'bootsnap/setup'
|
25
30
|
```
|
26
31
|
|
32
|
+
Note that bootsnap writes to `tmp/cache`, and that directory *must* be writable. Rails will fail to
|
33
|
+
boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
|
34
|
+
unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
|
35
|
+
|
36
|
+
**Note also that bootsnap will never clean up its own cache: this is left up to you. Depending on your
|
37
|
+
deployment strategy, you may need to periodically purge `tmp/cache/bootsnap*`. If you notice deploys
|
38
|
+
getting progressively slower, this is almost certainly the cause.**
|
39
|
+
|
40
|
+
It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
|
41
|
+
important to load Bootsnap as early as possible to get maximum performance improvement.
|
42
|
+
|
27
43
|
You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb).
|
28
44
|
|
29
45
|
If you are not using Rails, or if you are but want more control over things, add this to your
|
@@ -38,12 +54,14 @@ Bootsnap.setup(
|
|
38
54
|
development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
|
39
55
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
40
56
|
autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
|
41
|
-
disable_trace: true, #
|
57
|
+
disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
|
42
58
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
43
59
|
compile_cache_yaml: true # Compile YAML into a cache
|
44
60
|
)
|
45
61
|
```
|
46
62
|
|
63
|
+
**Note that `disable_trace` will break debuggers and tracing.**
|
64
|
+
|
47
65
|
**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
|
48
66
|
'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
|
49
67
|
will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
|
@@ -196,7 +214,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
|
|
196
214
|
is a cache key including several fields:
|
197
215
|
|
198
216
|
* `version`, hardcoded in bootsnap. Essentially a schema version;
|
199
|
-
* `
|
217
|
+
* `ruby_platform`, A hash of `RUBY_PLATFORM` (e.g. x86_64-linux-gnu) variable and glibc version (on Linux) or OS version (`uname -v` on BSD, macOS)
|
200
218
|
* `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
|
201
219
|
* `ruby_revision`, the version of Ruby this was compiled with;
|
202
220
|
* `size`, the size of the source file;
|
@@ -275,3 +293,25 @@ open /c/nope.bundle -> -1
|
|
275
293
|
```
|
276
294
|
# (nothing!)
|
277
295
|
```
|
296
|
+
|
297
|
+
## Precompilation
|
298
|
+
|
299
|
+
In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
|
300
|
+
But in production environments, such as docker images, you might need to precompile the cache.
|
301
|
+
|
302
|
+
To do so you can use the `bootsnap precompile` command.
|
303
|
+
|
304
|
+
Example:
|
305
|
+
|
306
|
+
```bash
|
307
|
+
$ bundle exec bootsnap precompile --gemfile app/ lib/
|
308
|
+
```
|
309
|
+
|
310
|
+
## When not to use Bootsnap
|
311
|
+
|
312
|
+
*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
|
313
|
+
engines.
|
314
|
+
|
315
|
+
*Non-local filesystems*: Bootsnap depends on `tmp/cache` (or whatever you set its cache directory
|
316
|
+
to) being on a relatively fast filesystem. If you put it on a network mount, bootsnap is very likely
|
317
|
+
to slow your application down quite a lot.
|
data/exe/bootsnap
ADDED
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -21,6 +21,9 @@
|
|
21
21
|
#ifndef _WIN32
|
22
22
|
#include <sys/utsname.h>
|
23
23
|
#endif
|
24
|
+
#ifdef __GLIBC__
|
25
|
+
#include <gnu/libc-version.h>
|
26
|
+
#endif
|
24
27
|
|
25
28
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
26
29
|
* 981 for the cache dir */
|
@@ -29,6 +32,8 @@
|
|
29
32
|
|
30
33
|
#define KEY_SIZE 64
|
31
34
|
|
35
|
+
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
36
|
+
|
32
37
|
/*
|
33
38
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
34
39
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -74,6 +79,8 @@ static uint32_t current_ruby_platform;
|
|
74
79
|
static uint32_t current_ruby_revision;
|
75
80
|
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
76
81
|
static uint32_t current_compile_option_crc32 = 0;
|
82
|
+
/* Current umask */
|
83
|
+
static mode_t current_umask;
|
77
84
|
|
78
85
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
79
86
|
static VALUE rb_mBootsnap;
|
@@ -84,29 +91,29 @@ static ID uncompilable;
|
|
84
91
|
|
85
92
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
86
93
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
87
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
94
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
88
95
|
|
89
96
|
/* Helpers */
|
90
97
|
static uint64_t fnv1a_64(const char *str);
|
91
|
-
static void bs_cache_path(const char * cachedir, const char * path, char
|
98
|
+
static void bs_cache_path(const char * cachedir, const char * path, const char * extra, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
92
99
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
93
100
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
94
|
-
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
95
|
-
static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
|
96
|
-
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
|
97
|
-
static
|
101
|
+
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
102
|
+
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
103
|
+
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
|
+
static uint32_t get_ruby_revision(void);
|
98
105
|
static uint32_t get_ruby_platform(void);
|
99
106
|
|
100
107
|
/*
|
101
108
|
* Helper functions to call ruby methods on handler object without crashing on
|
102
109
|
* exception.
|
103
110
|
*/
|
104
|
-
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
111
|
+
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
105
112
|
static VALUE prot_storage_to_output(VALUE arg);
|
106
113
|
static VALUE prot_input_to_output(VALUE arg);
|
107
|
-
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
114
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
108
115
|
static VALUE prot_input_to_storage(VALUE arg);
|
109
|
-
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
116
|
+
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
110
117
|
struct s2o_data;
|
111
118
|
struct i2o_data;
|
112
119
|
struct i2s_data;
|
@@ -135,14 +142,17 @@ Init_bootsnap(void)
|
|
135
142
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
136
143
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
137
144
|
|
138
|
-
current_ruby_revision =
|
145
|
+
current_ruby_revision = get_ruby_revision();
|
139
146
|
current_ruby_platform = get_ruby_platform();
|
140
147
|
|
141
148
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
142
149
|
|
143
150
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
144
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
151
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
145
152
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
153
|
+
|
154
|
+
current_umask = umask(0777);
|
155
|
+
umask(current_umask);
|
146
156
|
}
|
147
157
|
|
148
158
|
/*
|
@@ -192,6 +202,26 @@ fnv1a_64(const char *str)
|
|
192
202
|
return fnv1a_64_iter(h, str);
|
193
203
|
}
|
194
204
|
|
205
|
+
/*
|
206
|
+
* Ruby's revision may be Integer or String. CRuby 2.7 or later uses
|
207
|
+
* Git commit ID as revision. It's String.
|
208
|
+
*/
|
209
|
+
static uint32_t
|
210
|
+
get_ruby_revision(void)
|
211
|
+
{
|
212
|
+
VALUE ruby_revision;
|
213
|
+
|
214
|
+
ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
|
215
|
+
if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
|
216
|
+
return FIX2INT(ruby_revision);
|
217
|
+
} else {
|
218
|
+
uint64_t hash;
|
219
|
+
|
220
|
+
hash = fnv1a_64(StringValueCStr(ruby_revision));
|
221
|
+
return (uint32_t)(hash >> 32);
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
195
225
|
/*
|
196
226
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
197
227
|
* (or OS version), we need to invalidate the cache.
|
@@ -211,6 +241,9 @@ get_ruby_platform(void)
|
|
211
241
|
|
212
242
|
#ifdef _WIN32
|
213
243
|
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
244
|
+
#elif defined(__GLIBC__)
|
245
|
+
hash = fnv1a_64_iter(hash, gnu_get_libc_version());
|
246
|
+
return (uint32_t)(hash >> 32);
|
214
247
|
#else
|
215
248
|
struct utsname utsname;
|
216
249
|
|
@@ -231,9 +264,12 @@ get_ruby_platform(void)
|
|
231
264
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
232
265
|
*/
|
233
266
|
static void
|
234
|
-
bs_cache_path(const char * cachedir, const char * path, char
|
267
|
+
bs_cache_path(const char * cachedir, const char * path, const char * extra, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
235
268
|
{
|
236
269
|
uint64_t hash = fnv1a_64(path);
|
270
|
+
if (extra) {
|
271
|
+
hash ^= fnv1a_64(extra);
|
272
|
+
}
|
237
273
|
|
238
274
|
uint8_t first_byte = (hash >> (64 - 8));
|
239
275
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
@@ -268,8 +304,10 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
268
304
|
* conversions on the ruby VALUE arguments before passing them along.
|
269
305
|
*/
|
270
306
|
static VALUE
|
271
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
307
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
272
308
|
{
|
309
|
+
FilePathValue(path_v);
|
310
|
+
|
273
311
|
Check_Type(cachedir_v, T_STRING);
|
274
312
|
Check_Type(path_v, T_STRING);
|
275
313
|
|
@@ -280,13 +318,16 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
280
318
|
char * cachedir = RSTRING_PTR(cachedir_v);
|
281
319
|
char * path = RSTRING_PTR(path_v);
|
282
320
|
char cache_path[MAX_CACHEPATH_SIZE];
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
321
|
+
char * extra = NULL;
|
322
|
+
if (!NIL_P(args)) {
|
323
|
+
VALUE args_serial = rb_marshal_dump(args, Qnil);
|
324
|
+
extra = RSTRING_PTR(args_serial);
|
287
325
|
}
|
288
326
|
|
289
|
-
|
327
|
+
/* generate cache path to cache_path */
|
328
|
+
bs_cache_path(cachedir, path, extra, &cache_path);
|
329
|
+
|
330
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
290
331
|
}
|
291
332
|
|
292
333
|
/*
|
@@ -294,14 +335,14 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
294
335
|
* was loaded.
|
295
336
|
*/
|
296
337
|
static int
|
297
|
-
open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
|
338
|
+
open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
298
339
|
{
|
299
340
|
struct stat statbuf;
|
300
341
|
int fd;
|
301
342
|
|
302
343
|
fd = open(path, O_RDONLY);
|
303
344
|
if (fd < 0) {
|
304
|
-
*errno_provenance =
|
345
|
+
*errno_provenance = "bs_fetch:open_current_file:open";
|
305
346
|
return fd;
|
306
347
|
}
|
307
348
|
#ifdef _WIN32
|
@@ -309,7 +350,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
309
350
|
#endif
|
310
351
|
|
311
352
|
if (fstat(fd, &statbuf) < 0) {
|
312
|
-
*errno_provenance =
|
353
|
+
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
313
354
|
close(fd);
|
314
355
|
return -1;
|
315
356
|
}
|
@@ -355,13 +396,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
355
396
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
356
397
|
*/
|
357
398
|
static int
|
358
|
-
open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
|
399
|
+
open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
359
400
|
{
|
360
401
|
int fd, res;
|
361
402
|
|
362
403
|
fd = open(path, O_RDONLY);
|
363
404
|
if (fd < 0) {
|
364
|
-
*errno_provenance =
|
405
|
+
*errno_provenance = "bs_fetch:open_cache_file:open";
|
365
406
|
if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
|
366
407
|
return ERROR_WITH_ERRNO;
|
367
408
|
}
|
@@ -371,7 +412,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
371
412
|
|
372
413
|
res = bs_read_key(fd, key);
|
373
414
|
if (res < 0) {
|
374
|
-
*errno_provenance =
|
415
|
+
*errno_provenance = "bs_fetch:open_cache_file:read";
|
375
416
|
close(fd);
|
376
417
|
return res;
|
377
418
|
}
|
@@ -395,7 +436,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
395
436
|
* or exception, will be the final data returnable to the user.
|
396
437
|
*/
|
397
438
|
static int
|
398
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
|
439
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
399
440
|
{
|
400
441
|
char * data = NULL;
|
401
442
|
ssize_t nread;
|
@@ -404,7 +445,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
404
445
|
VALUE storage_data;
|
405
446
|
|
406
447
|
if (data_size > 100000000000) {
|
407
|
-
*errno_provenance =
|
448
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
408
449
|
errno = EINVAL; /* because wtf? */
|
409
450
|
ret = -1;
|
410
451
|
goto done;
|
@@ -412,7 +453,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
412
453
|
data = ALLOC_N(char, data_size);
|
413
454
|
nread = read(fd, data, data_size);
|
414
455
|
if (nread < 0) {
|
415
|
-
*errno_provenance =
|
456
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
416
457
|
ret = -1;
|
417
458
|
goto done;
|
418
459
|
}
|
@@ -421,9 +462,9 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
421
462
|
goto done;
|
422
463
|
}
|
423
464
|
|
424
|
-
storage_data =
|
465
|
+
storage_data = rb_str_new(data, data_size);
|
425
466
|
|
426
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
467
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
427
468
|
ret = 0;
|
428
469
|
done:
|
429
470
|
if (data != NULL) xfree(data);
|
@@ -464,30 +505,36 @@ mkpath(char * file_path, mode_t mode)
|
|
464
505
|
* path.
|
465
506
|
*/
|
466
507
|
static int
|
467
|
-
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
508
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
|
468
509
|
{
|
469
510
|
char template[MAX_CACHEPATH_SIZE + 20];
|
470
|
-
char * dest;
|
471
511
|
char * tmp_path;
|
472
|
-
int fd, ret;
|
512
|
+
int fd, ret, attempt;
|
473
513
|
ssize_t nwrite;
|
474
514
|
|
475
|
-
|
476
|
-
|
515
|
+
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
516
|
+
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
517
|
+
strcat(tmp_path, ".tmp.XXXXXX");
|
477
518
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
}
|
485
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
486
|
-
if (fd < 0) {
|
487
|
-
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
|
519
|
+
// mkstemp modifies the template to be the actual created path
|
520
|
+
fd = mkstemp(tmp_path);
|
521
|
+
if (fd > 0) break;
|
522
|
+
|
523
|
+
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
524
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
488
525
|
return -1;
|
489
526
|
}
|
490
527
|
}
|
528
|
+
if (fd < 0) {
|
529
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
|
530
|
+
return -1;
|
531
|
+
}
|
532
|
+
|
533
|
+
if (chmod(tmp_path, 0644) < 0) {
|
534
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
535
|
+
return -1;
|
536
|
+
}
|
537
|
+
|
491
538
|
#ifdef _WIN32
|
492
539
|
setmode(fd, O_BINARY);
|
493
540
|
#endif
|
@@ -495,11 +542,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
495
542
|
key->data_size = RSTRING_LEN(data);
|
496
543
|
nwrite = write(fd, key, KEY_SIZE);
|
497
544
|
if (nwrite < 0) {
|
498
|
-
*errno_provenance =
|
545
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
499
546
|
return -1;
|
500
547
|
}
|
501
548
|
if (nwrite != KEY_SIZE) {
|
502
|
-
*errno_provenance =
|
549
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
503
550
|
errno = EIO; /* Lies but whatever */
|
504
551
|
return -1;
|
505
552
|
}
|
@@ -507,7 +554,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
507
554
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
508
555
|
if (nwrite < 0) return -1;
|
509
556
|
if (nwrite != RSTRING_LEN(data)) {
|
510
|
-
*errno_provenance =
|
557
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
511
558
|
errno = EIO; /* Lies but whatever */
|
512
559
|
return -1;
|
513
560
|
}
|
@@ -515,38 +562,27 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
515
562
|
close(fd);
|
516
563
|
ret = rename(tmp_path, path);
|
517
564
|
if (ret < 0) {
|
518
|
-
*errno_provenance =
|
565
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
566
|
+
return -1;
|
567
|
+
}
|
568
|
+
ret = chmod(path, 0664 & ~current_umask);
|
569
|
+
if (ret < 0) {
|
570
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
519
571
|
}
|
520
572
|
return ret;
|
521
573
|
}
|
522
574
|
|
523
|
-
/*
|
524
|
-
* Given an errno value (converted to a ruby Fixnum), return the corresponding
|
525
|
-
* Errno::* constant. If none is found, return StandardError instead.
|
526
|
-
*/
|
527
|
-
static VALUE
|
528
|
-
prot_exception_for_errno(VALUE err)
|
529
|
-
{
|
530
|
-
if (err != INT2FIX(0)) {
|
531
|
-
VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
|
532
|
-
VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
|
533
|
-
VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
|
534
|
-
return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
|
535
|
-
}
|
536
|
-
return rb_eStandardError;
|
537
|
-
}
|
538
|
-
|
539
575
|
|
540
576
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
541
577
|
* long, into a buffer */
|
542
578
|
static ssize_t
|
543
|
-
bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
|
579
|
+
bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
|
544
580
|
{
|
545
581
|
ssize_t nread;
|
546
582
|
*contents = ALLOC_N(char, size);
|
547
583
|
nread = read(fd, *contents, size);
|
548
584
|
if (nread < 0) {
|
549
|
-
*errno_provenance =
|
585
|
+
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
550
586
|
}
|
551
587
|
return nread;
|
552
588
|
}
|
@@ -596,13 +632,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
|
|
596
632
|
* - Return storage_to_output(storage_data)
|
597
633
|
*/
|
598
634
|
static VALUE
|
599
|
-
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
635
|
+
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
600
636
|
{
|
601
637
|
struct bs_cache_key cached_key, current_key;
|
602
638
|
char * contents = NULL;
|
603
639
|
int cache_fd = -1, current_fd = -1;
|
604
640
|
int res, valid_cache = 0, exception_tag = 0;
|
605
|
-
char * errno_provenance = NULL;
|
641
|
+
const char * errno_provenance = NULL;
|
606
642
|
|
607
643
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
608
644
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
@@ -629,7 +665,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
629
665
|
if (valid_cache) {
|
630
666
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
631
667
|
res = fetch_cached_data(
|
632
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
668
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
633
669
|
&output_data, &exception_tag, &errno_provenance
|
634
670
|
);
|
635
671
|
if (exception_tag != 0) goto raise;
|
@@ -643,15 +679,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
643
679
|
|
644
680
|
/* Read the contents of the source file into a buffer */
|
645
681
|
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
646
|
-
input_data =
|
682
|
+
input_data = rb_str_new(contents, current_key.size);
|
647
683
|
|
648
684
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
649
|
-
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
685
|
+
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
650
686
|
if (exception_tag != 0) goto raise;
|
651
687
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
652
688
|
* to cache anything; just return input_to_output(input_data) */
|
653
689
|
if (storage_data == uncompilable) {
|
654
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
690
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
655
691
|
if (exception_tag != 0) goto raise;
|
656
692
|
goto succeed;
|
657
693
|
}
|
@@ -663,17 +699,17 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
663
699
|
if (res < 0) goto fail_errno;
|
664
700
|
|
665
701
|
/* Having written the cache, now convert storage_data to output_data */
|
666
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
702
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
667
703
|
if (exception_tag != 0) goto raise;
|
668
704
|
|
669
705
|
/* If output_data is nil, delete the cache entry and generate the output
|
670
706
|
* using input_to_output */
|
671
707
|
if (NIL_P(output_data)) {
|
672
708
|
if (unlink(cache_path) < 0) {
|
673
|
-
errno_provenance =
|
709
|
+
errno_provenance = "bs_fetch:unlink";
|
674
710
|
goto fail_errno;
|
675
711
|
}
|
676
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
712
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
677
713
|
if (exception_tag != 0) goto raise;
|
678
714
|
}
|
679
715
|
|
@@ -689,11 +725,7 @@ succeed:
|
|
689
725
|
return output_data;
|
690
726
|
fail_errno:
|
691
727
|
CLEANUP;
|
692
|
-
exception =
|
693
|
-
if (res) exception = rb_eStandardError;
|
694
|
-
if (errno_provenance != NULL) {
|
695
|
-
exception = rb_exc_new_str(exception, rb_str_new2(errno_provenance));
|
696
|
-
}
|
728
|
+
exception = rb_syserr_new(errno, errno_provenance);
|
697
729
|
rb_exc_raise(exception);
|
698
730
|
__builtin_unreachable();
|
699
731
|
raise:
|
@@ -727,16 +759,19 @@ invalid_type_storage_data:
|
|
727
759
|
|
728
760
|
struct s2o_data {
|
729
761
|
VALUE handler;
|
762
|
+
VALUE args;
|
730
763
|
VALUE storage_data;
|
731
764
|
};
|
732
765
|
|
733
766
|
struct i2o_data {
|
734
767
|
VALUE handler;
|
768
|
+
VALUE args;
|
735
769
|
VALUE input_data;
|
736
770
|
};
|
737
771
|
|
738
772
|
struct i2s_data {
|
739
773
|
VALUE handler;
|
774
|
+
VALUE args;
|
740
775
|
VALUE input_data;
|
741
776
|
VALUE pathval;
|
742
777
|
};
|
@@ -745,15 +780,16 @@ static VALUE
|
|
745
780
|
prot_storage_to_output(VALUE arg)
|
746
781
|
{
|
747
782
|
struct s2o_data * data = (struct s2o_data *)arg;
|
748
|
-
return rb_funcall(data->handler, rb_intern("storage_to_output"),
|
783
|
+
return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
|
749
784
|
}
|
750
785
|
|
751
786
|
static int
|
752
|
-
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
787
|
+
bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
|
753
788
|
{
|
754
789
|
int state;
|
755
790
|
struct s2o_data s2o_data = {
|
756
791
|
.handler = handler,
|
792
|
+
.args = args,
|
757
793
|
.storage_data = storage_data,
|
758
794
|
};
|
759
795
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -761,10 +797,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
761
797
|
}
|
762
798
|
|
763
799
|
static void
|
764
|
-
bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
|
800
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
765
801
|
{
|
766
802
|
struct i2o_data i2o_data = {
|
767
803
|
.handler = handler,
|
804
|
+
.args = args,
|
768
805
|
.input_data = input_data,
|
769
806
|
};
|
770
807
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -774,18 +811,18 @@ static VALUE
|
|
774
811
|
prot_input_to_output(VALUE arg)
|
775
812
|
{
|
776
813
|
struct i2o_data * data = (struct i2o_data *)arg;
|
777
|
-
return rb_funcall(data->handler, rb_intern("input_to_output"),
|
814
|
+
return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
|
778
815
|
}
|
779
816
|
|
780
817
|
static VALUE
|
781
818
|
try_input_to_storage(VALUE arg)
|
782
819
|
{
|
783
820
|
struct i2s_data * data = (struct i2s_data *)arg;
|
784
|
-
return rb_funcall(data->handler, rb_intern("input_to_storage"),
|
821
|
+
return rb_funcall(data->handler, rb_intern("input_to_storage"), 3, data->input_data, data->pathval, data->args);
|
785
822
|
}
|
786
823
|
|
787
824
|
static VALUE
|
788
|
-
rescue_input_to_storage(VALUE arg)
|
825
|
+
rescue_input_to_storage(VALUE arg, VALUE e)
|
789
826
|
{
|
790
827
|
return uncompilable;
|
791
828
|
}
|
@@ -801,11 +838,12 @@ prot_input_to_storage(VALUE arg)
|
|
801
838
|
}
|
802
839
|
|
803
840
|
static int
|
804
|
-
bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
841
|
+
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
805
842
|
{
|
806
843
|
int state;
|
807
844
|
struct i2s_data i2s_data = {
|
808
845
|
.handler = handler,
|
846
|
+
.args = args,
|
809
847
|
.input_data = input_data,
|
810
848
|
.pathval = pathval,
|
811
849
|
};
|