bootsnap 1.17.0 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +148 -11
- data/LICENSE.txt +2 -1
- data/README.md +62 -14
- data/ext/bootsnap/bootsnap.c +475 -182
- data/ext/bootsnap/extconf.rb +20 -12
- data/lib/bootsnap/bundler.rb +1 -1
- data/lib/bootsnap/cli/worker_pool.rb +73 -1
- data/lib/bootsnap/cli.rb +22 -45
- data/lib/bootsnap/compile_cache/iseq.rb +185 -79
- data/lib/bootsnap/compile_cache/ruby_bug_22023_canary.rb +1 -0
- data/lib/bootsnap/compile_cache/yaml.rb +102 -50
- data/lib/bootsnap/compile_cache.rb +8 -12
- data/lib/bootsnap/explicit_require.rb +5 -0
- data/lib/bootsnap/load_path_cache/cache.rb +12 -15
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +13 -8
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +2 -2
- data/lib/bootsnap/load_path_cache/path.rb +35 -28
- data/lib/bootsnap/load_path_cache/path_scanner.rb +79 -33
- data/lib/bootsnap/load_path_cache/store.rb +6 -4
- data/lib/bootsnap/load_path_cache.rb +8 -8
- data/lib/bootsnap/rake.rb +14 -0
- data/lib/bootsnap/setup.rb +1 -1
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +86 -19
- metadata +12 -15
- data/ext/bootsnap/bootsnap.h +0 -6
- data/lib/bootsnap/compile_cache/json.rb +0 -89
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -11,55 +11,65 @@
|
|
|
11
11
|
* here.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
#include "bootsnap.h"
|
|
15
14
|
#include "ruby.h"
|
|
16
15
|
#include <stdint.h>
|
|
17
16
|
#include <stdbool.h>
|
|
18
17
|
#include <sys/types.h>
|
|
19
18
|
#include <errno.h>
|
|
20
19
|
#include <fcntl.h>
|
|
20
|
+
#include <unistd.h>
|
|
21
21
|
#include <sys/stat.h>
|
|
22
|
+
#include <dirent.h>
|
|
23
|
+
|
|
24
|
+
#ifndef RBIMPL_ATTR_NORETURN
|
|
25
|
+
#define RBIMPL_ATTR_NORETURN()
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
#ifdef __APPLE__
|
|
29
|
+
// The symbol is present, however not in the headers
|
|
30
|
+
// See: https://github.com/rails/bootsnap/issues/470
|
|
31
|
+
extern int fdatasync(int);
|
|
32
|
+
#endif
|
|
33
|
+
|
|
34
|
+
#ifndef O_NOATIME
|
|
35
|
+
#define O_NOATIME 0
|
|
36
|
+
#endif
|
|
22
37
|
|
|
23
38
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
|
24
39
|
* 981 for the cache dir */
|
|
25
40
|
#define MAX_CACHEPATH_SIZE 1000
|
|
26
41
|
#define MAX_CACHEDIR_SIZE 981
|
|
27
42
|
|
|
28
|
-
#define KEY_SIZE
|
|
43
|
+
#define KEY_SIZE 32
|
|
29
44
|
|
|
30
45
|
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
|
31
46
|
|
|
32
47
|
#ifndef RB_UNLIKELY
|
|
33
|
-
|
|
48
|
+
#define RB_UNLIKELY(x) (x)
|
|
34
49
|
#endif
|
|
35
50
|
|
|
36
51
|
/*
|
|
37
|
-
* An instance of this key is written as the first
|
|
52
|
+
* An instance of this key is written as the first `KEY_SIZE` bytes of each cache file.
|
|
38
53
|
* The mtime and size members track whether the file contents have changed, and
|
|
39
|
-
* the
|
|
40
|
-
* changes to the environment that could invalidate compile results without
|
|
54
|
+
* the ruby_version_digest (bootsnap_cache_version + RUBY_DESCRIPTION) and compile_option
|
|
55
|
+
* members track changes to the environment that could invalidate compile results without
|
|
41
56
|
* file contents having changed. The data_size member is not truly part of the
|
|
42
57
|
* "key". Really, this could be called a "header" with the first six members
|
|
43
58
|
* being an embedded "key" struct and an additional data_size member.
|
|
44
59
|
*
|
|
45
60
|
* The data_size indicates the remaining number of bytes in the cache file
|
|
46
61
|
* after the header (the size of the cached artifact).
|
|
47
|
-
*
|
|
48
|
-
* After data_size, the struct is padded to 64 bytes.
|
|
49
62
|
*/
|
|
50
63
|
struct bs_cache_key {
|
|
51
|
-
|
|
52
|
-
uint32_t ruby_platform;
|
|
53
|
-
uint32_t compile_option;
|
|
54
|
-
uint32_t ruby_revision;
|
|
55
|
-
uint64_t size;
|
|
64
|
+
uint64_t ruby_version_digest;
|
|
56
65
|
uint64_t mtime;
|
|
57
|
-
uint64_t
|
|
58
|
-
|
|
66
|
+
uint64_t digest;
|
|
67
|
+
uint32_t size;
|
|
68
|
+
uint32_t data_size;
|
|
59
69
|
} __attribute__((packed));
|
|
60
70
|
|
|
61
71
|
/*
|
|
62
|
-
* If the struct padding isn't correct to pad the key to
|
|
72
|
+
* If the struct padding isn't correct to pad the key to `KEY_SIZE` bytes, refuse to
|
|
63
73
|
* compile.
|
|
64
74
|
*/
|
|
65
75
|
#define STATIC_ASSERT(X) STATIC_ASSERT2(X,__LINE__)
|
|
@@ -69,46 +79,57 @@ struct bs_cache_key {
|
|
|
69
79
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
|
70
80
|
|
|
71
81
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
|
72
|
-
static const uint32_t
|
|
73
|
-
|
|
74
|
-
/*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
static const uint32_t bootsnap_cache_version = 8;
|
|
83
|
+
|
|
84
|
+
/* Invalidates cache when switching ruby version, platform or ABI */
|
|
85
|
+
static uint64_t base_ruby_version_digest = 0;
|
|
86
|
+
|
|
87
|
+
// `base_ruby_version_digest` combined with RubyVM::InstructionSequence.compile_option
|
|
88
|
+
// Invalidates cache when RubyVM::InstructionSequence.compile_option changes
|
|
89
|
+
static uint64_t current_ruby_version_digest = 0;
|
|
90
|
+
|
|
81
91
|
/* Current umask */
|
|
82
92
|
static mode_t current_umask;
|
|
83
93
|
|
|
84
94
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
|
85
95
|
static VALUE rb_mBootsnap;
|
|
86
|
-
static VALUE rb_mBootsnap_CompileCache;
|
|
87
96
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
|
88
97
|
static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
|
89
98
|
static ID instrumentation_method;
|
|
90
|
-
static VALUE sym_miss;
|
|
91
|
-
static VALUE sym_stale;
|
|
99
|
+
static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated;
|
|
92
100
|
static bool instrumentation_enabled = false;
|
|
93
101
|
static bool readonly = false;
|
|
102
|
+
static bool revalidation = false;
|
|
103
|
+
static bool perm_issue = false;
|
|
104
|
+
|
|
105
|
+
static ID id_storage_to_output, id_input_to_output, id_input_to_storage;
|
|
94
106
|
|
|
95
107
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
|
96
108
|
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
|
97
109
|
static VALUE bs_readonly_set(VALUE self, VALUE enabled);
|
|
110
|
+
static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
|
|
98
111
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
|
99
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
|
100
|
-
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
|
112
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args);
|
|
113
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler);
|
|
101
114
|
|
|
102
115
|
/* Helpers */
|
|
103
|
-
|
|
116
|
+
enum cache_status {
|
|
117
|
+
miss,
|
|
118
|
+
hit,
|
|
119
|
+
stale,
|
|
120
|
+
};
|
|
121
|
+
static void bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
|
104
122
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
|
105
|
-
static
|
|
123
|
+
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
|
124
|
+
static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
|
|
125
|
+
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance);
|
|
126
|
+
|
|
127
|
+
static void bs_cache_key_digest(struct bs_cache_key * key, const VALUE input_data);
|
|
106
128
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
|
107
129
|
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
|
108
|
-
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
|
130
|
+
static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
|
109
131
|
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);
|
|
110
|
-
static
|
|
111
|
-
static uint32_t get_ruby_platform(void);
|
|
132
|
+
static uint64_t get_ruby_version_digest(void);
|
|
112
133
|
|
|
113
134
|
/*
|
|
114
135
|
* Helper functions to call ruby methods on handler object without crashing on
|
|
@@ -116,26 +137,125 @@ static uint32_t get_ruby_platform(void);
|
|
|
116
137
|
*/
|
|
117
138
|
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
|
118
139
|
static VALUE prot_input_to_output(VALUE arg);
|
|
119
|
-
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
|
140
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * output_data, int * exception_tag);
|
|
120
141
|
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
|
121
142
|
struct s2o_data;
|
|
122
143
|
struct i2o_data;
|
|
123
144
|
struct i2s_data;
|
|
124
145
|
|
|
125
|
-
/* https://bugs.ruby-lang.org/issues/13667 */
|
|
126
|
-
extern VALUE rb_get_coverages(void);
|
|
127
146
|
static VALUE
|
|
128
|
-
|
|
147
|
+
bs_rb_get_path(VALUE self, VALUE fname)
|
|
148
|
+
{
|
|
149
|
+
return rb_get_path(fname);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#ifdef HAVE_FSTATAT
|
|
153
|
+
|
|
154
|
+
RBIMPL_ATTR_NORETURN()
|
|
155
|
+
static void
|
|
156
|
+
bs_syserr_fail_path(const char *func_name, int n, VALUE path)
|
|
157
|
+
{
|
|
158
|
+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s", func_name, RSTRING_PTR(path)));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
RBIMPL_ATTR_NORETURN()
|
|
162
|
+
static void
|
|
163
|
+
bs_syserr_fail_dir_entry(const char *func_name, int n, VALUE dir, const char *d_name)
|
|
129
164
|
{
|
|
130
|
-
|
|
131
|
-
return RTEST(cov) ? Qtrue : Qfalse;
|
|
165
|
+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s/%s", func_name, RSTRING_PTR(dir), d_name));
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
static VALUE
|
|
135
|
-
|
|
169
|
+
bs_rb_scan_dir(VALUE self, VALUE abspath)
|
|
136
170
|
{
|
|
137
|
-
|
|
171
|
+
Check_Type(abspath, T_STRING);
|
|
172
|
+
|
|
173
|
+
VALUE dirs = rb_ary_new();
|
|
174
|
+
VALUE requirables = rb_ary_new();
|
|
175
|
+
VALUE result = rb_ary_new_from_args(2, requirables, dirs);
|
|
176
|
+
|
|
177
|
+
DIR *dirp = opendir(RSTRING_PTR(abspath));
|
|
178
|
+
if (dirp == NULL) {
|
|
179
|
+
if (errno == ENOTDIR || errno == ENOENT) {
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
bs_syserr_fail_path("opendir", errno, abspath);
|
|
184
|
+
return Qundef;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
struct dirent *entry;
|
|
188
|
+
struct stat st;
|
|
189
|
+
int dfd = -1;
|
|
190
|
+
|
|
191
|
+
while (1) {
|
|
192
|
+
errno = 0;
|
|
193
|
+
|
|
194
|
+
entry = readdir(dirp);
|
|
195
|
+
if (entry == NULL) break;
|
|
196
|
+
|
|
197
|
+
if (entry->d_name[0] == '.') continue;
|
|
198
|
+
|
|
199
|
+
if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
|
|
200
|
+
// Note: the original implementation of LoadPathCache did follow symlink.
|
|
201
|
+
// So this is replicated here, but I'm not sure it's a good idea.
|
|
202
|
+
if (dfd < 0) {
|
|
203
|
+
dfd = dirfd(dirp);
|
|
204
|
+
if (dfd < 0) {
|
|
205
|
+
int err = errno;
|
|
206
|
+
closedir(dirp);
|
|
207
|
+
bs_syserr_fail_path("dirfd", err, abspath);
|
|
208
|
+
return Qundef;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (fstatat(dfd, entry->d_name, &st, 0)) {
|
|
213
|
+
if (errno == ENOENT) continue; // Broken symlink
|
|
214
|
+
|
|
215
|
+
int err = errno;
|
|
216
|
+
closedir(dirp);
|
|
217
|
+
bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
|
|
218
|
+
return Qundef;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (S_ISREG(st.st_mode)) {
|
|
222
|
+
entry->d_type = DT_REG;
|
|
223
|
+
} else if (S_ISDIR(st.st_mode)) {
|
|
224
|
+
entry->d_type = DT_DIR;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (entry->d_type == DT_DIR) {
|
|
229
|
+
rb_ary_push(dirs, rb_utf8_str_new_cstr(entry->d_name));
|
|
230
|
+
continue;
|
|
231
|
+
} else if (entry->d_type == DT_REG) {
|
|
232
|
+
size_t len = strlen(entry->d_name);
|
|
233
|
+
bool is_requirable = (
|
|
234
|
+
// Comparing 4B allows compiler to optimize this into a single 32b integer comparison.
|
|
235
|
+
(len > 3 && memcmp(entry->d_name + (len - 3), ".rb", 4) == 0)
|
|
236
|
+
|| (len > DLEXT_MAXLEN && memcmp(entry->d_name + (len - DLEXT_MAXLEN), DLEXT, DLEXT_MAXLEN + 1) == 0)
|
|
237
|
+
#ifdef DLEXT2
|
|
238
|
+
|| (len > DLEXT2_MAXLEN && memcmp(entry->d_name + (len - DLEXT2_MAXLEN), DLEXT2, DLEXT2_MAXLEN + 1) == 0)
|
|
239
|
+
#endif
|
|
240
|
+
);
|
|
241
|
+
if (is_requirable) {
|
|
242
|
+
rb_ary_push(requirables, rb_utf8_str_new(entry->d_name, len));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (errno) {
|
|
248
|
+
int err = errno;
|
|
249
|
+
closedir(dirp);
|
|
250
|
+
bs_syserr_fail_path("readdir", err, abspath);
|
|
251
|
+
} else if (closedir(dirp)) {
|
|
252
|
+
bs_syserr_fail_path("closedir", errno, abspath);
|
|
253
|
+
return Qundef;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return result;
|
|
138
257
|
}
|
|
258
|
+
#endif
|
|
139
259
|
|
|
140
260
|
/*
|
|
141
261
|
* Ruby C extensions are initialized by calling Init_<extname>.
|
|
@@ -149,29 +269,38 @@ Init_bootsnap(void)
|
|
|
149
269
|
{
|
|
150
270
|
rb_mBootsnap = rb_define_module("Bootsnap");
|
|
151
271
|
|
|
272
|
+
id_storage_to_output = rb_intern("storage_to_output");
|
|
273
|
+
id_input_to_output = rb_intern("input_to_output");
|
|
274
|
+
id_input_to_storage = rb_intern("input_to_storage");
|
|
275
|
+
|
|
152
276
|
rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
|
|
153
277
|
|
|
154
|
-
|
|
278
|
+
#ifdef HAVE_FSTATAT
|
|
279
|
+
VALUE rb_mBootsnap_LoadPathCache = rb_define_module_under(rb_mBootsnap, "LoadPathCache");
|
|
280
|
+
VALUE rb_mBootsnap_LoadPathCache_Native = rb_define_module_under(rb_mBootsnap_LoadPathCache, "Native");
|
|
281
|
+
|
|
282
|
+
rb_define_singleton_method(rb_mBootsnap_LoadPathCache_Native, "scan_dir", bs_rb_scan_dir, 1);
|
|
283
|
+
#endif
|
|
284
|
+
|
|
285
|
+
VALUE rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
|
|
155
286
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
|
156
287
|
rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
|
|
157
288
|
rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
|
|
158
289
|
|
|
159
|
-
|
|
160
|
-
current_ruby_platform = get_ruby_platform();
|
|
290
|
+
base_ruby_version_digest = get_ruby_version_digest();
|
|
161
291
|
|
|
162
292
|
instrumentation_method = rb_intern("_instrument");
|
|
163
293
|
|
|
294
|
+
sym_hit = ID2SYM(rb_intern("hit"));
|
|
164
295
|
sym_miss = ID2SYM(rb_intern("miss"));
|
|
165
|
-
rb_global_variable(&sym_miss);
|
|
166
|
-
|
|
167
296
|
sym_stale = ID2SYM(rb_intern("stale"));
|
|
168
|
-
|
|
297
|
+
sym_revalidated = ID2SYM(rb_intern("revalidated"));
|
|
169
298
|
|
|
170
299
|
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
|
171
300
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
|
172
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "
|
|
173
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
|
174
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile,
|
|
301
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
|
|
302
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 5);
|
|
303
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 4);
|
|
175
304
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
|
176
305
|
|
|
177
306
|
current_umask = umask(0777);
|
|
@@ -185,6 +314,20 @@ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
|
|
185
314
|
return enabled;
|
|
186
315
|
}
|
|
187
316
|
|
|
317
|
+
static inline void
|
|
318
|
+
bs_instrumentation(VALUE event, VALUE path)
|
|
319
|
+
{
|
|
320
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
|
321
|
+
// If the funcall raises and rescues an error we will lose the errinfo
|
|
322
|
+
// that exists now so we save it before and restore it after.
|
|
323
|
+
VALUE saved_errinfo = rb_errinfo();
|
|
324
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
|
|
325
|
+
// If the ruby code in instrumentation_method raised we won't reach this point.
|
|
326
|
+
// If it didn't raise it _might_ have rescued so we should restore errinfo.
|
|
327
|
+
rb_set_errinfo(saved_errinfo);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
188
331
|
static VALUE
|
|
189
332
|
bs_readonly_set(VALUE self, VALUE enabled)
|
|
190
333
|
{
|
|
@@ -192,28 +335,17 @@ bs_readonly_set(VALUE self, VALUE enabled)
|
|
|
192
335
|
return enabled;
|
|
193
336
|
}
|
|
194
337
|
|
|
195
|
-
/*
|
|
196
|
-
* Bootsnap's ruby code registers a hook that notifies us via this function
|
|
197
|
-
* when compile_option changes. These changes invalidate all existing caches.
|
|
198
|
-
*
|
|
199
|
-
* Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
|
|
200
|
-
* can be represented by a uint.
|
|
201
|
-
*/
|
|
202
338
|
static VALUE
|
|
203
|
-
|
|
339
|
+
bs_revalidation_set(VALUE self, VALUE enabled)
|
|
204
340
|
{
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
current_compile_option_crc32 = NUM2UINT(crc32_v);
|
|
209
|
-
return Qnil;
|
|
341
|
+
revalidation = RTEST(enabled);
|
|
342
|
+
return enabled;
|
|
210
343
|
}
|
|
211
344
|
|
|
212
345
|
static uint64_t
|
|
213
|
-
fnv1a_64_iter(uint64_t h, const
|
|
346
|
+
fnv1a_64_iter(uint64_t h, const unsigned char *s, size_t len)
|
|
214
347
|
{
|
|
215
|
-
unsigned char *
|
|
216
|
-
unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
|
|
348
|
+
const unsigned char *str_end = s + len;
|
|
217
349
|
|
|
218
350
|
while (s < str_end) {
|
|
219
351
|
h ^= (uint64_t)*s++;
|
|
@@ -224,45 +356,43 @@ fnv1a_64_iter(uint64_t h, const VALUE str)
|
|
|
224
356
|
}
|
|
225
357
|
|
|
226
358
|
static uint64_t
|
|
227
|
-
|
|
359
|
+
fnv1a_64_iter_str(uint64_t h, const VALUE str)
|
|
360
|
+
{
|
|
361
|
+
Check_Type(str, T_STRING);
|
|
362
|
+
return fnv1a_64_iter(h, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
static uint64_t
|
|
366
|
+
fnv1a_64_str(const VALUE str)
|
|
228
367
|
{
|
|
229
368
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
|
230
|
-
return
|
|
369
|
+
return fnv1a_64_iter_str(h, str);
|
|
231
370
|
}
|
|
232
371
|
|
|
233
372
|
/*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
373
|
+
* Bootsnap's ruby code registers a hook that notifies us via this function
|
|
374
|
+
* when compile_option changes. These changes invalidate all existing caches.
|
|
375
|
+
*
|
|
376
|
+
* Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
|
|
377
|
+
* can be represented by a uint.
|
|
236
378
|
*/
|
|
237
|
-
static
|
|
238
|
-
|
|
379
|
+
static VALUE
|
|
380
|
+
bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
239
381
|
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
|
|
243
|
-
if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
|
|
244
|
-
return FIX2INT(ruby_revision);
|
|
245
|
-
} else {
|
|
246
|
-
uint64_t hash;
|
|
247
|
-
|
|
248
|
-
hash = fnv1a_64(ruby_revision);
|
|
249
|
-
return (uint32_t)(hash >> 32);
|
|
382
|
+
if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) {
|
|
383
|
+
Check_Type(crc32_v, T_FIXNUM);
|
|
250
384
|
}
|
|
385
|
+
uint32_t crc32 = (uint32_t)NUM2UINT(crc32_v);
|
|
386
|
+
current_ruby_version_digest = fnv1a_64_iter(base_ruby_version_digest, (unsigned char *)&crc32, sizeof(crc32));
|
|
387
|
+
return Qnil;
|
|
251
388
|
}
|
|
252
389
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
* (or OS version), we need to invalidate the cache.
|
|
256
|
-
*/
|
|
257
|
-
static uint32_t
|
|
258
|
-
get_ruby_platform(void)
|
|
390
|
+
static uint64_t
|
|
391
|
+
get_ruby_version_digest(void)
|
|
259
392
|
{
|
|
260
|
-
uint64_t hash;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
|
264
|
-
hash = fnv1a_64(ruby_platform);
|
|
265
|
-
return (uint32_t)(hash >> 32);
|
|
393
|
+
uint64_t hash = fnv1a_64_str(rb_const_get(rb_mBootsnap, rb_intern("RUBY_CACHE_KEY")));
|
|
394
|
+
hash = fnv1a_64_iter(hash, (unsigned char *)&bootsnap_cache_version, sizeof(bootsnap_cache_version));
|
|
395
|
+
return hash;
|
|
266
396
|
}
|
|
267
397
|
|
|
268
398
|
/*
|
|
@@ -271,15 +401,35 @@ get_ruby_platform(void)
|
|
|
271
401
|
* be stored.
|
|
272
402
|
*
|
|
273
403
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
|
404
|
+
*
|
|
405
|
+
* `path_v` must already have been through FilePathValue(): the callers read it
|
|
406
|
+
* with RSTRING_PTR() too, so the conversion has to happen in the frame that
|
|
407
|
+
* owns the variable.
|
|
274
408
|
*/
|
|
275
409
|
static void
|
|
276
|
-
bs_cache_path(
|
|
410
|
+
bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
|
277
411
|
{
|
|
278
|
-
|
|
412
|
+
Check_Type(cachedir_v, T_STRING);
|
|
413
|
+
Check_Type(path_v, T_STRING);
|
|
414
|
+
|
|
415
|
+
long namespace_len = 0;
|
|
416
|
+
if (!NIL_P(namespace_v)) {
|
|
417
|
+
Check_Type(namespace_v, T_STRING);
|
|
418
|
+
namespace_len = RSTRING_LEN(namespace_v);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (RSTRING_LEN(cachedir_v) + namespace_len > MAX_CACHEDIR_SIZE) {
|
|
422
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const char * cachedir = RSTRING_PTR(cachedir_v);
|
|
426
|
+
const char * namespace = NIL_P(namespace_v) ? "" : RSTRING_PTR(namespace_v);
|
|
427
|
+
|
|
428
|
+
uint64_t hash = fnv1a_64_str(path_v);
|
|
279
429
|
uint8_t first_byte = (hash >> (64 - 8));
|
|
280
430
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
|
281
431
|
|
|
282
|
-
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
|
432
|
+
sprintf(*cache_path, "%s%s/%02"PRIx8"/%014"PRIx64, cachedir, namespace, first_byte, remainder);
|
|
283
433
|
}
|
|
284
434
|
|
|
285
435
|
/*
|
|
@@ -290,17 +440,79 @@ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_C
|
|
|
290
440
|
* The data_size member is not compared, as it serves more of a "header"
|
|
291
441
|
* function.
|
|
292
442
|
*/
|
|
293
|
-
static
|
|
294
|
-
|
|
443
|
+
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
|
|
444
|
+
struct bs_cache_key *k2) {
|
|
445
|
+
if (k1->ruby_version_digest == k2->ruby_version_digest &&
|
|
446
|
+
k1->size == k2->size) {
|
|
447
|
+
if (k1->mtime == k2->mtime) {
|
|
448
|
+
return hit;
|
|
449
|
+
}
|
|
450
|
+
if (revalidation) {
|
|
451
|
+
return stale;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return miss;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
|
|
458
|
+
struct bs_cache_key *cached_key,
|
|
459
|
+
const VALUE input_data)
|
|
460
|
+
{
|
|
461
|
+
bs_cache_key_digest(current_key, input_data);
|
|
462
|
+
return current_key->digest == cached_key->digest;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
static ssize_t
|
|
466
|
+
resumable_write(int fd, const void *buf, size_t count)
|
|
467
|
+
{
|
|
468
|
+
size_t total_written = 0;
|
|
469
|
+
const char *ptr = buf;
|
|
470
|
+
|
|
471
|
+
while (count > 0) {
|
|
472
|
+
ssize_t res = write(fd, ptr, count);
|
|
473
|
+
|
|
474
|
+
if (res < 0) {
|
|
475
|
+
if (errno == EINTR) {
|
|
476
|
+
continue; // Interrupted by signal, try again
|
|
477
|
+
}
|
|
478
|
+
return -1; // Real error
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
ptr += res;
|
|
482
|
+
count -= res;
|
|
483
|
+
total_written += res;
|
|
484
|
+
}
|
|
485
|
+
return total_written;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
|
|
295
489
|
{
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
490
|
+
old_key->mtime = current_key->mtime;
|
|
491
|
+
lseek(cache_fd, 0, SEEK_SET);
|
|
492
|
+
ssize_t nwrite = resumable_write(cache_fd, old_key, KEY_SIZE);
|
|
493
|
+
if (nwrite < 0) {
|
|
494
|
+
*errno_provenance = "update_cache_key:write";
|
|
495
|
+
return -1;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
#ifdef HAVE_FDATASYNC
|
|
499
|
+
if (fdatasync(cache_fd) < 0) {
|
|
500
|
+
*errno_provenance = "update_cache_key:fdatasync";
|
|
501
|
+
return -1;
|
|
502
|
+
}
|
|
503
|
+
#endif
|
|
504
|
+
|
|
505
|
+
return 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/*
|
|
509
|
+
* Fills the cache key digest.
|
|
510
|
+
*/
|
|
511
|
+
static void bs_cache_key_digest(struct bs_cache_key *key,
|
|
512
|
+
const VALUE input_data) {
|
|
513
|
+
if (key->digest)
|
|
514
|
+
return;
|
|
515
|
+
key->digest = fnv1a_64_str(input_data);
|
|
304
516
|
}
|
|
305
517
|
|
|
306
518
|
/*
|
|
@@ -309,25 +521,16 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
|
309
521
|
* conversions on the ruby VALUE arguments before passing them along.
|
|
310
522
|
*/
|
|
311
523
|
static VALUE
|
|
312
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
|
524
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args)
|
|
313
525
|
{
|
|
314
526
|
FilePathValue(path_v);
|
|
315
527
|
|
|
316
|
-
Check_Type(cachedir_v, T_STRING);
|
|
317
|
-
Check_Type(path_v, T_STRING);
|
|
318
|
-
|
|
319
|
-
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
320
|
-
rb_raise(rb_eArgError, "cachedir too long");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
char * cachedir = RSTRING_PTR(cachedir_v);
|
|
324
|
-
char * path = RSTRING_PTR(path_v);
|
|
325
528
|
char cache_path[MAX_CACHEPATH_SIZE];
|
|
326
529
|
|
|
327
530
|
/* generate cache path to cache_path */
|
|
328
|
-
bs_cache_path(
|
|
531
|
+
bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
|
|
329
532
|
|
|
330
|
-
return bs_fetch(
|
|
533
|
+
return bs_fetch(RSTRING_PTR(path_v), path_v, cache_path, handler, args);
|
|
331
534
|
}
|
|
332
535
|
|
|
333
536
|
/*
|
|
@@ -336,37 +539,44 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
|
|
|
336
539
|
* and doesn't return the content.
|
|
337
540
|
*/
|
|
338
541
|
static VALUE
|
|
339
|
-
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
542
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler)
|
|
340
543
|
{
|
|
341
544
|
FilePathValue(path_v);
|
|
342
545
|
|
|
343
|
-
Check_Type(cachedir_v, T_STRING);
|
|
344
|
-
Check_Type(path_v, T_STRING);
|
|
345
|
-
|
|
346
|
-
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
347
|
-
rb_raise(rb_eArgError, "cachedir too long");
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
char * cachedir = RSTRING_PTR(cachedir_v);
|
|
351
|
-
char * path = RSTRING_PTR(path_v);
|
|
352
546
|
char cache_path[MAX_CACHEPATH_SIZE];
|
|
353
|
-
|
|
354
547
|
/* generate cache path to cache_path */
|
|
355
|
-
bs_cache_path(
|
|
548
|
+
bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
|
|
549
|
+
|
|
550
|
+
return bs_precompile(RSTRING_PTR(path_v), path_v, cache_path, handler);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
static int bs_open_noatime(const char *path, int flags) {
|
|
554
|
+
int fd = 1;
|
|
555
|
+
if (!perm_issue) {
|
|
556
|
+
fd = open(path, flags | O_NOATIME);
|
|
557
|
+
if (fd < 0 && errno == EPERM) {
|
|
558
|
+
errno = 0;
|
|
559
|
+
perm_issue = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
356
562
|
|
|
357
|
-
|
|
563
|
+
if (perm_issue) {
|
|
564
|
+
fd = open(path, flags);
|
|
565
|
+
}
|
|
566
|
+
return fd;
|
|
358
567
|
}
|
|
568
|
+
|
|
359
569
|
/*
|
|
360
570
|
* Open the file we want to load/cache and generate a cache key for it if it
|
|
361
571
|
* was loaded.
|
|
362
572
|
*/
|
|
363
573
|
static int
|
|
364
|
-
open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
|
574
|
+
open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
|
365
575
|
{
|
|
366
576
|
struct stat statbuf;
|
|
367
577
|
int fd;
|
|
368
578
|
|
|
369
|
-
fd =
|
|
579
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
|
370
580
|
if (fd < 0) {
|
|
371
581
|
*errno_provenance = "bs_fetch:open_current_file:open";
|
|
372
582
|
return fd;
|
|
@@ -383,12 +593,19 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
|
383
593
|
return -1;
|
|
384
594
|
}
|
|
385
595
|
|
|
386
|
-
key->
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
596
|
+
key->ruby_version_digest = current_ruby_version_digest;
|
|
597
|
+
|
|
598
|
+
// We're limited to file of 4GiB or less. Hopefully that's enough for everyone.
|
|
599
|
+
if (statbuf.st_size > (uint32_t)-1) {
|
|
600
|
+
*errno_provenance = "bs_fetch:open_current_file:file_too_big";
|
|
601
|
+
close(fd);
|
|
602
|
+
errno = EFBIG;
|
|
603
|
+
return -1;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
key->size = (uint32_t)statbuf.st_size;
|
|
607
|
+
key->mtime = (uint64_t)statbuf.st_mtime;
|
|
608
|
+
key->digest = 0;
|
|
392
609
|
|
|
393
610
|
return fd;
|
|
394
611
|
}
|
|
@@ -432,7 +649,12 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
|
432
649
|
{
|
|
433
650
|
int fd, res;
|
|
434
651
|
|
|
435
|
-
|
|
652
|
+
if (readonly || !revalidation) {
|
|
653
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
|
654
|
+
} else {
|
|
655
|
+
fd = bs_open_noatime(path, O_RDWR);
|
|
656
|
+
}
|
|
657
|
+
|
|
436
658
|
if (fd < 0) {
|
|
437
659
|
*errno_provenance = "bs_fetch:open_cache_file:open";
|
|
438
660
|
return CACHE_MISS;
|
|
@@ -453,10 +675,10 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
|
453
675
|
|
|
454
676
|
/*
|
|
455
677
|
* The cache file is laid out like:
|
|
456
|
-
* 0
|
|
457
|
-
*
|
|
678
|
+
* 0...`KEY_SIZE` : bs_cache_key
|
|
679
|
+
* `KEY_SIZE`..-1 : cached artifact
|
|
458
680
|
*
|
|
459
|
-
* This function takes a file descriptor whose position is pre-set to
|
|
681
|
+
* This function takes a file descriptor whose position is pre-set to `KEY_SIZE`, and
|
|
460
682
|
* the data_size (corresponding to the remaining number of bytes) listed in the
|
|
461
683
|
* cache header.
|
|
462
684
|
*
|
|
@@ -532,6 +754,17 @@ mkpath(char * file_path, mode_t mode)
|
|
|
532
754
|
return 0;
|
|
533
755
|
}
|
|
534
756
|
|
|
757
|
+
static int
|
|
758
|
+
bs_fchmod(int fd, const char *path, mode_t mode)
|
|
759
|
+
{
|
|
760
|
+
#ifdef _WIN32
|
|
761
|
+
setmode(fd, O_BINARY);
|
|
762
|
+
return chmod(path, mode);
|
|
763
|
+
#else
|
|
764
|
+
return fchmod(fd, mode);
|
|
765
|
+
#endif
|
|
766
|
+
}
|
|
767
|
+
|
|
535
768
|
/*
|
|
536
769
|
* Write a cache header/key and a compiled artifact to a given cache path by
|
|
537
770
|
* writing to a tmpfile and then renaming the tmpfile over top of the final
|
|
@@ -545,13 +778,18 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
|
|
|
545
778
|
int fd, ret, attempt;
|
|
546
779
|
ssize_t nwrite;
|
|
547
780
|
|
|
781
|
+
uint64_t data_size = RSTRING_LEN(data);
|
|
782
|
+
if (data_size > (uint32_t)-1) {
|
|
783
|
+
return 0; // Don't cache.
|
|
784
|
+
}
|
|
785
|
+
|
|
548
786
|
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
|
549
787
|
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
|
550
788
|
strcat(tmp_path, ".tmp.XXXXXX");
|
|
551
789
|
|
|
552
790
|
// mkstemp modifies the template to be the actual created path
|
|
553
791
|
fd = mkstemp(tmp_path);
|
|
554
|
-
if (fd
|
|
792
|
+
if (fd >= 0) break;
|
|
555
793
|
|
|
556
794
|
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
|
557
795
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
|
@@ -563,45 +801,42 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
|
|
|
563
801
|
return -1;
|
|
564
802
|
}
|
|
565
803
|
|
|
566
|
-
if (
|
|
804
|
+
if (bs_fchmod(fd, tmp_path, 0644 & ~current_umask) < 0) {
|
|
805
|
+
close(fd);
|
|
567
806
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
|
568
807
|
return -1;
|
|
569
808
|
}
|
|
570
809
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
#endif
|
|
574
|
-
|
|
575
|
-
key->data_size = RSTRING_LEN(data);
|
|
576
|
-
nwrite = write(fd, key, KEY_SIZE);
|
|
810
|
+
key->data_size = (uint32_t)data_size;
|
|
811
|
+
nwrite = resumable_write(fd, key, KEY_SIZE);
|
|
577
812
|
if (nwrite < 0) {
|
|
813
|
+
close(fd);
|
|
578
814
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
|
579
815
|
return -1;
|
|
580
816
|
}
|
|
581
817
|
if (nwrite != KEY_SIZE) {
|
|
818
|
+
close(fd);
|
|
582
819
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
|
583
820
|
errno = EIO; /* Lies but whatever */
|
|
584
821
|
return -1;
|
|
585
822
|
}
|
|
586
823
|
|
|
587
|
-
nwrite =
|
|
824
|
+
nwrite = resumable_write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
|
588
825
|
if (nwrite < 0) return -1;
|
|
589
826
|
if (nwrite != RSTRING_LEN(data)) {
|
|
827
|
+
close(fd);
|
|
590
828
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
|
591
829
|
errno = EIO; /* Lies but whatever */
|
|
592
830
|
return -1;
|
|
593
831
|
}
|
|
594
832
|
|
|
595
833
|
close(fd);
|
|
834
|
+
|
|
596
835
|
ret = rename(tmp_path, path);
|
|
597
836
|
if (ret < 0) {
|
|
598
837
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
|
599
838
|
return -1;
|
|
600
839
|
}
|
|
601
|
-
ret = chmod(path, 0664 & ~current_umask);
|
|
602
|
-
if (ret < 0) {
|
|
603
|
-
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
|
604
|
-
}
|
|
605
840
|
return ret;
|
|
606
841
|
}
|
|
607
842
|
|
|
@@ -677,7 +912,8 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
677
912
|
int res, valid_cache = 0, exception_tag = 0;
|
|
678
913
|
const char * errno_provenance = NULL;
|
|
679
914
|
|
|
680
|
-
VALUE
|
|
915
|
+
VALUE status = Qfalse;
|
|
916
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
|
681
917
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
|
682
918
|
VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
|
|
683
919
|
|
|
@@ -695,20 +931,44 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
695
931
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
|
696
932
|
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
|
697
933
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
|
698
|
-
|
|
699
|
-
rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
|
700
|
-
}
|
|
934
|
+
bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
|
701
935
|
} else if (cache_fd < 0) {
|
|
702
936
|
exception_message = rb_str_new_cstr(cache_path);
|
|
703
937
|
goto fail_errno;
|
|
704
938
|
} else {
|
|
705
939
|
/* True if the cache existed and no invalidating changes have occurred since
|
|
706
940
|
* it was generated. */
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
941
|
+
|
|
942
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
|
943
|
+
case hit:
|
|
944
|
+
status = sym_hit;
|
|
945
|
+
valid_cache = true;
|
|
946
|
+
break;
|
|
947
|
+
case miss:
|
|
948
|
+
valid_cache = false;
|
|
949
|
+
break;
|
|
950
|
+
case stale:
|
|
951
|
+
valid_cache = false;
|
|
952
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size,
|
|
953
|
+
&errno_provenance)) == Qfalse) {
|
|
954
|
+
exception_message = path_v;
|
|
955
|
+
goto fail_errno;
|
|
711
956
|
}
|
|
957
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
|
958
|
+
if (valid_cache) {
|
|
959
|
+
if (!readonly) {
|
|
960
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
|
961
|
+
exception_message = path_v;
|
|
962
|
+
goto fail_errno;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
status = sym_revalidated;
|
|
966
|
+
}
|
|
967
|
+
break;
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
if (!valid_cache) {
|
|
971
|
+
status = sym_stale;
|
|
712
972
|
}
|
|
713
973
|
}
|
|
714
974
|
|
|
@@ -722,11 +982,11 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
722
982
|
else if (res == CACHE_UNCOMPILABLE) {
|
|
723
983
|
/* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
|
|
724
984
|
This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
|
|
725
|
-
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){
|
|
985
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
|
726
986
|
exception_message = path_v;
|
|
727
987
|
goto fail_errno;
|
|
728
988
|
}
|
|
729
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
989
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
730
990
|
if (exception_tag != 0) goto raise;
|
|
731
991
|
goto succeed;
|
|
732
992
|
} else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
|
@@ -741,7 +1001,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
741
1001
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
|
742
1002
|
|
|
743
1003
|
/* Read the contents of the source file into a buffer */
|
|
744
|
-
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){
|
|
1004
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
|
745
1005
|
exception_message = path_v;
|
|
746
1006
|
goto fail_errno;
|
|
747
1007
|
}
|
|
@@ -752,7 +1012,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
752
1012
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
|
753
1013
|
* to cache anything; just return input_to_output(input_data) */
|
|
754
1014
|
if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
|
755
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1015
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
756
1016
|
if (exception_tag != 0) goto raise;
|
|
757
1017
|
goto succeed;
|
|
758
1018
|
}
|
|
@@ -763,6 +1023,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
763
1023
|
* We do however ignore any failures to persist the cache, as it's better
|
|
764
1024
|
* to move along, than to interrupt the process.
|
|
765
1025
|
*/
|
|
1026
|
+
bs_cache_key_digest(¤t_key, input_data);
|
|
766
1027
|
atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
|
767
1028
|
|
|
768
1029
|
/* Having written the cache, now convert storage_data to output_data */
|
|
@@ -771,7 +1032,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
771
1032
|
|
|
772
1033
|
if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
|
773
1034
|
/* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
|
|
774
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1035
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
775
1036
|
if (exception_tag != 0) goto raise;
|
|
776
1037
|
} else if (NIL_P(output_data)) {
|
|
777
1038
|
/* If output_data is nil, delete the cache entry and generate the output
|
|
@@ -785,7 +1046,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
785
1046
|
goto fail_errno;
|
|
786
1047
|
}
|
|
787
1048
|
}
|
|
788
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1049
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
789
1050
|
if (exception_tag != 0) goto raise;
|
|
790
1051
|
}
|
|
791
1052
|
|
|
@@ -793,13 +1054,20 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
793
1054
|
|
|
794
1055
|
#define CLEANUP \
|
|
795
1056
|
if (current_fd >= 0) close(current_fd); \
|
|
796
|
-
if (cache_fd >= 0) close(cache_fd);
|
|
1057
|
+
if (cache_fd >= 0) close(cache_fd); \
|
|
1058
|
+
if (status != Qfalse) bs_instrumentation(status, path_v);
|
|
797
1059
|
|
|
798
1060
|
succeed:
|
|
799
1061
|
CLEANUP;
|
|
800
1062
|
return output_data;
|
|
801
1063
|
fail_errno:
|
|
802
1064
|
CLEANUP;
|
|
1065
|
+
if (errno_provenance) {
|
|
1066
|
+
exception_message = rb_str_concat(
|
|
1067
|
+
rb_str_new_cstr(errno_provenance),
|
|
1068
|
+
rb_str_concat(rb_str_new_cstr(": "), exception_message)
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
803
1071
|
exception = rb_syserr_new_str(errno, exception_message);
|
|
804
1072
|
rb_exc_raise(exception);
|
|
805
1073
|
__builtin_unreachable();
|
|
@@ -818,13 +1086,16 @@ invalid_type_storage_data:
|
|
|
818
1086
|
static VALUE
|
|
819
1087
|
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
820
1088
|
{
|
|
1089
|
+
if (readonly) {
|
|
1090
|
+
return Qfalse;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
821
1093
|
struct bs_cache_key cached_key, current_key;
|
|
822
|
-
char * contents = NULL;
|
|
823
1094
|
int cache_fd = -1, current_fd = -1;
|
|
824
1095
|
int res, valid_cache = 0, exception_tag = 0;
|
|
825
1096
|
const char * errno_provenance = NULL;
|
|
826
1097
|
|
|
827
|
-
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
|
1098
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
|
828
1099
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
|
829
1100
|
|
|
830
1101
|
/* Open the source file and generate a cache key for it */
|
|
@@ -840,7 +1111,26 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
840
1111
|
} else {
|
|
841
1112
|
/* True if the cache existed and no invalidating changes have occurred since
|
|
842
1113
|
* it was generated. */
|
|
843
|
-
|
|
1114
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
|
1115
|
+
case hit:
|
|
1116
|
+
valid_cache = true;
|
|
1117
|
+
break;
|
|
1118
|
+
case miss:
|
|
1119
|
+
valid_cache = false;
|
|
1120
|
+
break;
|
|
1121
|
+
case stale:
|
|
1122
|
+
valid_cache = false;
|
|
1123
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
|
1124
|
+
goto fail;
|
|
1125
|
+
}
|
|
1126
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
|
1127
|
+
if (valid_cache) {
|
|
1128
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
|
1129
|
+
goto fail;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
break;
|
|
1133
|
+
};
|
|
844
1134
|
}
|
|
845
1135
|
|
|
846
1136
|
if (valid_cache) {
|
|
@@ -852,7 +1142,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
852
1142
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
|
853
1143
|
|
|
854
1144
|
/* Read the contents of the source file into a buffer */
|
|
855
|
-
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
|
|
1145
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
|
|
856
1146
|
|
|
857
1147
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
|
858
1148
|
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
|
@@ -867,6 +1157,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
|
867
1157
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
|
868
1158
|
|
|
869
1159
|
/* Write the cache key and storage_data to the cache directory */
|
|
1160
|
+
bs_cache_key_digest(¤t_key, input_data);
|
|
870
1161
|
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
|
871
1162
|
if (res < 0) goto fail;
|
|
872
1163
|
|
|
@@ -913,6 +1204,7 @@ struct i2o_data {
|
|
|
913
1204
|
VALUE handler;
|
|
914
1205
|
VALUE args;
|
|
915
1206
|
VALUE input_data;
|
|
1207
|
+
VALUE pathval;
|
|
916
1208
|
};
|
|
917
1209
|
|
|
918
1210
|
struct i2s_data {
|
|
@@ -925,7 +1217,7 @@ static VALUE
|
|
|
925
1217
|
try_storage_to_output(VALUE arg)
|
|
926
1218
|
{
|
|
927
1219
|
struct s2o_data * data = (struct s2o_data *)arg;
|
|
928
|
-
return rb_funcall(data->handler,
|
|
1220
|
+
return rb_funcall(data->handler, id_storage_to_output, 2, data->storage_data, data->args);
|
|
929
1221
|
}
|
|
930
1222
|
|
|
931
1223
|
static int
|
|
@@ -942,12 +1234,13 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
|
|
|
942
1234
|
}
|
|
943
1235
|
|
|
944
1236
|
static void
|
|
945
|
-
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
|
1237
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE path_v, VALUE * output_data, int * exception_tag)
|
|
946
1238
|
{
|
|
947
1239
|
struct i2o_data i2o_data = {
|
|
948
1240
|
.handler = handler,
|
|
949
1241
|
.args = args,
|
|
950
1242
|
.input_data = input_data,
|
|
1243
|
+
.pathval = path_v,
|
|
951
1244
|
};
|
|
952
1245
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
|
953
1246
|
}
|
|
@@ -956,14 +1249,14 @@ static VALUE
|
|
|
956
1249
|
prot_input_to_output(VALUE arg)
|
|
957
1250
|
{
|
|
958
1251
|
struct i2o_data * data = (struct i2o_data *)arg;
|
|
959
|
-
return rb_funcall(data->handler,
|
|
1252
|
+
return rb_funcall(data->handler, id_input_to_output, 3, data->input_data, data->pathval, data->args);
|
|
960
1253
|
}
|
|
961
1254
|
|
|
962
1255
|
static VALUE
|
|
963
1256
|
try_input_to_storage(VALUE arg)
|
|
964
1257
|
{
|
|
965
1258
|
struct i2s_data * data = (struct i2s_data *)arg;
|
|
966
|
-
return rb_funcall(data->handler,
|
|
1259
|
+
return rb_funcall(data->handler, id_input_to_storage, 2, data->input_data, data->pathval);
|
|
967
1260
|
}
|
|
968
1261
|
|
|
969
1262
|
static int
|