bootsnap 1.4.5 → 1.18.3
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 +264 -0
- data/LICENSE.txt +1 -1
- data/README.md +63 -23
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +504 -184
- data/ext/bootsnap/extconf.rb +30 -15
- data/lib/bootsnap/bundler.rb +3 -1
- data/lib/bootsnap/cli/worker_pool.rb +136 -0
- data/lib/bootsnap/cli.rb +283 -0
- data/lib/bootsnap/compile_cache/iseq.rb +72 -21
- data/lib/bootsnap/compile_cache/json.rb +89 -0
- data/lib/bootsnap/compile_cache/yaml.rb +316 -41
- data/lib/bootsnap/compile_cache.rb +27 -17
- data/lib/bootsnap/explicit_require.rb +5 -3
- data/lib/bootsnap/load_path_cache/cache.rb +73 -37
- data/lib/bootsnap/load_path_cache/change_observer.rb +25 -3
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +27 -82
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +2 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +63 -29
- data/lib/bootsnap/load_path_cache/path.rb +42 -19
- data/lib/bootsnap/load_path_cache/path_scanner.rb +60 -29
- data/lib/bootsnap/load_path_cache/store.rb +64 -23
- data/lib/bootsnap/load_path_cache.rb +40 -38
- data/lib/bootsnap/setup.rb +3 -36
- data/lib/bootsnap/version.rb +3 -1
- data/lib/bootsnap.rb +141 -36
- metadata +15 -99
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/README.jp.md +0 -231
- data/Rakefile +0 -12
- data/bin/ci +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -45
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -106
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
- data/shipit.rubygems.yml +0 -0
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -14,12 +14,21 @@
|
|
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
|
+
#include <unistd.h>
|
20
22
|
#include <sys/stat.h>
|
21
|
-
|
22
|
-
#
|
23
|
+
|
24
|
+
#ifdef __APPLE__
|
25
|
+
// The symbol is present, however not in the headers
|
26
|
+
// See: https://github.com/Shopify/bootsnap/issues/470
|
27
|
+
extern int fdatasync(int);
|
28
|
+
#endif
|
29
|
+
|
30
|
+
#ifndef O_NOATIME
|
31
|
+
#define O_NOATIME 0
|
23
32
|
#endif
|
24
33
|
|
25
34
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
@@ -29,6 +38,12 @@
|
|
29
38
|
|
30
39
|
#define KEY_SIZE 64
|
31
40
|
|
41
|
+
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
42
|
+
|
43
|
+
#ifndef RB_UNLIKELY
|
44
|
+
#define RB_UNLIKELY(x) (x)
|
45
|
+
#endif
|
46
|
+
|
32
47
|
/*
|
33
48
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
34
49
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -50,8 +65,10 @@ struct bs_cache_key {
|
|
50
65
|
uint32_t ruby_revision;
|
51
66
|
uint64_t size;
|
52
67
|
uint64_t mtime;
|
53
|
-
uint64_t data_size;
|
54
|
-
|
68
|
+
uint64_t data_size; //
|
69
|
+
uint64_t digest;
|
70
|
+
uint8_t digest_set;
|
71
|
+
uint8_t pad[15];
|
55
72
|
} __attribute__((packed));
|
56
73
|
|
57
74
|
/*
|
@@ -65,7 +82,7 @@ struct bs_cache_key {
|
|
65
82
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
66
83
|
|
67
84
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
68
|
-
static const uint32_t current_version =
|
85
|
+
static const uint32_t current_version = 5;
|
69
86
|
|
70
87
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
71
88
|
* new OS ABI, etc. */
|
@@ -81,21 +98,39 @@ static mode_t current_umask;
|
|
81
98
|
static VALUE rb_mBootsnap;
|
82
99
|
static VALUE rb_mBootsnap_CompileCache;
|
83
100
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
84
|
-
static VALUE
|
85
|
-
static ID
|
101
|
+
static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
102
|
+
static ID instrumentation_method;
|
103
|
+
static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated;
|
104
|
+
static bool instrumentation_enabled = false;
|
105
|
+
static bool readonly = false;
|
106
|
+
static bool revalidation = false;
|
107
|
+
static bool perm_issue = false;
|
86
108
|
|
87
109
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
110
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
111
|
+
static VALUE bs_readonly_set(VALUE self, VALUE enabled);
|
112
|
+
static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
|
88
113
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
89
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
114
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
115
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
90
116
|
|
91
117
|
/* Helpers */
|
92
|
-
|
93
|
-
|
118
|
+
enum cache_status {
|
119
|
+
miss,
|
120
|
+
hit,
|
121
|
+
stale,
|
122
|
+
};
|
123
|
+
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
94
124
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
95
|
-
static
|
96
|
-
static
|
97
|
-
static int
|
98
|
-
|
125
|
+
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
126
|
+
static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
|
127
|
+
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance);
|
128
|
+
|
129
|
+
static void bs_cache_key_digest(struct bs_cache_key * key, const VALUE input_data);
|
130
|
+
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
131
|
+
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
132
|
+
static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
133
|
+
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
99
134
|
static uint32_t get_ruby_revision(void);
|
100
135
|
static uint32_t get_ruby_platform(void);
|
101
136
|
|
@@ -103,12 +138,10 @@ static uint32_t get_ruby_platform(void);
|
|
103
138
|
* Helper functions to call ruby methods on handler object without crashing on
|
104
139
|
* exception.
|
105
140
|
*/
|
106
|
-
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
107
|
-
static VALUE prot_storage_to_output(VALUE arg);
|
141
|
+
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
108
142
|
static VALUE prot_input_to_output(VALUE arg);
|
109
|
-
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
110
|
-
static
|
111
|
-
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
143
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
144
|
+
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
112
145
|
struct s2o_data;
|
113
146
|
struct i2o_data;
|
114
147
|
struct i2s_data;
|
@@ -122,6 +155,12 @@ bs_rb_coverage_running(VALUE self)
|
|
122
155
|
return RTEST(cov) ? Qtrue : Qfalse;
|
123
156
|
}
|
124
157
|
|
158
|
+
static VALUE
|
159
|
+
bs_rb_get_path(VALUE self, VALUE fname)
|
160
|
+
{
|
161
|
+
return rb_get_path(fname);
|
162
|
+
}
|
163
|
+
|
125
164
|
/*
|
126
165
|
* Ruby C extensions are initialized by calling Init_<extname>.
|
127
166
|
*
|
@@ -133,23 +172,65 @@ void
|
|
133
172
|
Init_bootsnap(void)
|
134
173
|
{
|
135
174
|
rb_mBootsnap = rb_define_module("Bootsnap");
|
175
|
+
|
176
|
+
rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
|
177
|
+
|
136
178
|
rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
|
137
179
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
138
|
-
|
180
|
+
rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
|
181
|
+
rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
|
139
182
|
|
140
183
|
current_ruby_revision = get_ruby_revision();
|
141
184
|
current_ruby_platform = get_ruby_platform();
|
142
185
|
|
143
|
-
|
186
|
+
instrumentation_method = rb_intern("_instrument");
|
144
187
|
|
188
|
+
sym_hit = ID2SYM(rb_intern("hit"));
|
189
|
+
sym_miss = ID2SYM(rb_intern("miss"));
|
190
|
+
sym_stale = ID2SYM(rb_intern("stale"));
|
191
|
+
sym_revalidated = ID2SYM(rb_intern("revalidated"));
|
192
|
+
|
193
|
+
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
194
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
195
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
|
145
196
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
146
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
197
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
198
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
147
199
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
148
200
|
|
149
201
|
current_umask = umask(0777);
|
150
202
|
umask(current_umask);
|
151
203
|
}
|
152
204
|
|
205
|
+
static VALUE
|
206
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
207
|
+
{
|
208
|
+
instrumentation_enabled = RTEST(enabled);
|
209
|
+
return enabled;
|
210
|
+
}
|
211
|
+
|
212
|
+
static inline void
|
213
|
+
bs_instrumentation(VALUE event, VALUE path)
|
214
|
+
{
|
215
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
216
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
220
|
+
static VALUE
|
221
|
+
bs_readonly_set(VALUE self, VALUE enabled)
|
222
|
+
{
|
223
|
+
readonly = RTEST(enabled);
|
224
|
+
return enabled;
|
225
|
+
}
|
226
|
+
|
227
|
+
static VALUE
|
228
|
+
bs_revalidation_set(VALUE self, VALUE enabled)
|
229
|
+
{
|
230
|
+
revalidation = RTEST(enabled);
|
231
|
+
return enabled;
|
232
|
+
}
|
233
|
+
|
153
234
|
/*
|
154
235
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
155
236
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -167,22 +248,13 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
167
248
|
return Qnil;
|
168
249
|
}
|
169
250
|
|
170
|
-
/*
|
171
|
-
* We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
|
172
|
-
* it has several nice properties:
|
173
|
-
*
|
174
|
-
* - Tiny implementation
|
175
|
-
* - No external dependency
|
176
|
-
* - Solid performance
|
177
|
-
* - Solid randomness
|
178
|
-
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
179
|
-
*/
|
180
251
|
static uint64_t
|
181
|
-
fnv1a_64_iter(uint64_t h, const
|
252
|
+
fnv1a_64_iter(uint64_t h, const VALUE str)
|
182
253
|
{
|
183
|
-
unsigned char *s = (unsigned char *)str;
|
254
|
+
unsigned char *s = (unsigned char *)RSTRING_PTR(str);
|
255
|
+
unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
|
184
256
|
|
185
|
-
while (
|
257
|
+
while (s < str_end) {
|
186
258
|
h ^= (uint64_t)*s++;
|
187
259
|
h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
|
188
260
|
}
|
@@ -191,7 +263,7 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
191
263
|
}
|
192
264
|
|
193
265
|
static uint64_t
|
194
|
-
fnv1a_64(const
|
266
|
+
fnv1a_64(const VALUE str)
|
195
267
|
{
|
196
268
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
197
269
|
return fnv1a_64_iter(h, str);
|
@@ -212,7 +284,7 @@ get_ruby_revision(void)
|
|
212
284
|
} else {
|
213
285
|
uint64_t hash;
|
214
286
|
|
215
|
-
hash = fnv1a_64(
|
287
|
+
hash = fnv1a_64(ruby_revision);
|
216
288
|
return (uint32_t)(hash >> 32);
|
217
289
|
}
|
218
290
|
}
|
@@ -220,10 +292,6 @@ get_ruby_revision(void)
|
|
220
292
|
/*
|
221
293
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
222
294
|
* (or OS version), we need to invalidate the cache.
|
223
|
-
*
|
224
|
-
* We actually factor in some extra information here, to be extra confident
|
225
|
-
* that we don't try to re-use caches that will not be compatible, by factoring
|
226
|
-
* in utsname.version.
|
227
295
|
*/
|
228
296
|
static uint32_t
|
229
297
|
get_ruby_platform(void)
|
@@ -232,20 +300,8 @@ get_ruby_platform(void)
|
|
232
300
|
VALUE ruby_platform;
|
233
301
|
|
234
302
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
235
|
-
hash = fnv1a_64(
|
236
|
-
|
237
|
-
#ifdef _WIN32
|
238
|
-
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
239
|
-
#else
|
240
|
-
struct utsname utsname;
|
241
|
-
|
242
|
-
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
243
|
-
if (uname(&utsname) >= 0) {
|
244
|
-
hash = fnv1a_64_iter(hash, utsname.version);
|
245
|
-
}
|
246
|
-
|
303
|
+
hash = fnv1a_64(ruby_platform);
|
247
304
|
return (uint32_t)(hash >> 32);
|
248
|
-
#endif
|
249
305
|
}
|
250
306
|
|
251
307
|
/*
|
@@ -256,14 +312,13 @@ get_ruby_platform(void)
|
|
256
312
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
257
313
|
*/
|
258
314
|
static void
|
259
|
-
bs_cache_path(const char * cachedir, const
|
315
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
260
316
|
{
|
261
317
|
uint64_t hash = fnv1a_64(path);
|
262
|
-
|
263
318
|
uint8_t first_byte = (hash >> (64 - 8));
|
264
319
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
265
320
|
|
266
|
-
sprintf(*cache_path, "%s/%
|
321
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
267
322
|
}
|
268
323
|
|
269
324
|
/*
|
@@ -274,17 +329,59 @@ bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
|
|
274
329
|
* The data_size member is not compared, as it serves more of a "header"
|
275
330
|
* function.
|
276
331
|
*/
|
277
|
-
static
|
278
|
-
|
332
|
+
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1,
|
333
|
+
struct bs_cache_key *k2) {
|
334
|
+
if (k1->version == k2->version &&
|
335
|
+
k1->ruby_platform == k2->ruby_platform &&
|
336
|
+
k1->compile_option == k2->compile_option &&
|
337
|
+
k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) {
|
338
|
+
if (k1->mtime == k2->mtime) {
|
339
|
+
return hit;
|
340
|
+
}
|
341
|
+
if (revalidation) {
|
342
|
+
return stale;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
return miss;
|
346
|
+
}
|
347
|
+
|
348
|
+
static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
|
349
|
+
struct bs_cache_key *cached_key,
|
350
|
+
const VALUE input_data)
|
279
351
|
{
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
);
|
352
|
+
bs_cache_key_digest(current_key, input_data);
|
353
|
+
return current_key->digest == cached_key->digest;
|
354
|
+
}
|
355
|
+
|
356
|
+
static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
|
357
|
+
{
|
358
|
+
old_key->mtime = current_key->mtime;
|
359
|
+
lseek(cache_fd, 0, SEEK_SET);
|
360
|
+
ssize_t nwrite = write(cache_fd, old_key, KEY_SIZE);
|
361
|
+
if (nwrite < 0) {
|
362
|
+
*errno_provenance = "update_cache_key:write";
|
363
|
+
return -1;
|
364
|
+
}
|
365
|
+
|
366
|
+
#ifdef HAVE_FDATASYNC
|
367
|
+
if (fdatasync(cache_fd) < 0) {
|
368
|
+
*errno_provenance = "update_cache_key:fdatasync";
|
369
|
+
return -1;
|
370
|
+
}
|
371
|
+
#endif
|
372
|
+
|
373
|
+
return 0;
|
374
|
+
}
|
375
|
+
|
376
|
+
/*
|
377
|
+
* Fills the cache key digest.
|
378
|
+
*/
|
379
|
+
static void bs_cache_key_digest(struct bs_cache_key *key,
|
380
|
+
const VALUE input_data) {
|
381
|
+
if (key->digest_set)
|
382
|
+
return;
|
383
|
+
key->digest = fnv1a_64(input_data);
|
384
|
+
key->digest_set = 1;
|
288
385
|
}
|
289
386
|
|
290
387
|
/*
|
@@ -293,7 +390,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
293
390
|
* conversions on the ruby VALUE arguments before passing them along.
|
294
391
|
*/
|
295
392
|
static VALUE
|
296
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
393
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
297
394
|
{
|
298
395
|
FilePathValue(path_v);
|
299
396
|
|
@@ -308,12 +405,53 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
308
405
|
char * path = RSTRING_PTR(path_v);
|
309
406
|
char cache_path[MAX_CACHEPATH_SIZE];
|
310
407
|
|
311
|
-
|
312
|
-
|
313
|
-
|
408
|
+
/* generate cache path to cache_path */
|
409
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
410
|
+
|
411
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
412
|
+
}
|
413
|
+
|
414
|
+
/*
|
415
|
+
* Entrypoint for Bootsnap::CompileCache::Native.precompile.
|
416
|
+
* Similar to fetch, but it only generate the cache if missing
|
417
|
+
* and doesn't return the content.
|
418
|
+
*/
|
419
|
+
static VALUE
|
420
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
421
|
+
{
|
422
|
+
FilePathValue(path_v);
|
423
|
+
|
424
|
+
Check_Type(cachedir_v, T_STRING);
|
425
|
+
Check_Type(path_v, T_STRING);
|
426
|
+
|
427
|
+
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
428
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
429
|
+
}
|
430
|
+
|
431
|
+
char * cachedir = RSTRING_PTR(cachedir_v);
|
432
|
+
char * path = RSTRING_PTR(path_v);
|
433
|
+
char cache_path[MAX_CACHEPATH_SIZE];
|
434
|
+
|
435
|
+
/* generate cache path to cache_path */
|
436
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
437
|
+
|
438
|
+
return bs_precompile(path, path_v, cache_path, handler);
|
439
|
+
}
|
440
|
+
|
441
|
+
static int bs_open_noatime(const char *path, int flags) {
|
442
|
+
int fd = 1;
|
443
|
+
if (!perm_issue) {
|
444
|
+
fd = open(path, flags | O_NOATIME);
|
445
|
+
if (fd < 0 && errno == EPERM) {
|
446
|
+
errno = 0;
|
447
|
+
perm_issue = true;
|
448
|
+
}
|
314
449
|
}
|
315
450
|
|
316
|
-
|
451
|
+
if (perm_issue) {
|
452
|
+
fd = open(path, flags);
|
453
|
+
}
|
454
|
+
return fd;
|
317
455
|
}
|
318
456
|
|
319
457
|
/*
|
@@ -321,14 +459,14 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
321
459
|
* was loaded.
|
322
460
|
*/
|
323
461
|
static int
|
324
|
-
open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
|
462
|
+
open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
325
463
|
{
|
326
464
|
struct stat statbuf;
|
327
465
|
int fd;
|
328
466
|
|
329
|
-
fd =
|
467
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
330
468
|
if (fd < 0) {
|
331
|
-
*errno_provenance =
|
469
|
+
*errno_provenance = "bs_fetch:open_current_file:open";
|
332
470
|
return fd;
|
333
471
|
}
|
334
472
|
#ifdef _WIN32
|
@@ -336,8 +474,10 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
336
474
|
#endif
|
337
475
|
|
338
476
|
if (fstat(fd, &statbuf) < 0) {
|
339
|
-
*errno_provenance =
|
477
|
+
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
478
|
+
int previous_errno = errno;
|
340
479
|
close(fd);
|
480
|
+
errno = previous_errno;
|
341
481
|
return -1;
|
342
482
|
}
|
343
483
|
|
@@ -347,12 +487,15 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
347
487
|
key->ruby_revision = current_ruby_revision;
|
348
488
|
key->size = (uint64_t)statbuf.st_size;
|
349
489
|
key->mtime = (uint64_t)statbuf.st_mtime;
|
490
|
+
key->digest_set = false;
|
350
491
|
|
351
492
|
return fd;
|
352
493
|
}
|
353
494
|
|
354
495
|
#define ERROR_WITH_ERRNO -1
|
355
|
-
#define
|
496
|
+
#define CACHE_MISS -2
|
497
|
+
#define CACHE_STALE -3
|
498
|
+
#define CACHE_UNCOMPILABLE -4
|
356
499
|
|
357
500
|
/*
|
358
501
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -360,15 +503,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
360
503
|
*
|
361
504
|
* Possible return values:
|
362
505
|
* - 0 (OK, key was loaded)
|
363
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
364
506
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
507
|
+
* - CACHE_MISS (-2)
|
508
|
+
* - CACHE_STALE (-3)
|
365
509
|
*/
|
366
510
|
static int
|
367
511
|
bs_read_key(int fd, struct bs_cache_key * key)
|
368
512
|
{
|
369
513
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
370
514
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
371
|
-
if (nread < KEY_SIZE) return
|
515
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
372
516
|
return 0;
|
373
517
|
}
|
374
518
|
|
@@ -378,19 +522,24 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
378
522
|
*
|
379
523
|
* Possible return values:
|
380
524
|
* - 0 (OK, key was loaded)
|
381
|
-
* -
|
525
|
+
* - CACHE_MISS (-2)
|
526
|
+
* - CACHE_STALE (-3)
|
382
527
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
383
528
|
*/
|
384
529
|
static int
|
385
|
-
open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
|
530
|
+
open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
386
531
|
{
|
387
532
|
int fd, res;
|
388
533
|
|
389
|
-
|
534
|
+
if (readonly || !revalidation) {
|
535
|
+
fd = bs_open_noatime(path, O_RDONLY);
|
536
|
+
} else {
|
537
|
+
fd = bs_open_noatime(path, O_RDWR);
|
538
|
+
}
|
539
|
+
|
390
540
|
if (fd < 0) {
|
391
|
-
*errno_provenance =
|
392
|
-
|
393
|
-
return ERROR_WITH_ERRNO;
|
541
|
+
*errno_provenance = "bs_fetch:open_cache_file:open";
|
542
|
+
return CACHE_MISS;
|
394
543
|
}
|
395
544
|
#ifdef _WIN32
|
396
545
|
setmode(fd, O_BINARY);
|
@@ -398,7 +547,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
398
547
|
|
399
548
|
res = bs_read_key(fd, key);
|
400
549
|
if (res < 0) {
|
401
|
-
*errno_provenance =
|
550
|
+
*errno_provenance = "bs_fetch:open_cache_file:read";
|
402
551
|
close(fd);
|
403
552
|
return res;
|
404
553
|
}
|
@@ -422,38 +571,40 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
422
571
|
* or exception, will be the final data returnable to the user.
|
423
572
|
*/
|
424
573
|
static int
|
425
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
|
574
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
426
575
|
{
|
427
|
-
char * data = NULL;
|
428
576
|
ssize_t nread;
|
429
577
|
int ret;
|
430
578
|
|
431
579
|
VALUE storage_data;
|
432
580
|
|
433
581
|
if (data_size > 100000000000) {
|
434
|
-
*errno_provenance =
|
582
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
435
583
|
errno = EINVAL; /* because wtf? */
|
436
|
-
ret =
|
584
|
+
ret = ERROR_WITH_ERRNO;
|
437
585
|
goto done;
|
438
586
|
}
|
439
|
-
|
440
|
-
nread = read(fd,
|
587
|
+
storage_data = rb_str_buf_new(data_size);
|
588
|
+
nread = read(fd, RSTRING_PTR(storage_data), data_size);
|
441
589
|
if (nread < 0) {
|
442
|
-
*errno_provenance =
|
443
|
-
ret =
|
590
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
591
|
+
ret = ERROR_WITH_ERRNO;
|
444
592
|
goto done;
|
445
593
|
}
|
446
594
|
if (nread != data_size) {
|
447
|
-
ret =
|
595
|
+
ret = CACHE_STALE;
|
448
596
|
goto done;
|
449
597
|
}
|
450
598
|
|
451
|
-
storage_data
|
599
|
+
rb_str_set_len(storage_data, nread);
|
452
600
|
|
453
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
601
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
602
|
+
if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
603
|
+
ret = CACHE_UNCOMPILABLE;
|
604
|
+
goto done;
|
605
|
+
}
|
454
606
|
ret = 0;
|
455
607
|
done:
|
456
|
-
if (data != NULL) xfree(data);
|
457
608
|
return ret;
|
458
609
|
}
|
459
610
|
|
@@ -491,29 +642,36 @@ mkpath(char * file_path, mode_t mode)
|
|
491
642
|
* path.
|
492
643
|
*/
|
493
644
|
static int
|
494
|
-
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
645
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
|
495
646
|
{
|
496
647
|
char template[MAX_CACHEPATH_SIZE + 20];
|
497
648
|
char * tmp_path;
|
498
|
-
int fd, ret;
|
649
|
+
int fd, ret, attempt;
|
499
650
|
ssize_t nwrite;
|
500
651
|
|
501
|
-
|
502
|
-
|
652
|
+
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
653
|
+
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
654
|
+
strcat(tmp_path, ".tmp.XXXXXX");
|
503
655
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
}
|
511
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
512
|
-
if (fd < 0) {
|
513
|
-
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
|
656
|
+
// mkstemp modifies the template to be the actual created path
|
657
|
+
fd = mkstemp(tmp_path);
|
658
|
+
if (fd > 0) break;
|
659
|
+
|
660
|
+
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
661
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
514
662
|
return -1;
|
515
663
|
}
|
516
664
|
}
|
665
|
+
if (fd < 0) {
|
666
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
|
667
|
+
return -1;
|
668
|
+
}
|
669
|
+
|
670
|
+
if (chmod(tmp_path, 0644) < 0) {
|
671
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
672
|
+
return -1;
|
673
|
+
}
|
674
|
+
|
517
675
|
#ifdef _WIN32
|
518
676
|
setmode(fd, O_BINARY);
|
519
677
|
#endif
|
@@ -521,11 +679,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
521
679
|
key->data_size = RSTRING_LEN(data);
|
522
680
|
nwrite = write(fd, key, KEY_SIZE);
|
523
681
|
if (nwrite < 0) {
|
524
|
-
*errno_provenance =
|
682
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
525
683
|
return -1;
|
526
684
|
}
|
527
685
|
if (nwrite != KEY_SIZE) {
|
528
|
-
*errno_provenance =
|
686
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
529
687
|
errno = EIO; /* Lies but whatever */
|
530
688
|
return -1;
|
531
689
|
}
|
@@ -533,7 +691,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
533
691
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
534
692
|
if (nwrite < 0) return -1;
|
535
693
|
if (nwrite != RSTRING_LEN(data)) {
|
536
|
-
*errno_provenance =
|
694
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
537
695
|
errno = EIO; /* Lies but whatever */
|
538
696
|
return -1;
|
539
697
|
}
|
@@ -541,29 +699,34 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
541
699
|
close(fd);
|
542
700
|
ret = rename(tmp_path, path);
|
543
701
|
if (ret < 0) {
|
544
|
-
*errno_provenance =
|
702
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
545
703
|
return -1;
|
546
704
|
}
|
547
705
|
ret = chmod(path, 0664 & ~current_umask);
|
548
706
|
if (ret < 0) {
|
549
|
-
*errno_provenance =
|
707
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
550
708
|
}
|
551
709
|
return ret;
|
552
710
|
}
|
553
711
|
|
554
712
|
|
555
713
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
556
|
-
* long,
|
557
|
-
static
|
558
|
-
bs_read_contents(int fd, size_t size,
|
714
|
+
* long, returning a Ruby string on success and Qfalse on failure */
|
715
|
+
static VALUE
|
716
|
+
bs_read_contents(int fd, size_t size, const char ** errno_provenance)
|
559
717
|
{
|
718
|
+
VALUE contents;
|
560
719
|
ssize_t nread;
|
561
|
-
|
562
|
-
nread = read(fd,
|
720
|
+
contents = rb_str_buf_new(size);
|
721
|
+
nread = read(fd, RSTRING_PTR(contents), size);
|
722
|
+
|
563
723
|
if (nread < 0) {
|
564
|
-
*errno_provenance =
|
724
|
+
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
725
|
+
return Qfalse;
|
726
|
+
} else {
|
727
|
+
rb_str_set_len(contents, nread);
|
728
|
+
return contents;
|
565
729
|
}
|
566
|
-
return nread;
|
567
730
|
}
|
568
731
|
|
569
732
|
/*
|
@@ -611,91 +774,155 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
|
|
611
774
|
* - Return storage_to_output(storage_data)
|
612
775
|
*/
|
613
776
|
static VALUE
|
614
|
-
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
777
|
+
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
615
778
|
{
|
616
779
|
struct bs_cache_key cached_key, current_key;
|
617
|
-
char * contents = NULL;
|
618
780
|
int cache_fd = -1, current_fd = -1;
|
619
781
|
int res, valid_cache = 0, exception_tag = 0;
|
620
|
-
char * errno_provenance = NULL;
|
782
|
+
const char * errno_provenance = NULL;
|
621
783
|
|
622
|
-
VALUE
|
784
|
+
VALUE status = Qfalse;
|
785
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
623
786
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
624
787
|
VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
|
625
788
|
|
626
789
|
VALUE exception; /* ruby exception object to raise instead of returning */
|
790
|
+
VALUE exception_message; /* ruby exception string to use instead of errno_provenance */
|
627
791
|
|
628
792
|
/* Open the source file and generate a cache key for it */
|
629
793
|
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
630
|
-
if (current_fd < 0)
|
794
|
+
if (current_fd < 0) {
|
795
|
+
exception_message = path_v;
|
796
|
+
goto fail_errno;
|
797
|
+
}
|
631
798
|
|
632
799
|
/* Open the cache key if it exists, and read its cache key in */
|
633
800
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
634
|
-
if (cache_fd ==
|
801
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
635
802
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
803
|
+
bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
636
804
|
} else if (cache_fd < 0) {
|
805
|
+
exception_message = rb_str_new_cstr(cache_path);
|
637
806
|
goto fail_errno;
|
638
807
|
} else {
|
639
808
|
/* True if the cache existed and no invalidating changes have occurred since
|
640
809
|
* it was generated. */
|
641
|
-
|
810
|
+
|
811
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
812
|
+
case hit:
|
813
|
+
status = sym_hit;
|
814
|
+
valid_cache = true;
|
815
|
+
break;
|
816
|
+
case miss:
|
817
|
+
valid_cache = false;
|
818
|
+
break;
|
819
|
+
case stale:
|
820
|
+
valid_cache = false;
|
821
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size,
|
822
|
+
&errno_provenance)) == Qfalse) {
|
823
|
+
exception_message = path_v;
|
824
|
+
goto fail_errno;
|
825
|
+
}
|
826
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
827
|
+
if (valid_cache) {
|
828
|
+
if (!readonly) {
|
829
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
830
|
+
exception_message = path_v;
|
831
|
+
goto fail_errno;
|
832
|
+
}
|
833
|
+
}
|
834
|
+
status = sym_revalidated;
|
835
|
+
}
|
836
|
+
break;
|
837
|
+
};
|
838
|
+
|
839
|
+
if (!valid_cache) {
|
840
|
+
status = sym_stale;
|
841
|
+
}
|
642
842
|
}
|
643
843
|
|
644
844
|
if (valid_cache) {
|
645
845
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
646
846
|
res = fetch_cached_data(
|
647
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
847
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
648
848
|
&output_data, &exception_tag, &errno_provenance
|
649
849
|
);
|
650
|
-
if (exception_tag != 0)
|
651
|
-
else if (res ==
|
652
|
-
|
653
|
-
|
850
|
+
if (exception_tag != 0) goto raise;
|
851
|
+
else if (res == CACHE_UNCOMPILABLE) {
|
852
|
+
/* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
|
853
|
+
This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
|
854
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
855
|
+
exception_message = path_v;
|
856
|
+
goto fail_errno;
|
857
|
+
}
|
858
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
859
|
+
if (exception_tag != 0) goto raise;
|
860
|
+
goto succeed;
|
861
|
+
} else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
862
|
+
else if (res == ERROR_WITH_ERRNO){
|
863
|
+
exception_message = rb_str_new_cstr(cache_path);
|
864
|
+
goto fail_errno;
|
865
|
+
}
|
866
|
+
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
654
867
|
}
|
655
868
|
close(cache_fd);
|
656
869
|
cache_fd = -1;
|
657
870
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
658
871
|
|
659
872
|
/* Read the contents of the source file into a buffer */
|
660
|
-
if (bs_read_contents(current_fd, current_key.size, &
|
661
|
-
|
873
|
+
if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
874
|
+
exception_message = path_v;
|
875
|
+
goto fail_errno;
|
876
|
+
}
|
662
877
|
|
663
878
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
664
|
-
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
879
|
+
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
665
880
|
if (exception_tag != 0) goto raise;
|
666
881
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
667
882
|
* to cache anything; just return input_to_output(input_data) */
|
668
|
-
if (storage_data ==
|
669
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
883
|
+
if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
884
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
670
885
|
if (exception_tag != 0) goto raise;
|
671
886
|
goto succeed;
|
672
887
|
}
|
673
888
|
/* If storage_data isn't a string, we can't cache it */
|
674
889
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
675
890
|
|
676
|
-
/*
|
677
|
-
|
678
|
-
|
891
|
+
/* Attempt to write the cache key and storage_data to the cache directory.
|
892
|
+
* We do however ignore any failures to persist the cache, as it's better
|
893
|
+
* to move along, than to interrupt the process.
|
894
|
+
*/
|
895
|
+
bs_cache_key_digest(¤t_key, input_data);
|
896
|
+
atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
679
897
|
|
680
898
|
/* Having written the cache, now convert storage_data to output_data */
|
681
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
899
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
682
900
|
if (exception_tag != 0) goto raise;
|
683
901
|
|
684
|
-
|
685
|
-
|
686
|
-
|
902
|
+
if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
903
|
+
/* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
|
904
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
905
|
+
if (exception_tag != 0) goto raise;
|
906
|
+
} else if (NIL_P(output_data)) {
|
907
|
+
/* If output_data is nil, delete the cache entry and generate the output
|
908
|
+
* using input_to_output */
|
687
909
|
if (unlink(cache_path) < 0) {
|
688
|
-
|
689
|
-
|
910
|
+
/* If the cache was already deleted, it might be that another process did it before us.
|
911
|
+
* No point raising an error */
|
912
|
+
if (errno != ENOENT) {
|
913
|
+
errno_provenance = "bs_fetch:unlink";
|
914
|
+
exception_message = rb_str_new_cstr(cache_path);
|
915
|
+
goto fail_errno;
|
916
|
+
}
|
690
917
|
}
|
691
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
918
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
692
919
|
if (exception_tag != 0) goto raise;
|
693
920
|
}
|
694
921
|
|
695
922
|
goto succeed; /* output_data is now the correct return. */
|
696
923
|
|
697
924
|
#define CLEANUP \
|
698
|
-
if (
|
925
|
+
if (status != Qfalse) bs_instrumentation(status, path_v); \
|
699
926
|
if (current_fd >= 0) close(current_fd); \
|
700
927
|
if (cache_fd >= 0) close(cache_fd);
|
701
928
|
|
@@ -704,7 +931,13 @@ succeed:
|
|
704
931
|
return output_data;
|
705
932
|
fail_errno:
|
706
933
|
CLEANUP;
|
707
|
-
|
934
|
+
if (errno_provenance) {
|
935
|
+
exception_message = rb_str_concat(
|
936
|
+
rb_str_new_cstr(errno_provenance),
|
937
|
+
rb_str_concat(rb_str_new_cstr(": "), exception_message)
|
938
|
+
);
|
939
|
+
}
|
940
|
+
exception = rb_syserr_new_str(errno, exception_message);
|
708
941
|
rb_exc_raise(exception);
|
709
942
|
__builtin_unreachable();
|
710
943
|
raise:
|
@@ -719,6 +952,100 @@ invalid_type_storage_data:
|
|
719
952
|
#undef CLEANUP
|
720
953
|
}
|
721
954
|
|
955
|
+
static VALUE
|
956
|
+
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
957
|
+
{
|
958
|
+
if (readonly) {
|
959
|
+
return Qfalse;
|
960
|
+
}
|
961
|
+
|
962
|
+
struct bs_cache_key cached_key, current_key;
|
963
|
+
int cache_fd = -1, current_fd = -1;
|
964
|
+
int res, valid_cache = 0, exception_tag = 0;
|
965
|
+
const char * errno_provenance = NULL;
|
966
|
+
|
967
|
+
VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */
|
968
|
+
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
969
|
+
|
970
|
+
/* Open the source file and generate a cache key for it */
|
971
|
+
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
972
|
+
if (current_fd < 0) goto fail;
|
973
|
+
|
974
|
+
/* Open the cache key if it exists, and read its cache key in */
|
975
|
+
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
976
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
977
|
+
/* This is ok: valid_cache remains false, we re-populate it. */
|
978
|
+
} else if (cache_fd < 0) {
|
979
|
+
goto fail;
|
980
|
+
} else {
|
981
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
982
|
+
* it was generated. */
|
983
|
+
switch(cache_key_equal_fast_path(¤t_key, &cached_key)) {
|
984
|
+
case hit:
|
985
|
+
valid_cache = true;
|
986
|
+
break;
|
987
|
+
case miss:
|
988
|
+
valid_cache = false;
|
989
|
+
break;
|
990
|
+
case stale:
|
991
|
+
valid_cache = false;
|
992
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) {
|
993
|
+
goto fail;
|
994
|
+
}
|
995
|
+
valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data);
|
996
|
+
if (valid_cache) {
|
997
|
+
if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) {
|
998
|
+
goto fail;
|
999
|
+
}
|
1000
|
+
}
|
1001
|
+
break;
|
1002
|
+
};
|
1003
|
+
}
|
1004
|
+
|
1005
|
+
if (valid_cache) {
|
1006
|
+
goto succeed;
|
1007
|
+
}
|
1008
|
+
|
1009
|
+
close(cache_fd);
|
1010
|
+
cache_fd = -1;
|
1011
|
+
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
1012
|
+
|
1013
|
+
/* Read the contents of the source file into a buffer */
|
1014
|
+
if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
|
1015
|
+
|
1016
|
+
/* Try to compile the input_data using input_to_storage(input_data) */
|
1017
|
+
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
1018
|
+
if (exception_tag != 0) goto fail;
|
1019
|
+
|
1020
|
+
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
1021
|
+
* to cache anything; just return false */
|
1022
|
+
if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
1023
|
+
goto fail;
|
1024
|
+
}
|
1025
|
+
/* If storage_data isn't a string, we can't cache it */
|
1026
|
+
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
1027
|
+
|
1028
|
+
/* Write the cache key and storage_data to the cache directory */
|
1029
|
+
bs_cache_key_digest(¤t_key, input_data);
|
1030
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
1031
|
+
if (res < 0) goto fail;
|
1032
|
+
|
1033
|
+
goto succeed;
|
1034
|
+
|
1035
|
+
#define CLEANUP \
|
1036
|
+
if (current_fd >= 0) close(current_fd); \
|
1037
|
+
if (cache_fd >= 0) close(cache_fd);
|
1038
|
+
|
1039
|
+
succeed:
|
1040
|
+
CLEANUP;
|
1041
|
+
return Qtrue;
|
1042
|
+
fail:
|
1043
|
+
CLEANUP;
|
1044
|
+
return Qfalse;
|
1045
|
+
#undef CLEANUP
|
1046
|
+
}
|
1047
|
+
|
1048
|
+
|
722
1049
|
/*****************************************************************************/
|
723
1050
|
/********************* Handler Wrappers **************************************/
|
724
1051
|
/*****************************************************************************
|
@@ -738,11 +1065,13 @@ invalid_type_storage_data:
|
|
738
1065
|
|
739
1066
|
struct s2o_data {
|
740
1067
|
VALUE handler;
|
1068
|
+
VALUE args;
|
741
1069
|
VALUE storage_data;
|
742
1070
|
};
|
743
1071
|
|
744
1072
|
struct i2o_data {
|
745
1073
|
VALUE handler;
|
1074
|
+
VALUE args;
|
746
1075
|
VALUE input_data;
|
747
1076
|
};
|
748
1077
|
|
@@ -753,29 +1082,31 @@ struct i2s_data {
|
|
753
1082
|
};
|
754
1083
|
|
755
1084
|
static VALUE
|
756
|
-
|
1085
|
+
try_storage_to_output(VALUE arg)
|
757
1086
|
{
|
758
1087
|
struct s2o_data * data = (struct s2o_data *)arg;
|
759
|
-
return rb_funcall(data->handler, rb_intern("storage_to_output"),
|
1088
|
+
return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
|
760
1089
|
}
|
761
1090
|
|
762
1091
|
static int
|
763
|
-
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
1092
|
+
bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
|
764
1093
|
{
|
765
1094
|
int state;
|
766
1095
|
struct s2o_data s2o_data = {
|
767
1096
|
.handler = handler,
|
1097
|
+
.args = args,
|
768
1098
|
.storage_data = storage_data,
|
769
1099
|
};
|
770
|
-
*output_data = rb_protect(
|
1100
|
+
*output_data = rb_protect(try_storage_to_output, (VALUE)&s2o_data, &state);
|
771
1101
|
return state;
|
772
1102
|
}
|
773
1103
|
|
774
1104
|
static void
|
775
|
-
bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
|
1105
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
776
1106
|
{
|
777
1107
|
struct i2o_data i2o_data = {
|
778
1108
|
.handler = handler,
|
1109
|
+
.args = args,
|
779
1110
|
.input_data = input_data,
|
780
1111
|
};
|
781
1112
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -785,7 +1116,7 @@ static VALUE
|
|
785
1116
|
prot_input_to_output(VALUE arg)
|
786
1117
|
{
|
787
1118
|
struct i2o_data * data = (struct i2o_data *)arg;
|
788
|
-
return rb_funcall(data->handler, rb_intern("input_to_output"),
|
1119
|
+
return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
|
789
1120
|
}
|
790
1121
|
|
791
1122
|
static VALUE
|
@@ -795,31 +1126,20 @@ try_input_to_storage(VALUE arg)
|
|
795
1126
|
return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
|
796
1127
|
}
|
797
1128
|
|
798
|
-
static VALUE
|
799
|
-
rescue_input_to_storage(VALUE arg)
|
800
|
-
{
|
801
|
-
return uncompilable;
|
802
|
-
}
|
803
|
-
|
804
|
-
static VALUE
|
805
|
-
prot_input_to_storage(VALUE arg)
|
806
|
-
{
|
807
|
-
struct i2s_data * data = (struct i2s_data *)arg;
|
808
|
-
return rb_rescue2(
|
809
|
-
try_input_to_storage, (VALUE)data,
|
810
|
-
rescue_input_to_storage, Qnil,
|
811
|
-
rb_eBootsnap_CompileCache_Uncompilable, 0);
|
812
|
-
}
|
813
|
-
|
814
1129
|
static int
|
815
|
-
bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
1130
|
+
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
816
1131
|
{
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
1132
|
+
if (readonly) {
|
1133
|
+
*storage_data = rb_cBootsnap_CompileCache_UNCOMPILABLE;
|
1134
|
+
return 0;
|
1135
|
+
} else {
|
1136
|
+
int state;
|
1137
|
+
struct i2s_data i2s_data = {
|
1138
|
+
.handler = handler,
|
1139
|
+
.input_data = input_data,
|
1140
|
+
.pathval = pathval,
|
1141
|
+
};
|
1142
|
+
*storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
|
1143
|
+
return state;
|
1144
|
+
}
|
825
1145
|
}
|