bootsnap 1.1.8 → 1.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +121 -0
- data/README.md +72 -14
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +283 -105
- data/ext/bootsnap/extconf.rb +21 -14
- data/lib/bootsnap.rb +95 -14
- data/lib/bootsnap/bundler.rb +6 -3
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/compile_cache.rb +32 -4
- data/lib/bootsnap/compile_cache/iseq.rb +32 -15
- data/lib/bootsnap/compile_cache/yaml.rb +97 -40
- data/lib/bootsnap/explicit_require.rb +2 -1
- data/lib/bootsnap/load_path_cache.rb +33 -20
- data/lib/bootsnap/load_path_cache/cache.rb +65 -29
- data/lib/bootsnap/load_path_cache/change_observer.rb +36 -29
- 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 +61 -39
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
- data/lib/bootsnap/load_path_cache/store.rb +27 -14
- data/lib/bootsnap/setup.rb +3 -40
- data/lib/bootsnap/version.rb +2 -1
- metadata +26 -29
- 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
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
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,124 @@
|
|
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
|
+
|
65
|
+
# 1.4.5
|
66
|
+
|
67
|
+
* MRI 2.7 support
|
68
|
+
* Fixed concurrency bugs
|
69
|
+
|
70
|
+
# 1.4.4
|
71
|
+
|
72
|
+
* Disable ISeq cache in `bootsnap/setup` by default in Ruby 2.5
|
73
|
+
|
74
|
+
# 1.4.3
|
75
|
+
|
76
|
+
* Fix some cache permissions and umask issues after switch to mkstemp
|
77
|
+
|
78
|
+
# 1.4.2
|
79
|
+
|
80
|
+
* Fix bug when removing features loaded by relative path from `$LOADED_FEATURES`
|
81
|
+
* Fix bug with propagation of `NameError` up from nested calls to `require`
|
82
|
+
|
83
|
+
# 1.4.1
|
84
|
+
|
85
|
+
* Don't register change observers to frozen objects.
|
86
|
+
|
87
|
+
# 1.4.0
|
88
|
+
|
89
|
+
* When running in development mode, always fall back to a full path scan on LoadError, making
|
90
|
+
bootsnap more able to detect newly-created files. (#230)
|
91
|
+
* Respect `$LOADED_FEATURES.delete` in order to support code reloading, for integration with
|
92
|
+
Zeitwerk. (#230)
|
93
|
+
* Minor performance improvement: flow-control exceptions no longer generate backtraces.
|
94
|
+
* Better support for requiring from environments where some features are not supported (especially
|
95
|
+
JRuby). (#226)k
|
96
|
+
* More robust handling of OS errors when creating files. (#225)
|
97
|
+
|
98
|
+
# 1.3.2
|
99
|
+
|
100
|
+
* Fix Spring + Bootsnap incompatibility when there are files with similar names.
|
101
|
+
* Fix `YAML.load_file` monkey patch to keep accepting File objects as arguments.
|
102
|
+
* Fix the API for `ActiveSupport::Dependencies#autoloadable_module?`.
|
103
|
+
* Some performance improvements.
|
104
|
+
|
105
|
+
# 1.3.1
|
106
|
+
|
107
|
+
* Change load path scanning to more correctly follow symlinks.
|
108
|
+
|
109
|
+
# 1.3.0
|
110
|
+
|
111
|
+
* Handle cases where load path entries are symlinked (https://github.com/Shopify/bootsnap/pull/136)
|
112
|
+
|
113
|
+
# 1.2.1
|
114
|
+
|
115
|
+
* Fix method visibility of `Kernel#require`.
|
116
|
+
|
117
|
+
# 1.2.0
|
118
|
+
|
119
|
+
* Add `LoadedFeaturesIndex` to preserve fix a common bug related to `LOAD_PATH` modifications after
|
120
|
+
loading bootsnap.
|
121
|
+
|
1
122
|
# 1.1.8
|
2
123
|
|
3
124
|
* 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
|
-
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
|
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. 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,18 @@ 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` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
|
33
|
+
and that directory *must* be writable. Rails will fail to
|
34
|
+
boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
|
35
|
+
unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
|
36
|
+
|
37
|
+
**Note also that bootsnap will never clean up its own cache: this is left up to you. Depending on your
|
38
|
+
deployment strategy, you may need to periodically purge `tmp/cache/bootsnap*`. If you notice deploys
|
39
|
+
getting progressively slower, this is almost certainly the cause.**
|
40
|
+
|
41
|
+
It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
|
42
|
+
important to load Bootsnap as early as possible to get maximum performance improvement.
|
43
|
+
|
27
44
|
You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb).
|
28
45
|
|
29
46
|
If you are not using Rails, or if you are but want more control over things, add this to your
|
@@ -37,8 +54,6 @@ Bootsnap.setup(
|
|
37
54
|
cache_dir: 'tmp/cache', # Path to your cache
|
38
55
|
development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
|
39
56
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
40
|
-
autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
|
41
|
-
disable_trace: true, # (Alpha) Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
|
42
57
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
43
58
|
compile_cache_yaml: true # Compile YAML into a cache
|
44
59
|
)
|
@@ -53,12 +68,39 @@ speeds up the loading of individual source files, Spring keeps a copy of a pre-b
|
|
53
68
|
on hand to completely skip parts of the boot process the next time it's needed. The two tools work
|
54
69
|
well together, and are both included in a newly-generated Rails applications by default.
|
55
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
|
+
|
56
81
|
### Environments
|
57
82
|
|
58
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.
|
59
84
|
|
60
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.
|
61
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
|
+
|
62
104
|
## How does this work?
|
63
105
|
|
64
106
|
Bootsnap optimizes methods to cache results of expensive computations, and can be grouped
|
@@ -66,8 +108,6 @@ into two broad categories:
|
|
66
108
|
|
67
109
|
* [Path Pre-Scanning](#path-pre-scanning)
|
68
110
|
* `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans.
|
69
|
-
* `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are
|
70
|
-
overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`.
|
71
111
|
* [Compilation caching](#compilation-caching)
|
72
112
|
* `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode
|
73
113
|
compilation.
|
@@ -106,10 +146,6 @@ open y/foo.rb
|
|
106
146
|
...
|
107
147
|
```
|
108
148
|
|
109
|
-
Exactly the same strategy is employed for methods that traverse
|
110
|
-
`ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to
|
111
|
-
`Bootsnap.setup`.
|
112
|
-
|
113
149
|
The following diagram flowcharts the overrides that make the `*_path_cache` features work.
|
114
150
|
|
115
151
|
![Flowchart explaining
|
@@ -196,7 +232,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
|
|
196
232
|
is a cache key including several fields:
|
197
233
|
|
198
234
|
* `version`, hardcoded in bootsnap. Essentially a schema version;
|
199
|
-
* `
|
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)
|
200
236
|
* `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
|
201
237
|
* `ruby_revision`, the version of Ruby this was compiled with;
|
202
238
|
* `size`, the size of the source file;
|
@@ -275,3 +311,25 @@ open /c/nope.bundle -> -1
|
|
275
311
|
```
|
276
312
|
# (nothing!)
|
277
313
|
```
|
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
|
+
|
328
|
+
## When not to use Bootsnap
|
329
|
+
|
330
|
+
*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
|
331
|
+
engines.
|
332
|
+
|
333
|
+
*Non-local filesystems*: Bootsnap depends on `tmp/cache` (or whatever you set its cache directory
|
334
|
+
to) being on a relatively fast filesystem. If you put it on a network mount, bootsnap is very likely
|
335
|
+
to slow your application down quite a lot.
|
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. */
|
@@ -74,6 +84,8 @@ static uint32_t current_ruby_platform;
|
|
74
84
|
static uint32_t current_ruby_revision;
|
75
85
|
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
76
86
|
static uint32_t current_compile_option_crc32 = 0;
|
87
|
+
/* Current umask */
|
88
|
+
static mode_t current_umask;
|
77
89
|
|
78
90
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
79
91
|
static VALUE rb_mBootsnap;
|
@@ -81,32 +93,38 @@ static VALUE rb_mBootsnap_CompileCache;
|
|
81
93
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
82
94
|
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
83
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;
|
84
100
|
|
85
101
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
102
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
86
103
|
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);
|
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);
|
88
106
|
|
89
107
|
/* Helpers */
|
90
|
-
static
|
91
|
-
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]);
|
92
109
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
93
110
|
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
|
96
|
-
static int
|
97
|
-
static
|
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);
|
115
|
+
static uint32_t get_ruby_revision(void);
|
98
116
|
static uint32_t get_ruby_platform(void);
|
99
117
|
|
100
118
|
/*
|
101
119
|
* Helper functions to call ruby methods on handler object without crashing on
|
102
120
|
* exception.
|
103
121
|
*/
|
104
|
-
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);
|
105
123
|
static VALUE prot_storage_to_output(VALUE arg);
|
106
124
|
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);
|
125
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
108
126
|
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);
|
127
|
+
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
110
128
|
struct s2o_data;
|
111
129
|
struct i2o_data;
|
112
130
|
struct i2s_data;
|
@@ -135,14 +153,33 @@ Init_bootsnap(void)
|
|
135
153
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
136
154
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
137
155
|
|
138
|
-
current_ruby_revision =
|
156
|
+
current_ruby_revision = get_ruby_revision();
|
139
157
|
current_ruby_platform = get_ruby_platform();
|
140
158
|
|
141
159
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
160
|
+
instrumentation_method = rb_intern("_instrument");
|
161
|
+
|
162
|
+
sym_miss = ID2SYM(rb_intern("miss"));
|
163
|
+
rb_global_variable(&sym_miss);
|
142
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);
|
143
169
|
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,
|
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);
|
145
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
173
|
+
|
174
|
+
current_umask = umask(0777);
|
175
|
+
umask(current_umask);
|
176
|
+
}
|
177
|
+
|
178
|
+
static VALUE
|
179
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
180
|
+
{
|
181
|
+
instrumentation_enabled = RTEST(enabled);
|
182
|
+
return enabled;
|
146
183
|
}
|
147
184
|
|
148
185
|
/*
|
@@ -173,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
173
210
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
174
211
|
*/
|
175
212
|
static uint64_t
|
176
|
-
|
213
|
+
fnv1a_64_iter_cstr(uint64_t h, const char *str)
|
177
214
|
{
|
178
215
|
unsigned char *s = (unsigned char *)str;
|
179
216
|
|
@@ -186,12 +223,46 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
186
223
|
}
|
187
224
|
|
188
225
|
static uint64_t
|
189
|
-
|
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)
|
190
241
|
{
|
191
242
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
192
243
|
return fnv1a_64_iter(h, str);
|
193
244
|
}
|
194
245
|
|
246
|
+
/*
|
247
|
+
* Ruby's revision may be Integer or String. CRuby 2.7 or later uses
|
248
|
+
* Git commit ID as revision. It's String.
|
249
|
+
*/
|
250
|
+
static uint32_t
|
251
|
+
get_ruby_revision(void)
|
252
|
+
{
|
253
|
+
VALUE ruby_revision;
|
254
|
+
|
255
|
+
ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
|
256
|
+
if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
|
257
|
+
return FIX2INT(ruby_revision);
|
258
|
+
} else {
|
259
|
+
uint64_t hash;
|
260
|
+
|
261
|
+
hash = fnv1a_64(ruby_revision);
|
262
|
+
return (uint32_t)(hash >> 32);
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
195
266
|
/*
|
196
267
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
197
268
|
* (or OS version), we need to invalidate the cache.
|
@@ -207,16 +278,19 @@ get_ruby_platform(void)
|
|
207
278
|
VALUE ruby_platform;
|
208
279
|
|
209
280
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
210
|
-
hash = fnv1a_64(
|
281
|
+
hash = fnv1a_64(ruby_platform);
|
211
282
|
|
212
283
|
#ifdef _WIN32
|
213
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);
|
214
288
|
#else
|
215
289
|
struct utsname utsname;
|
216
290
|
|
217
291
|
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
218
292
|
if (uname(&utsname) >= 0) {
|
219
|
-
hash =
|
293
|
+
hash = fnv1a_64_iter_cstr(hash, utsname.version);
|
220
294
|
}
|
221
295
|
|
222
296
|
return (uint32_t)(hash >> 32);
|
@@ -231,14 +305,13 @@ get_ruby_platform(void)
|
|
231
305
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
232
306
|
*/
|
233
307
|
static void
|
234
|
-
bs_cache_path(const char * cachedir, const
|
308
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
235
309
|
{
|
236
310
|
uint64_t hash = fnv1a_64(path);
|
237
|
-
|
238
311
|
uint8_t first_byte = (hash >> (64 - 8));
|
239
312
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
240
313
|
|
241
|
-
sprintf(*cache_path, "%s/%
|
314
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
242
315
|
}
|
243
316
|
|
244
317
|
/*
|
@@ -268,8 +341,10 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
268
341
|
* conversions on the ruby VALUE arguments before passing them along.
|
269
342
|
*/
|
270
343
|
static VALUE
|
271
|
-
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)
|
272
345
|
{
|
346
|
+
FilePathValue(path_v);
|
347
|
+
|
273
348
|
Check_Type(cachedir_v, T_STRING);
|
274
349
|
Check_Type(path_v, T_STRING);
|
275
350
|
|
@@ -281,27 +356,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
281
356
|
char * path = RSTRING_PTR(path_v);
|
282
357
|
char cache_path[MAX_CACHEPATH_SIZE];
|
283
358
|
|
284
|
-
|
285
|
-
|
286
|
-
bs_cache_path(cachedir, path, &tmp);
|
287
|
-
}
|
359
|
+
/* generate cache path to cache_path */
|
360
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
288
361
|
|
289
|
-
return bs_fetch(path, path_v, cache_path, handler);
|
362
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
290
363
|
}
|
291
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
|
+
}
|
292
391
|
/*
|
293
392
|
* Open the file we want to load/cache and generate a cache key for it if it
|
294
393
|
* was loaded.
|
295
394
|
*/
|
296
395
|
static int
|
297
|
-
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)
|
298
397
|
{
|
299
398
|
struct stat statbuf;
|
300
399
|
int fd;
|
301
400
|
|
302
401
|
fd = open(path, O_RDONLY);
|
303
402
|
if (fd < 0) {
|
304
|
-
*errno_provenance =
|
403
|
+
*errno_provenance = "bs_fetch:open_current_file:open";
|
305
404
|
return fd;
|
306
405
|
}
|
307
406
|
#ifdef _WIN32
|
@@ -309,7 +408,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
309
408
|
#endif
|
310
409
|
|
311
410
|
if (fstat(fd, &statbuf) < 0) {
|
312
|
-
*errno_provenance =
|
411
|
+
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
313
412
|
close(fd);
|
314
413
|
return -1;
|
315
414
|
}
|
@@ -325,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
325
424
|
}
|
326
425
|
|
327
426
|
#define ERROR_WITH_ERRNO -1
|
328
|
-
#define
|
427
|
+
#define CACHE_MISS -2
|
428
|
+
#define CACHE_STALE -3
|
329
429
|
|
330
430
|
/*
|
331
431
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -333,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
333
433
|
*
|
334
434
|
* Possible return values:
|
335
435
|
* - 0 (OK, key was loaded)
|
336
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
337
436
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
437
|
+
* - CACHE_MISS (-2)
|
438
|
+
* - CACHE_STALE (-3)
|
338
439
|
*/
|
339
440
|
static int
|
340
441
|
bs_read_key(int fd, struct bs_cache_key * key)
|
341
442
|
{
|
342
443
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
343
444
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
344
|
-
if (nread < KEY_SIZE) return
|
445
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
345
446
|
return 0;
|
346
447
|
}
|
347
448
|
|
@@ -351,18 +452,19 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
351
452
|
*
|
352
453
|
* Possible return values:
|
353
454
|
* - 0 (OK, key was loaded)
|
354
|
-
* -
|
455
|
+
* - CACHE_MISS (-2)
|
456
|
+
* - CACHE_STALE (-3)
|
355
457
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
356
458
|
*/
|
357
459
|
static int
|
358
|
-
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)
|
359
461
|
{
|
360
462
|
int fd, res;
|
361
463
|
|
362
464
|
fd = open(path, O_RDONLY);
|
363
465
|
if (fd < 0) {
|
364
|
-
*errno_provenance =
|
365
|
-
if (errno == ENOENT) return
|
466
|
+
*errno_provenance = "bs_fetch:open_cache_file:open";
|
467
|
+
if (errno == ENOENT) return CACHE_MISS;
|
366
468
|
return ERROR_WITH_ERRNO;
|
367
469
|
}
|
368
470
|
#ifdef _WIN32
|
@@ -371,7 +473,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
371
473
|
|
372
474
|
res = bs_read_key(fd, key);
|
373
475
|
if (res < 0) {
|
374
|
-
*errno_provenance =
|
476
|
+
*errno_provenance = "bs_fetch:open_cache_file:read";
|
375
477
|
close(fd);
|
376
478
|
return res;
|
377
479
|
}
|
@@ -395,7 +497,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
395
497
|
* or exception, will be the final data returnable to the user.
|
396
498
|
*/
|
397
499
|
static int
|
398
|
-
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)
|
399
501
|
{
|
400
502
|
char * data = NULL;
|
401
503
|
ssize_t nread;
|
@@ -404,7 +506,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
404
506
|
VALUE storage_data;
|
405
507
|
|
406
508
|
if (data_size > 100000000000) {
|
407
|
-
*errno_provenance =
|
509
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
408
510
|
errno = EINVAL; /* because wtf? */
|
409
511
|
ret = -1;
|
410
512
|
goto done;
|
@@ -412,18 +514,18 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
412
514
|
data = ALLOC_N(char, data_size);
|
413
515
|
nread = read(fd, data, data_size);
|
414
516
|
if (nread < 0) {
|
415
|
-
*errno_provenance =
|
517
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
416
518
|
ret = -1;
|
417
519
|
goto done;
|
418
520
|
}
|
419
521
|
if (nread != data_size) {
|
420
|
-
ret =
|
522
|
+
ret = CACHE_STALE;
|
421
523
|
goto done;
|
422
524
|
}
|
423
525
|
|
424
|
-
storage_data =
|
526
|
+
storage_data = rb_str_new(data, data_size);
|
425
527
|
|
426
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
528
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
427
529
|
ret = 0;
|
428
530
|
done:
|
429
531
|
if (data != NULL) xfree(data);
|
@@ -464,30 +566,36 @@ mkpath(char * file_path, mode_t mode)
|
|
464
566
|
* path.
|
465
567
|
*/
|
466
568
|
static int
|
467
|
-
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)
|
468
570
|
{
|
469
571
|
char template[MAX_CACHEPATH_SIZE + 20];
|
470
|
-
char * dest;
|
471
572
|
char * tmp_path;
|
472
|
-
int fd, ret;
|
573
|
+
int fd, ret, attempt;
|
473
574
|
ssize_t nwrite;
|
474
575
|
|
475
|
-
|
476
|
-
|
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");
|
477
579
|
|
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";
|
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";
|
488
586
|
return -1;
|
489
587
|
}
|
490
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
|
+
|
491
599
|
#ifdef _WIN32
|
492
600
|
setmode(fd, O_BINARY);
|
493
601
|
#endif
|
@@ -495,11 +603,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
495
603
|
key->data_size = RSTRING_LEN(data);
|
496
604
|
nwrite = write(fd, key, KEY_SIZE);
|
497
605
|
if (nwrite < 0) {
|
498
|
-
*errno_provenance =
|
606
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
499
607
|
return -1;
|
500
608
|
}
|
501
609
|
if (nwrite != KEY_SIZE) {
|
502
|
-
*errno_provenance =
|
610
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
503
611
|
errno = EIO; /* Lies but whatever */
|
504
612
|
return -1;
|
505
613
|
}
|
@@ -507,7 +615,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
507
615
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
508
616
|
if (nwrite < 0) return -1;
|
509
617
|
if (nwrite != RSTRING_LEN(data)) {
|
510
|
-
*errno_provenance =
|
618
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
511
619
|
errno = EIO; /* Lies but whatever */
|
512
620
|
return -1;
|
513
621
|
}
|
@@ -515,38 +623,27 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
515
623
|
close(fd);
|
516
624
|
ret = rename(tmp_path, path);
|
517
625
|
if (ret < 0) {
|
518
|
-
*errno_provenance =
|
626
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
627
|
+
return -1;
|
519
628
|
}
|
520
|
-
|
521
|
-
|
522
|
-
|
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);
|
629
|
+
ret = chmod(path, 0664 & ~current_umask);
|
630
|
+
if (ret < 0) {
|
631
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
535
632
|
}
|
536
|
-
return
|
633
|
+
return ret;
|
537
634
|
}
|
538
635
|
|
539
636
|
|
540
637
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
541
638
|
* long, into a buffer */
|
542
639
|
static ssize_t
|
543
|
-
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)
|
544
641
|
{
|
545
642
|
ssize_t nread;
|
546
643
|
*contents = ALLOC_N(char, size);
|
547
644
|
nread = read(fd, *contents, size);
|
548
645
|
if (nread < 0) {
|
549
|
-
*errno_provenance =
|
646
|
+
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
550
647
|
}
|
551
648
|
return nread;
|
552
649
|
}
|
@@ -596,13 +693,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
|
|
596
693
|
* - Return storage_to_output(storage_data)
|
597
694
|
*/
|
598
695
|
static VALUE
|
599
|
-
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)
|
600
697
|
{
|
601
698
|
struct bs_cache_key cached_key, current_key;
|
602
699
|
char * contents = NULL;
|
603
700
|
int cache_fd = -1, current_fd = -1;
|
604
701
|
int res, valid_cache = 0, exception_tag = 0;
|
605
|
-
char * errno_provenance = NULL;
|
702
|
+
const char * errno_provenance = NULL;
|
606
703
|
|
607
704
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
608
705
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
@@ -616,26 +713,34 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
616
713
|
|
617
714
|
/* Open the cache key if it exists, and read its cache key in */
|
618
715
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
619
|
-
if (cache_fd ==
|
716
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
620
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
|
+
}
|
621
721
|
} else if (cache_fd < 0) {
|
622
722
|
goto fail_errno;
|
623
723
|
} else {
|
624
724
|
/* True if the cache existed and no invalidating changes have occurred since
|
625
725
|
* it was generated. */
|
626
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
|
+
}
|
627
732
|
}
|
628
733
|
|
629
734
|
if (valid_cache) {
|
630
735
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
631
736
|
res = fetch_cached_data(
|
632
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
737
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
633
738
|
&output_data, &exception_tag, &errno_provenance
|
634
739
|
);
|
635
|
-
if (exception_tag != 0)
|
636
|
-
else if (res ==
|
637
|
-
else if (res == ERROR_WITH_ERRNO)
|
638
|
-
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 */
|
639
744
|
}
|
640
745
|
close(cache_fd);
|
641
746
|
cache_fd = -1;
|
@@ -643,15 +748,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
643
748
|
|
644
749
|
/* Read the contents of the source file into a buffer */
|
645
750
|
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
646
|
-
input_data =
|
751
|
+
input_data = rb_str_new(contents, current_key.size);
|
647
752
|
|
648
753
|
/* 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);
|
754
|
+
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
650
755
|
if (exception_tag != 0) goto raise;
|
651
756
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
652
757
|
* to cache anything; just return input_to_output(input_data) */
|
653
758
|
if (storage_data == uncompilable) {
|
654
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
759
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
655
760
|
if (exception_tag != 0) goto raise;
|
656
761
|
goto succeed;
|
657
762
|
}
|
@@ -663,17 +768,17 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
663
768
|
if (res < 0) goto fail_errno;
|
664
769
|
|
665
770
|
/* Having written the cache, now convert storage_data to output_data */
|
666
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
771
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
667
772
|
if (exception_tag != 0) goto raise;
|
668
773
|
|
669
774
|
/* If output_data is nil, delete the cache entry and generate the output
|
670
775
|
* using input_to_output */
|
671
776
|
if (NIL_P(output_data)) {
|
672
777
|
if (unlink(cache_path) < 0) {
|
673
|
-
errno_provenance =
|
778
|
+
errno_provenance = "bs_fetch:unlink";
|
674
779
|
goto fail_errno;
|
675
780
|
}
|
676
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
781
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
677
782
|
if (exception_tag != 0) goto raise;
|
678
783
|
}
|
679
784
|
|
@@ -689,11 +794,7 @@ succeed:
|
|
689
794
|
return output_data;
|
690
795
|
fail_errno:
|
691
796
|
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
|
-
}
|
797
|
+
exception = rb_syserr_new(errno, errno_provenance);
|
697
798
|
rb_exc_raise(exception);
|
698
799
|
__builtin_unreachable();
|
699
800
|
raise:
|
@@ -708,6 +809,79 @@ invalid_type_storage_data:
|
|
708
809
|
#undef CLEANUP
|
709
810
|
}
|
710
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
|
+
|
711
885
|
/*****************************************************************************/
|
712
886
|
/********************* Handler Wrappers **************************************/
|
713
887
|
/*****************************************************************************
|
@@ -727,11 +901,13 @@ invalid_type_storage_data:
|
|
727
901
|
|
728
902
|
struct s2o_data {
|
729
903
|
VALUE handler;
|
904
|
+
VALUE args;
|
730
905
|
VALUE storage_data;
|
731
906
|
};
|
732
907
|
|
733
908
|
struct i2o_data {
|
734
909
|
VALUE handler;
|
910
|
+
VALUE args;
|
735
911
|
VALUE input_data;
|
736
912
|
};
|
737
913
|
|
@@ -745,15 +921,16 @@ static VALUE
|
|
745
921
|
prot_storage_to_output(VALUE arg)
|
746
922
|
{
|
747
923
|
struct s2o_data * data = (struct s2o_data *)arg;
|
748
|
-
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);
|
749
925
|
}
|
750
926
|
|
751
927
|
static int
|
752
|
-
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)
|
753
929
|
{
|
754
930
|
int state;
|
755
931
|
struct s2o_data s2o_data = {
|
756
932
|
.handler = handler,
|
933
|
+
.args = args,
|
757
934
|
.storage_data = storage_data,
|
758
935
|
};
|
759
936
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -761,10 +938,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
761
938
|
}
|
762
939
|
|
763
940
|
static void
|
764
|
-
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)
|
765
942
|
{
|
766
943
|
struct i2o_data i2o_data = {
|
767
944
|
.handler = handler,
|
945
|
+
.args = args,
|
768
946
|
.input_data = input_data,
|
769
947
|
};
|
770
948
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -774,7 +952,7 @@ static VALUE
|
|
774
952
|
prot_input_to_output(VALUE arg)
|
775
953
|
{
|
776
954
|
struct i2o_data * data = (struct i2o_data *)arg;
|
777
|
-
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);
|
778
956
|
}
|
779
957
|
|
780
958
|
static VALUE
|
@@ -785,7 +963,7 @@ try_input_to_storage(VALUE arg)
|
|
785
963
|
}
|
786
964
|
|
787
965
|
static VALUE
|
788
|
-
rescue_input_to_storage(VALUE arg)
|
966
|
+
rescue_input_to_storage(VALUE arg, VALUE e)
|
789
967
|
{
|
790
968
|
return uncompilable;
|
791
969
|
}
|
@@ -801,7 +979,7 @@ prot_input_to_storage(VALUE arg)
|
|
801
979
|
}
|
802
980
|
|
803
981
|
static int
|
804
|
-
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)
|
805
983
|
{
|
806
984
|
int state;
|
807
985
|
struct i2s_data i2s_data = {
|