bootsnap 1.4.5 → 1.7.2
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 +64 -0
- data/README.md +46 -15
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +250 -83
- data/ext/bootsnap/extconf.rb +20 -14
- data/lib/bootsnap.rb +89 -15
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/compile_cache.rb +3 -2
- data/lib/bootsnap/compile_cache/iseq.rb +22 -7
- data/lib/bootsnap/compile_cache/yaml.rb +93 -40
- data/lib/bootsnap/explicit_require.rb +1 -0
- data/lib/bootsnap/load_path_cache.rb +3 -16
- data/lib/bootsnap/load_path_cache/cache.rb +25 -8
- data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -5
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +34 -11
- data/lib/bootsnap/load_path_cache/path.rb +3 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +15 -7
- data/lib/bootsnap/setup.rb +2 -36
- data/lib/bootsnap/version.rb +2 -1
- metadata +15 -29
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/README.jp.md +0 -231
- data/Rakefile +0 -12
- data/bin/ci +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -45
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -106
- data/shipit.rubygems.yml +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea223ff0ae18f28970e2b80aa24970fe24f5eeefc7ad12b5b4b77dc921af4ca3
|
4
|
+
data.tar.gz: 5f9ffdfcbeac27b128aa314749dd27c6182247fe7b6aa6492a1d99d00586ac3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e3bcea4690954e6e15a3b0c10e3bf5b93b23fefff0d3d61354cb04d683bd8925d61ce32dcc9f7dd526b2f14f4d44a24196e72003fa75714bb0e6f99fbc94f62
|
7
|
+
data.tar.gz: 587454d2a3a5019c56c81068e9202905328e7c2918f1786f35ff988424e2a9222b36db35f7ef6e403fc730a0752508ab248b06d6b164dc0b6e147623e5f0912e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,67 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# 1.7.2
|
4
|
+
|
5
|
+
* Fix compatibility with msgpack < 1. (#349)
|
6
|
+
|
7
|
+
# 1.7.1
|
8
|
+
|
9
|
+
* Warn Ruby 2.5 users if they turn ISeq caching on. (#327, #244)
|
10
|
+
* Disable ISeq caching for the whole 2.5.x series again.
|
11
|
+
* Better handle hashing of Ruby strings. (#318)
|
12
|
+
|
13
|
+
# 1.7.0
|
14
|
+
|
15
|
+
* Fix detection of YAML files in gems.
|
16
|
+
* Adds an instrumentation API to monitor cache misses.
|
17
|
+
* Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
|
18
|
+
* Deprecate the `disable_trace` option.
|
19
|
+
* Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
|
20
|
+
|
21
|
+
# 1.6.0
|
22
|
+
|
23
|
+
* Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
|
24
|
+
* `bootsnap precompile` CLI use multiple processes to complete faster. (#341)
|
25
|
+
* `bootsnap precompile` CLI also precompile YAML files. (#340)
|
26
|
+
* 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)
|
27
|
+
* Changed the compile cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-compile-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/compile-cache` for ease of use. (#334)
|
28
|
+
|
29
|
+
# 1.5.1
|
30
|
+
|
31
|
+
* Workaround a Ruby bug in InstructionSequence.compile_file. (#332)
|
32
|
+
|
33
|
+
# 1.5.0
|
34
|
+
|
35
|
+
* Add a command line to statically precompile the ISeq cache. (#326)
|
36
|
+
|
37
|
+
# 1.4.9
|
38
|
+
|
39
|
+
* [Windows support](https://github.com/Shopify/bootsnap/pull/319)
|
40
|
+
* [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
|
41
|
+
|
42
|
+
# 1.4.8
|
43
|
+
|
44
|
+
* [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
|
45
|
+
|
46
|
+
# 1.4.7
|
47
|
+
|
48
|
+
* Various performance enhancements
|
49
|
+
* Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
|
50
|
+
|
51
|
+
# 1.4.6
|
52
|
+
|
53
|
+
* Fix bug that was erroneously considering that files containing `.` in the names were being
|
54
|
+
required if a different file with the same name was already being required
|
55
|
+
|
56
|
+
Example:
|
57
|
+
|
58
|
+
require 'foo'
|
59
|
+
require 'foo.en'
|
60
|
+
|
61
|
+
Before bootsnap was considering `foo.en` to be the same file as `foo`
|
62
|
+
|
63
|
+
* Use glibc as part of the ruby_platform cache key
|
64
|
+
|
1
65
|
# 1.4.5
|
2
66
|
|
3
67
|
* MRI 2.7 support
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# Bootsnap [](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
|
@@ -214,7 +232,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
|
|
214
232
|
is a cache key including several fields:
|
215
233
|
|
216
234
|
* `version`, hardcoded in bootsnap. Essentially a schema version;
|
217
|
-
* `
|
235
|
+
* `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)
|
218
236
|
* `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
|
219
237
|
* `ruby_revision`, the version of Ruby this was compiled with;
|
220
238
|
* `size`, the size of the source file;
|
@@ -294,6 +312,19 @@ open /c/nope.bundle -> -1
|
|
294
312
|
# (nothing!)
|
295
313
|
```
|
296
314
|
|
315
|
+
## Precompilation
|
316
|
+
|
317
|
+
In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
|
318
|
+
But in production environments, such as docker images, you might need to precompile the cache.
|
319
|
+
|
320
|
+
To do so you can use the `bootsnap precompile` command.
|
321
|
+
|
322
|
+
Example:
|
323
|
+
|
324
|
+
```bash
|
325
|
+
$ bundle exec bootsnap precompile --gemfile app/ lib/
|
326
|
+
```
|
327
|
+
|
297
328
|
## When not to use Bootsnap
|
298
329
|
|
299
330
|
*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
|
data/exe/bootsnap
ADDED
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>
|
@@ -21,6 +22,9 @@
|
|
21
22
|
#ifndef _WIN32
|
22
23
|
#include <sys/utsname.h>
|
23
24
|
#endif
|
25
|
+
#ifdef __GLIBC__
|
26
|
+
#include <gnu/libc-version.h>
|
27
|
+
#endif
|
24
28
|
|
25
29
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
26
30
|
* 981 for the cache dir */
|
@@ -29,6 +33,12 @@
|
|
29
33
|
|
30
34
|
#define KEY_SIZE 64
|
31
35
|
|
36
|
+
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
37
|
+
|
38
|
+
#ifndef RB_UNLIKELY
|
39
|
+
#define RB_UNLIKELY(x) (x)
|
40
|
+
#endif
|
41
|
+
|
32
42
|
/*
|
33
43
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
34
44
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -65,7 +75,7 @@ struct bs_cache_key {
|
|
65
75
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
66
76
|
|
67
77
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
68
|
-
static const uint32_t current_version =
|
78
|
+
static const uint32_t current_version = 3;
|
69
79
|
|
70
80
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
71
81
|
* new OS ABI, etc. */
|
@@ -83,19 +93,25 @@ static VALUE rb_mBootsnap_CompileCache;
|
|
83
93
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
84
94
|
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
85
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;
|
86
100
|
|
87
101
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
102
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
88
103
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
89
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
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);
|
90
106
|
|
91
107
|
/* Helpers */
|
92
|
-
static
|
93
|
-
static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
|
108
|
+
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
94
109
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
95
110
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
96
|
-
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
97
|
-
static
|
98
|
-
static int
|
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);
|
113
|
+
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
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);
|
99
115
|
static uint32_t get_ruby_revision(void);
|
100
116
|
static uint32_t get_ruby_platform(void);
|
101
117
|
|
@@ -103,12 +119,12 @@ static uint32_t get_ruby_platform(void);
|
|
103
119
|
* Helper functions to call ruby methods on handler object without crashing on
|
104
120
|
* exception.
|
105
121
|
*/
|
106
|
-
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
122
|
+
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
107
123
|
static VALUE prot_storage_to_output(VALUE arg);
|
108
124
|
static VALUE prot_input_to_output(VALUE arg);
|
109
|
-
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
125
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
110
126
|
static VALUE prot_input_to_storage(VALUE arg);
|
111
|
-
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
127
|
+
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
112
128
|
struct s2o_data;
|
113
129
|
struct i2o_data;
|
114
130
|
struct i2s_data;
|
@@ -141,15 +157,31 @@ Init_bootsnap(void)
|
|
141
157
|
current_ruby_platform = get_ruby_platform();
|
142
158
|
|
143
159
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
160
|
+
instrumentation_method = rb_intern("_instrument");
|
144
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);
|
167
|
+
|
168
|
+
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
145
169
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
146
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
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);
|
147
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
148
173
|
|
149
174
|
current_umask = umask(0777);
|
150
175
|
umask(current_umask);
|
151
176
|
}
|
152
177
|
|
178
|
+
static VALUE
|
179
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
180
|
+
{
|
181
|
+
instrumentation_enabled = RTEST(enabled);
|
182
|
+
return enabled;
|
183
|
+
}
|
184
|
+
|
153
185
|
/*
|
154
186
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
155
187
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -178,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
178
210
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
179
211
|
*/
|
180
212
|
static uint64_t
|
181
|
-
|
213
|
+
fnv1a_64_iter_cstr(uint64_t h, const char *str)
|
182
214
|
{
|
183
215
|
unsigned char *s = (unsigned char *)str;
|
184
216
|
|
@@ -191,7 +223,21 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
191
223
|
}
|
192
224
|
|
193
225
|
static uint64_t
|
194
|
-
|
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)
|
195
241
|
{
|
196
242
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
197
243
|
return fnv1a_64_iter(h, str);
|
@@ -212,7 +258,7 @@ get_ruby_revision(void)
|
|
212
258
|
} else {
|
213
259
|
uint64_t hash;
|
214
260
|
|
215
|
-
hash = fnv1a_64(
|
261
|
+
hash = fnv1a_64(ruby_revision);
|
216
262
|
return (uint32_t)(hash >> 32);
|
217
263
|
}
|
218
264
|
}
|
@@ -232,16 +278,19 @@ get_ruby_platform(void)
|
|
232
278
|
VALUE ruby_platform;
|
233
279
|
|
234
280
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
235
|
-
hash = fnv1a_64(
|
281
|
+
hash = fnv1a_64(ruby_platform);
|
236
282
|
|
237
283
|
#ifdef _WIN32
|
238
284
|
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
285
|
+
#elif defined(__GLIBC__)
|
286
|
+
hash = fnv1a_64_iter_cstr(hash, gnu_get_libc_version());
|
287
|
+
return (uint32_t)(hash >> 32);
|
239
288
|
#else
|
240
289
|
struct utsname utsname;
|
241
290
|
|
242
291
|
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
243
292
|
if (uname(&utsname) >= 0) {
|
244
|
-
hash =
|
293
|
+
hash = fnv1a_64_iter_cstr(hash, utsname.version);
|
245
294
|
}
|
246
295
|
|
247
296
|
return (uint32_t)(hash >> 32);
|
@@ -256,14 +305,13 @@ get_ruby_platform(void)
|
|
256
305
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
257
306
|
*/
|
258
307
|
static void
|
259
|
-
bs_cache_path(const char * cachedir, const
|
308
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
260
309
|
{
|
261
310
|
uint64_t hash = fnv1a_64(path);
|
262
|
-
|
263
311
|
uint8_t first_byte = (hash >> (64 - 8));
|
264
312
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
265
313
|
|
266
|
-
sprintf(*cache_path, "%s/%
|
314
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
267
315
|
}
|
268
316
|
|
269
317
|
/*
|
@@ -293,7 +341,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
293
341
|
* conversions on the ruby VALUE arguments before passing them along.
|
294
342
|
*/
|
295
343
|
static VALUE
|
296
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
344
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
297
345
|
{
|
298
346
|
FilePathValue(path_v);
|
299
347
|
|
@@ -308,27 +356,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
308
356
|
char * path = RSTRING_PTR(path_v);
|
309
357
|
char cache_path[MAX_CACHEPATH_SIZE];
|
310
358
|
|
311
|
-
|
312
|
-
|
313
|
-
bs_cache_path(cachedir, path, &tmp);
|
314
|
-
}
|
359
|
+
/* generate cache path to cache_path */
|
360
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
315
361
|
|
316
|
-
return bs_fetch(path, path_v, cache_path, handler);
|
362
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
317
363
|
}
|
318
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
|
+
}
|
319
391
|
/*
|
320
392
|
* Open the file we want to load/cache and generate a cache key for it if it
|
321
393
|
* was loaded.
|
322
394
|
*/
|
323
395
|
static int
|
324
|
-
open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
|
396
|
+
open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
325
397
|
{
|
326
398
|
struct stat statbuf;
|
327
399
|
int fd;
|
328
400
|
|
329
401
|
fd = open(path, O_RDONLY);
|
330
402
|
if (fd < 0) {
|
331
|
-
*errno_provenance =
|
403
|
+
*errno_provenance = "bs_fetch:open_current_file:open";
|
332
404
|
return fd;
|
333
405
|
}
|
334
406
|
#ifdef _WIN32
|
@@ -336,7 +408,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
336
408
|
#endif
|
337
409
|
|
338
410
|
if (fstat(fd, &statbuf) < 0) {
|
339
|
-
*errno_provenance =
|
411
|
+
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
340
412
|
close(fd);
|
341
413
|
return -1;
|
342
414
|
}
|
@@ -352,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
352
424
|
}
|
353
425
|
|
354
426
|
#define ERROR_WITH_ERRNO -1
|
355
|
-
#define
|
427
|
+
#define CACHE_MISS -2
|
428
|
+
#define CACHE_STALE -3
|
356
429
|
|
357
430
|
/*
|
358
431
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -360,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
360
433
|
*
|
361
434
|
* Possible return values:
|
362
435
|
* - 0 (OK, key was loaded)
|
363
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
364
436
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
437
|
+
* - CACHE_MISS (-2)
|
438
|
+
* - CACHE_STALE (-3)
|
365
439
|
*/
|
366
440
|
static int
|
367
441
|
bs_read_key(int fd, struct bs_cache_key * key)
|
368
442
|
{
|
369
443
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
370
444
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
371
|
-
if (nread < KEY_SIZE) return
|
445
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
372
446
|
return 0;
|
373
447
|
}
|
374
448
|
|
@@ -378,18 +452,19 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
378
452
|
*
|
379
453
|
* Possible return values:
|
380
454
|
* - 0 (OK, key was loaded)
|
381
|
-
* -
|
455
|
+
* - CACHE_MISS (-2)
|
456
|
+
* - CACHE_STALE (-3)
|
382
457
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
383
458
|
*/
|
384
459
|
static int
|
385
|
-
open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
|
460
|
+
open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
386
461
|
{
|
387
462
|
int fd, res;
|
388
463
|
|
389
464
|
fd = open(path, O_RDONLY);
|
390
465
|
if (fd < 0) {
|
391
|
-
*errno_provenance =
|
392
|
-
if (errno == ENOENT) return
|
466
|
+
*errno_provenance = "bs_fetch:open_cache_file:open";
|
467
|
+
if (errno == ENOENT) return CACHE_MISS;
|
393
468
|
return ERROR_WITH_ERRNO;
|
394
469
|
}
|
395
470
|
#ifdef _WIN32
|
@@ -398,7 +473,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
398
473
|
|
399
474
|
res = bs_read_key(fd, key);
|
400
475
|
if (res < 0) {
|
401
|
-
*errno_provenance =
|
476
|
+
*errno_provenance = "bs_fetch:open_cache_file:read";
|
402
477
|
close(fd);
|
403
478
|
return res;
|
404
479
|
}
|
@@ -422,7 +497,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
422
497
|
* or exception, will be the final data returnable to the user.
|
423
498
|
*/
|
424
499
|
static int
|
425
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
|
500
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
426
501
|
{
|
427
502
|
char * data = NULL;
|
428
503
|
ssize_t nread;
|
@@ -431,7 +506,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
431
506
|
VALUE storage_data;
|
432
507
|
|
433
508
|
if (data_size > 100000000000) {
|
434
|
-
*errno_provenance =
|
509
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
435
510
|
errno = EINVAL; /* because wtf? */
|
436
511
|
ret = -1;
|
437
512
|
goto done;
|
@@ -439,18 +514,18 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
439
514
|
data = ALLOC_N(char, data_size);
|
440
515
|
nread = read(fd, data, data_size);
|
441
516
|
if (nread < 0) {
|
442
|
-
*errno_provenance =
|
517
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
443
518
|
ret = -1;
|
444
519
|
goto done;
|
445
520
|
}
|
446
521
|
if (nread != data_size) {
|
447
|
-
ret =
|
522
|
+
ret = CACHE_STALE;
|
448
523
|
goto done;
|
449
524
|
}
|
450
525
|
|
451
|
-
storage_data =
|
526
|
+
storage_data = rb_str_new(data, data_size);
|
452
527
|
|
453
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
528
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
454
529
|
ret = 0;
|
455
530
|
done:
|
456
531
|
if (data != NULL) xfree(data);
|
@@ -491,29 +566,36 @@ mkpath(char * file_path, mode_t mode)
|
|
491
566
|
* path.
|
492
567
|
*/
|
493
568
|
static int
|
494
|
-
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
569
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
|
495
570
|
{
|
496
571
|
char template[MAX_CACHEPATH_SIZE + 20];
|
497
572
|
char * tmp_path;
|
498
|
-
int fd, ret;
|
573
|
+
int fd, ret, attempt;
|
499
574
|
ssize_t nwrite;
|
500
575
|
|
501
|
-
|
502
|
-
|
576
|
+
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
577
|
+
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
578
|
+
strcat(tmp_path, ".tmp.XXXXXX");
|
503
579
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
}
|
511
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
512
|
-
if (fd < 0) {
|
513
|
-
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
|
580
|
+
// mkstemp modifies the template to be the actual created path
|
581
|
+
fd = mkstemp(tmp_path);
|
582
|
+
if (fd > 0) break;
|
583
|
+
|
584
|
+
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
585
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
514
586
|
return -1;
|
515
587
|
}
|
516
588
|
}
|
589
|
+
if (fd < 0) {
|
590
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
|
591
|
+
return -1;
|
592
|
+
}
|
593
|
+
|
594
|
+
if (chmod(tmp_path, 0644) < 0) {
|
595
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
596
|
+
return -1;
|
597
|
+
}
|
598
|
+
|
517
599
|
#ifdef _WIN32
|
518
600
|
setmode(fd, O_BINARY);
|
519
601
|
#endif
|
@@ -521,11 +603,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
521
603
|
key->data_size = RSTRING_LEN(data);
|
522
604
|
nwrite = write(fd, key, KEY_SIZE);
|
523
605
|
if (nwrite < 0) {
|
524
|
-
*errno_provenance =
|
606
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
525
607
|
return -1;
|
526
608
|
}
|
527
609
|
if (nwrite != KEY_SIZE) {
|
528
|
-
*errno_provenance =
|
610
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
529
611
|
errno = EIO; /* Lies but whatever */
|
530
612
|
return -1;
|
531
613
|
}
|
@@ -533,7 +615,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
533
615
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
534
616
|
if (nwrite < 0) return -1;
|
535
617
|
if (nwrite != RSTRING_LEN(data)) {
|
536
|
-
*errno_provenance =
|
618
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
537
619
|
errno = EIO; /* Lies but whatever */
|
538
620
|
return -1;
|
539
621
|
}
|
@@ -541,12 +623,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
541
623
|
close(fd);
|
542
624
|
ret = rename(tmp_path, path);
|
543
625
|
if (ret < 0) {
|
544
|
-
*errno_provenance =
|
626
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
545
627
|
return -1;
|
546
628
|
}
|
547
629
|
ret = chmod(path, 0664 & ~current_umask);
|
548
630
|
if (ret < 0) {
|
549
|
-
*errno_provenance =
|
631
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
550
632
|
}
|
551
633
|
return ret;
|
552
634
|
}
|
@@ -555,13 +637,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
555
637
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
556
638
|
* long, into a buffer */
|
557
639
|
static ssize_t
|
558
|
-
bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
|
640
|
+
bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
|
559
641
|
{
|
560
642
|
ssize_t nread;
|
561
643
|
*contents = ALLOC_N(char, size);
|
562
644
|
nread = read(fd, *contents, size);
|
563
645
|
if (nread < 0) {
|
564
|
-
*errno_provenance =
|
646
|
+
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
565
647
|
}
|
566
648
|
return nread;
|
567
649
|
}
|
@@ -611,13 +693,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
|
|
611
693
|
* - Return storage_to_output(storage_data)
|
612
694
|
*/
|
613
695
|
static VALUE
|
614
|
-
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
696
|
+
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
615
697
|
{
|
616
698
|
struct bs_cache_key cached_key, current_key;
|
617
699
|
char * contents = NULL;
|
618
700
|
int cache_fd = -1, current_fd = -1;
|
619
701
|
int res, valid_cache = 0, exception_tag = 0;
|
620
|
-
char * errno_provenance = NULL;
|
702
|
+
const char * errno_provenance = NULL;
|
621
703
|
|
622
704
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
623
705
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
@@ -631,26 +713,34 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
631
713
|
|
632
714
|
/* Open the cache key if it exists, and read its cache key in */
|
633
715
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
634
|
-
if (cache_fd ==
|
716
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
635
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
|
+
}
|
636
721
|
} else if (cache_fd < 0) {
|
637
722
|
goto fail_errno;
|
638
723
|
} else {
|
639
724
|
/* True if the cache existed and no invalidating changes have occurred since
|
640
725
|
* it was generated. */
|
641
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
|
+
}
|
642
732
|
}
|
643
733
|
|
644
734
|
if (valid_cache) {
|
645
735
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
646
736
|
res = fetch_cached_data(
|
647
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
737
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
648
738
|
&output_data, &exception_tag, &errno_provenance
|
649
739
|
);
|
650
|
-
if (exception_tag != 0)
|
651
|
-
else if (res ==
|
652
|
-
else if (res == ERROR_WITH_ERRNO)
|
653
|
-
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 */
|
654
744
|
}
|
655
745
|
close(cache_fd);
|
656
746
|
cache_fd = -1;
|
@@ -658,15 +748,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
658
748
|
|
659
749
|
/* Read the contents of the source file into a buffer */
|
660
750
|
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
661
|
-
input_data =
|
751
|
+
input_data = rb_str_new(contents, current_key.size);
|
662
752
|
|
663
753
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
664
|
-
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
754
|
+
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
665
755
|
if (exception_tag != 0) goto raise;
|
666
756
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
667
757
|
* to cache anything; just return input_to_output(input_data) */
|
668
758
|
if (storage_data == uncompilable) {
|
669
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
759
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
670
760
|
if (exception_tag != 0) goto raise;
|
671
761
|
goto succeed;
|
672
762
|
}
|
@@ -678,17 +768,17 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
678
768
|
if (res < 0) goto fail_errno;
|
679
769
|
|
680
770
|
/* Having written the cache, now convert storage_data to output_data */
|
681
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
771
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
682
772
|
if (exception_tag != 0) goto raise;
|
683
773
|
|
684
774
|
/* If output_data is nil, delete the cache entry and generate the output
|
685
775
|
* using input_to_output */
|
686
776
|
if (NIL_P(output_data)) {
|
687
777
|
if (unlink(cache_path) < 0) {
|
688
|
-
errno_provenance =
|
778
|
+
errno_provenance = "bs_fetch:unlink";
|
689
779
|
goto fail_errno;
|
690
780
|
}
|
691
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
781
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
692
782
|
if (exception_tag != 0) goto raise;
|
693
783
|
}
|
694
784
|
|
@@ -719,6 +809,79 @@ invalid_type_storage_data:
|
|
719
809
|
#undef CLEANUP
|
720
810
|
}
|
721
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
|
+
|
722
885
|
/*****************************************************************************/
|
723
886
|
/********************* Handler Wrappers **************************************/
|
724
887
|
/*****************************************************************************
|
@@ -738,11 +901,13 @@ invalid_type_storage_data:
|
|
738
901
|
|
739
902
|
struct s2o_data {
|
740
903
|
VALUE handler;
|
904
|
+
VALUE args;
|
741
905
|
VALUE storage_data;
|
742
906
|
};
|
743
907
|
|
744
908
|
struct i2o_data {
|
745
909
|
VALUE handler;
|
910
|
+
VALUE args;
|
746
911
|
VALUE input_data;
|
747
912
|
};
|
748
913
|
|
@@ -756,15 +921,16 @@ static VALUE
|
|
756
921
|
prot_storage_to_output(VALUE arg)
|
757
922
|
{
|
758
923
|
struct s2o_data * data = (struct s2o_data *)arg;
|
759
|
-
return rb_funcall(data->handler, rb_intern("storage_to_output"),
|
924
|
+
return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
|
760
925
|
}
|
761
926
|
|
762
927
|
static int
|
763
|
-
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
928
|
+
bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
|
764
929
|
{
|
765
930
|
int state;
|
766
931
|
struct s2o_data s2o_data = {
|
767
932
|
.handler = handler,
|
933
|
+
.args = args,
|
768
934
|
.storage_data = storage_data,
|
769
935
|
};
|
770
936
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -772,10 +938,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
772
938
|
}
|
773
939
|
|
774
940
|
static void
|
775
|
-
bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
|
941
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
776
942
|
{
|
777
943
|
struct i2o_data i2o_data = {
|
778
944
|
.handler = handler,
|
945
|
+
.args = args,
|
779
946
|
.input_data = input_data,
|
780
947
|
};
|
781
948
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -785,7 +952,7 @@ static VALUE
|
|
785
952
|
prot_input_to_output(VALUE arg)
|
786
953
|
{
|
787
954
|
struct i2o_data * data = (struct i2o_data *)arg;
|
788
|
-
return rb_funcall(data->handler, rb_intern("input_to_output"),
|
955
|
+
return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
|
789
956
|
}
|
790
957
|
|
791
958
|
static VALUE
|
@@ -796,7 +963,7 @@ try_input_to_storage(VALUE arg)
|
|
796
963
|
}
|
797
964
|
|
798
965
|
static VALUE
|
799
|
-
rescue_input_to_storage(VALUE arg)
|
966
|
+
rescue_input_to_storage(VALUE arg, VALUE e)
|
800
967
|
{
|
801
968
|
return uncompilable;
|
802
969
|
}
|
@@ -812,7 +979,7 @@ prot_input_to_storage(VALUE arg)
|
|
812
979
|
}
|
813
980
|
|
814
981
|
static int
|
815
|
-
bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
982
|
+
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
816
983
|
{
|
817
984
|
int state;
|
818
985
|
struct i2s_data i2s_data = {
|