bootsnap 1.1.1 → 1.3.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 +5 -5
- data/.rubocop.yml +13 -0
- data/.travis.yml +11 -3
- data/CHANGELOG.md +43 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.jp.md +229 -0
- data/README.md +21 -25
- data/Rakefile +1 -0
- data/bin/testunit +3 -3
- data/bootsnap.gemspec +2 -2
- data/dev.yml +3 -1
- data/ext/bootsnap/bootsnap.c +124 -78
- data/lib/bootsnap.rb +1 -0
- data/lib/bootsnap/bundler.rb +12 -0
- data/lib/bootsnap/compile_cache/iseq.rb +0 -1
- data/lib/bootsnap/compile_cache/yaml.rb +1 -0
- data/lib/bootsnap/explicit_require.rb +6 -1
- data/lib/bootsnap/load_path_cache.rb +7 -1
- data/lib/bootsnap/load_path_cache/cache.rb +13 -15
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +15 -13
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +77 -33
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +95 -0
- data/lib/bootsnap/load_path_cache/path.rb +1 -1
- data/lib/bootsnap/load_path_cache/path_scanner.rb +16 -6
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
- data/lib/bootsnap/load_path_cache/store.rb +9 -2
- data/lib/bootsnap/setup.rb +4 -10
- data/lib/bootsnap/version.rb +1 -1
- data/shipit.rubygems.yml +4 -0
- metadata +12 -6
- data/LICENSE +0 -20
data/Rakefile
CHANGED
data/bin/testunit
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/bin/bash
|
2
2
|
|
3
|
-
if [ $# -eq 0 ]; then
|
4
|
-
ruby -I"test" -e 'Dir.glob("./test/**/*_test.rb").each { |f| require f }' -- "$@"
|
3
|
+
if [[ $# -eq 0 ]]; then
|
4
|
+
exec ruby -I"test" -w -e 'Dir.glob("./test/**/*_test.rb").each { |f| require f }' -- "$@"
|
5
5
|
else
|
6
6
|
path=$1
|
7
|
-
ruby -I"test" -e "require '${path#test/}'" -- "$@"
|
7
|
+
exec ruby -I"test" -w -e "require '${path#test/}'" -- "$@"
|
8
8
|
fi
|
data/bootsnap.gemspec
CHANGED
@@ -11,8 +11,8 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.license = "MIT"
|
13
13
|
|
14
|
-
spec.summary = "
|
15
|
-
spec.description =
|
14
|
+
spec.summary = "Boot large ruby/rails apps faster"
|
15
|
+
spec.description = spec.summary
|
16
16
|
spec.homepage = "https://github.com/Shopify/bootsnap"
|
17
17
|
|
18
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
data/dev.yml
CHANGED
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -32,7 +32,7 @@
|
|
32
32
|
/*
|
33
33
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
34
34
|
* The mtime and size members track whether the file contents have changed, and
|
35
|
-
* the version,
|
35
|
+
* the version, ruby_platform, compile_option, and ruby_revision members track
|
36
36
|
* changes to the environment that could invalidate compile results without
|
37
37
|
* file contents having changed. The data_size member is not truly part of the
|
38
38
|
* "key". Really, this could be called a "header" with the first six members
|
@@ -45,7 +45,7 @@
|
|
45
45
|
*/
|
46
46
|
struct bs_cache_key {
|
47
47
|
uint32_t version;
|
48
|
-
uint32_t
|
48
|
+
uint32_t ruby_platform;
|
49
49
|
uint32_t compile_option;
|
50
50
|
uint32_t ruby_revision;
|
51
51
|
uint64_t size;
|
@@ -67,9 +67,9 @@ STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
|
67
67
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
68
68
|
static const uint32_t current_version = 2;
|
69
69
|
|
70
|
-
/*
|
71
|
-
*
|
72
|
-
static uint32_t
|
70
|
+
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
71
|
+
* new OS ABI, etc. */
|
72
|
+
static uint32_t current_ruby_platform;
|
73
73
|
/* Invalidates cache when switching ruby versions */
|
74
74
|
static uint32_t current_ruby_revision;
|
75
75
|
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
@@ -92,10 +92,9 @@ static void bs_cache_path(const char * cachedir, const char * path, char ** cach
|
|
92
92
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
93
93
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
94
94
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
95
|
-
static int open_current_file(char * path, struct bs_cache_key * key);
|
96
|
-
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag);
|
97
|
-
static
|
98
|
-
static uint32_t get_os_version(void);
|
95
|
+
static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
|
96
|
+
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
|
97
|
+
static uint32_t get_ruby_platform(void);
|
99
98
|
|
100
99
|
/*
|
101
100
|
* Helper functions to call ruby methods on handler object without crashing on
|
@@ -136,7 +135,7 @@ Init_bootsnap(void)
|
|
136
135
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
137
136
|
|
138
137
|
current_ruby_revision = FIX2INT(rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")));
|
139
|
-
|
138
|
+
current_ruby_platform = get_ruby_platform();
|
140
139
|
|
141
140
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
142
141
|
|
@@ -173,10 +172,9 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
173
172
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
174
173
|
*/
|
175
174
|
static uint64_t
|
176
|
-
|
175
|
+
fnv1a_64_iter(uint64_t h, const char *str)
|
177
176
|
{
|
178
177
|
unsigned char *s = (unsigned char *)str;
|
179
|
-
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
180
178
|
|
181
179
|
while (*s) {
|
182
180
|
h ^= (uint64_t)*s++;
|
@@ -186,26 +184,42 @@ fnv1a_64(const char *str)
|
|
186
184
|
return h;
|
187
185
|
}
|
188
186
|
|
187
|
+
static uint64_t
|
188
|
+
fnv1a_64(const char *str)
|
189
|
+
{
|
190
|
+
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
191
|
+
return fnv1a_64_iter(h, str);
|
192
|
+
}
|
193
|
+
|
189
194
|
/*
|
190
|
-
*
|
191
|
-
*
|
195
|
+
* When ruby's version doesn't change, but it's recompiled on a different OS
|
196
|
+
* (or OS version), we need to invalidate the cache.
|
197
|
+
*
|
198
|
+
* We actually factor in some extra information here, to be extra confident
|
199
|
+
* that we don't try to re-use caches that will not be compatible, by factoring
|
200
|
+
* in utsname.version.
|
192
201
|
*/
|
193
202
|
static uint32_t
|
194
|
-
|
203
|
+
get_ruby_platform(void)
|
195
204
|
{
|
196
|
-
#ifdef _WIN32
|
197
|
-
return (uint32_t)GetVersion();
|
198
|
-
#else
|
199
205
|
uint64_t hash;
|
200
|
-
|
206
|
+
VALUE ruby_platform;
|
201
207
|
|
202
|
-
|
203
|
-
|
208
|
+
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
209
|
+
hash = fnv1a_64(RSTRING_PTR(ruby_platform));
|
204
210
|
|
205
|
-
|
211
|
+
#ifdef _WIN32
|
212
|
+
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
213
|
+
#else
|
214
|
+
struct utsname utsname;
|
215
|
+
|
216
|
+
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
217
|
+
if (uname(&utsname) >= 0) {
|
218
|
+
hash = fnv1a_64_iter(hash, utsname.version);
|
219
|
+
}
|
206
220
|
|
207
221
|
return (uint32_t)(hash >> 32);
|
208
|
-
|
222
|
+
#endif
|
209
223
|
}
|
210
224
|
|
211
225
|
/*
|
@@ -239,7 +253,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
239
253
|
{
|
240
254
|
return (
|
241
255
|
k1->version == k2->version &&
|
242
|
-
k1->
|
256
|
+
k1->ruby_platform == k2->ruby_platform &&
|
243
257
|
k1->compile_option == k2->compile_option &&
|
244
258
|
k1->ruby_revision == k2->ruby_revision &&
|
245
259
|
k1->size == k2->size &&
|
@@ -279,24 +293,28 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
279
293
|
* was loaded.
|
280
294
|
*/
|
281
295
|
static int
|
282
|
-
open_current_file(char * path, struct bs_cache_key * key)
|
296
|
+
open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
|
283
297
|
{
|
284
298
|
struct stat statbuf;
|
285
299
|
int fd;
|
286
300
|
|
287
301
|
fd = open(path, O_RDONLY);
|
288
|
-
if (fd < 0)
|
302
|
+
if (fd < 0) {
|
303
|
+
*errno_provenance = (char *)"bs_fetch:open_current_file:open";
|
304
|
+
return fd;
|
305
|
+
}
|
289
306
|
#ifdef _WIN32
|
290
307
|
setmode(fd, O_BINARY);
|
291
308
|
#endif
|
292
309
|
|
293
310
|
if (fstat(fd, &statbuf) < 0) {
|
311
|
+
*errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
|
294
312
|
close(fd);
|
295
313
|
return -1;
|
296
314
|
}
|
297
315
|
|
298
316
|
key->version = current_version;
|
299
|
-
key->
|
317
|
+
key->ruby_platform = current_ruby_platform;
|
300
318
|
key->compile_option = current_compile_option_crc32;
|
301
319
|
key->ruby_revision = current_ruby_revision;
|
302
320
|
key->size = (uint64_t)statbuf.st_size;
|
@@ -336,12 +354,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
336
354
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
337
355
|
*/
|
338
356
|
static int
|
339
|
-
open_cache_file(const char * path, struct bs_cache_key * key)
|
357
|
+
open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
|
340
358
|
{
|
341
359
|
int fd, res;
|
342
360
|
|
343
361
|
fd = open(path, O_RDONLY);
|
344
362
|
if (fd < 0) {
|
363
|
+
*errno_provenance = (char *)"bs_fetch:open_cache_file:open";
|
345
364
|
if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
|
346
365
|
return ERROR_WITH_ERRNO;
|
347
366
|
}
|
@@ -351,6 +370,7 @@ open_cache_file(const char * path, struct bs_cache_key * key)
|
|
351
370
|
|
352
371
|
res = bs_read_key(fd, key);
|
353
372
|
if (res < 0) {
|
373
|
+
*errno_provenance = (char *)"bs_fetch:open_cache_file:read";
|
354
374
|
close(fd);
|
355
375
|
return res;
|
356
376
|
}
|
@@ -374,7 +394,7 @@ open_cache_file(const char * path, struct bs_cache_key * key)
|
|
374
394
|
* or exception, will be the final data returnable to the user.
|
375
395
|
*/
|
376
396
|
static int
|
377
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag)
|
397
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
|
378
398
|
{
|
379
399
|
char * data = NULL;
|
380
400
|
ssize_t nread;
|
@@ -383,6 +403,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
383
403
|
VALUE storage_data;
|
384
404
|
|
385
405
|
if (data_size > 100000000000) {
|
406
|
+
*errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
|
386
407
|
errno = EINVAL; /* because wtf? */
|
387
408
|
ret = -1;
|
388
409
|
goto done;
|
@@ -390,6 +411,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
390
411
|
data = ALLOC_N(char, data_size);
|
391
412
|
nread = read(fd, data, data_size);
|
392
413
|
if (nread < 0) {
|
414
|
+
*errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
|
393
415
|
ret = -1;
|
394
416
|
goto done;
|
395
417
|
}
|
@@ -441,23 +463,29 @@ mkpath(char * file_path, mode_t mode)
|
|
441
463
|
* path.
|
442
464
|
*/
|
443
465
|
static int
|
444
|
-
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
466
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
445
467
|
{
|
446
468
|
char template[MAX_CACHEPATH_SIZE + 20];
|
447
469
|
char * dest;
|
448
470
|
char * tmp_path;
|
449
|
-
int fd;
|
471
|
+
int fd, ret;
|
450
472
|
ssize_t nwrite;
|
451
473
|
|
452
474
|
dest = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
453
475
|
strcat(dest, ".tmp.XXXXXX");
|
454
476
|
|
455
477
|
tmp_path = mktemp(template);
|
456
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT,
|
478
|
+
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
457
479
|
if (fd < 0) {
|
458
|
-
if (mkpath(path,
|
459
|
-
|
460
|
-
|
480
|
+
if (mkpath(path, 0775) < 0) {
|
481
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
|
482
|
+
return -1;
|
483
|
+
}
|
484
|
+
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
485
|
+
if (fd < 0) {
|
486
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
|
487
|
+
return -1;
|
488
|
+
}
|
461
489
|
}
|
462
490
|
#ifdef _WIN32
|
463
491
|
setmode(fd, O_BINARY);
|
@@ -465,8 +493,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
|
465
493
|
|
466
494
|
key->data_size = RSTRING_LEN(data);
|
467
495
|
nwrite = write(fd, key, KEY_SIZE);
|
468
|
-
if (nwrite < 0)
|
496
|
+
if (nwrite < 0) {
|
497
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
|
498
|
+
return -1;
|
499
|
+
}
|
469
500
|
if (nwrite != KEY_SIZE) {
|
501
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
|
470
502
|
errno = EIO; /* Lies but whatever */
|
471
503
|
return -1;
|
472
504
|
}
|
@@ -474,38 +506,32 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
|
474
506
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
475
507
|
if (nwrite < 0) return -1;
|
476
508
|
if (nwrite != RSTRING_LEN(data)) {
|
509
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
|
477
510
|
errno = EIO; /* Lies but whatever */
|
478
511
|
return -1;
|
479
512
|
}
|
480
513
|
|
481
514
|
close(fd);
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
/*
|
486
|
-
* Given an errno value (converted to a ruby Fixnum), return the corresponding
|
487
|
-
* Errno::* constant. If none is found, return StandardError instead.
|
488
|
-
*/
|
489
|
-
static VALUE
|
490
|
-
prot_exception_for_errno(VALUE err)
|
491
|
-
{
|
492
|
-
if (err != INT2FIX(0)) {
|
493
|
-
VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
|
494
|
-
VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
|
495
|
-
VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
|
496
|
-
return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
|
515
|
+
ret = rename(tmp_path, path);
|
516
|
+
if (ret < 0) {
|
517
|
+
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
|
497
518
|
}
|
498
|
-
return
|
519
|
+
return ret;
|
499
520
|
}
|
500
521
|
|
501
522
|
|
502
523
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
503
524
|
* long, into a buffer */
|
504
525
|
static ssize_t
|
505
|
-
bs_read_contents(int fd, size_t size, char ** contents)
|
526
|
+
bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
|
506
527
|
{
|
528
|
+
ssize_t nread;
|
507
529
|
*contents = ALLOC_N(char, size);
|
508
|
-
|
530
|
+
nread = read(fd, *contents, size);
|
531
|
+
if (nread < 0) {
|
532
|
+
*errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
|
533
|
+
}
|
534
|
+
return nread;
|
509
535
|
}
|
510
536
|
|
511
537
|
/*
|
@@ -513,24 +539,24 @@ bs_read_contents(int fd, size_t size, char ** contents)
|
|
513
539
|
* Bootsnap::CompileCache::Native.fetch.
|
514
540
|
*
|
515
541
|
* There are three "formats" in use here:
|
516
|
-
* 1. "input"
|
542
|
+
* 1. "input" format, which is what we load from the source file;
|
517
543
|
* 2. "storage" format, which we write to the cache;
|
518
544
|
* 3. "output" format, which is what we return.
|
519
545
|
*
|
520
546
|
* E.g., For ISeq compilation:
|
521
|
-
* input:
|
547
|
+
* input: ruby source, as text
|
522
548
|
* storage: binary string (RubyVM::InstructionSequence#to_binary)
|
523
|
-
* output:
|
549
|
+
* output: Instance of RubyVM::InstructionSequence
|
524
550
|
*
|
525
551
|
* And for YAML:
|
526
|
-
* input:
|
552
|
+
* input: yaml as text
|
527
553
|
* storage: MessagePack or Marshal text
|
528
|
-
* output:
|
554
|
+
* output: ruby object, loaded from yaml/messagepack/marshal
|
529
555
|
*
|
530
|
-
*
|
531
|
-
* * storage_to_output(
|
532
|
-
* * input_to_output(
|
533
|
-
* * input_to_storage(
|
556
|
+
* A handler<I,S,O> passed in must support three messages:
|
557
|
+
* * storage_to_output(S) -> O
|
558
|
+
* * input_to_output(I) -> O
|
559
|
+
* * input_to_storage(I) -> S
|
534
560
|
* (input_to_storage may raise Bootsnap::CompileCache::Uncompilable, which
|
535
561
|
* will prevent caching and cause output to be generated with
|
536
562
|
* input_to_output)
|
@@ -558,7 +584,8 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
558
584
|
struct bs_cache_key cached_key, current_key;
|
559
585
|
char * contents = NULL;
|
560
586
|
int cache_fd = -1, current_fd = -1;
|
561
|
-
int res, valid_cache, exception_tag = 0;
|
587
|
+
int res, valid_cache = 0, exception_tag = 0;
|
588
|
+
char * errno_provenance = NULL;
|
562
589
|
|
563
590
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
564
591
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
@@ -567,20 +594,27 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
567
594
|
VALUE exception; /* ruby exception object to raise instead of returning */
|
568
595
|
|
569
596
|
/* Open the source file and generate a cache key for it */
|
570
|
-
current_fd = open_current_file(path, ¤t_key);
|
597
|
+
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
571
598
|
if (current_fd < 0) goto fail_errno;
|
572
599
|
|
573
600
|
/* Open the cache key if it exists, and read its cache key in */
|
574
|
-
cache_fd = open_cache_file(cache_path, &cached_key);
|
575
|
-
if (cache_fd
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
601
|
+
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
602
|
+
if (cache_fd == CACHE_MISSING_OR_INVALID) {
|
603
|
+
/* This is ok: valid_cache remains false, we re-populate it. */
|
604
|
+
} else if (cache_fd < 0) {
|
605
|
+
goto fail_errno;
|
606
|
+
} else {
|
607
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
608
|
+
* it was generated. */
|
609
|
+
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
610
|
+
}
|
580
611
|
|
581
612
|
if (valid_cache) {
|
582
613
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
583
|
-
res = fetch_cached_data(
|
614
|
+
res = fetch_cached_data(
|
615
|
+
cache_fd, (ssize_t)cached_key.data_size, handler,
|
616
|
+
&output_data, &exception_tag, &errno_provenance
|
617
|
+
);
|
584
618
|
if (exception_tag != 0) goto raise;
|
585
619
|
else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
|
586
620
|
else if (res == ERROR_WITH_ERRNO) goto fail_errno;
|
@@ -591,7 +625,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
591
625
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
592
626
|
|
593
627
|
/* Read the contents of the source file into a buffer */
|
594
|
-
if (bs_read_contents(current_fd, current_key.size, &contents) < 0) goto fail_errno;
|
628
|
+
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
595
629
|
input_data = rb_str_new_static(contents, current_key.size);
|
596
630
|
|
597
631
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
@@ -608,7 +642,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
608
642
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
609
643
|
|
610
644
|
/* Write the cache key and storage_data to the cache directory */
|
611
|
-
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data);
|
645
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
612
646
|
if (res < 0) goto fail_errno;
|
613
647
|
|
614
648
|
/* Having written the cache, now convert storage_data to output_data */
|
@@ -618,7 +652,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
618
652
|
/* If output_data is nil, delete the cache entry and generate the output
|
619
653
|
* using input_to_output */
|
620
654
|
if (NIL_P(output_data)) {
|
621
|
-
if (unlink(cache_path) < 0)
|
655
|
+
if (unlink(cache_path) < 0) {
|
656
|
+
errno_provenance = (char *)"bs_fetch:unlink";
|
657
|
+
goto fail_errno;
|
658
|
+
}
|
622
659
|
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
623
660
|
if (exception_tag != 0) goto raise;
|
624
661
|
}
|
@@ -635,8 +672,7 @@ succeed:
|
|
635
672
|
return output_data;
|
636
673
|
fail_errno:
|
637
674
|
CLEANUP;
|
638
|
-
exception =
|
639
|
-
if (res) exception = rb_eStandardError;
|
675
|
+
exception = rb_syserr_new(errno, errno_provenance);
|
640
676
|
rb_exc_raise(exception);
|
641
677
|
__builtin_unreachable();
|
642
678
|
raise:
|
@@ -655,7 +691,17 @@ invalid_type_storage_data:
|
|
655
691
|
/********************* Handler Wrappers **************************************/
|
656
692
|
/*****************************************************************************
|
657
693
|
* Everything after this point in the file is just wrappers to deal with ruby's
|
658
|
-
* clunky method of handling exceptions from ruby methods invoked from C
|
694
|
+
* clunky method of handling exceptions from ruby methods invoked from C:
|
695
|
+
*
|
696
|
+
* In order to call a ruby method from C, while protecting against crashing in
|
697
|
+
* the event of an exception, we must call the method with rb_protect().
|
698
|
+
*
|
699
|
+
* rb_protect takes a C function and precisely one argument; however, we want
|
700
|
+
* to pass multiple arguments, so we must create structs to wrap them up.
|
701
|
+
*
|
702
|
+
* These functions return an exception_tag, which, if non-zero, indicates an
|
703
|
+
* exception that should be jumped to with rb_jump_tag after cleaning up
|
704
|
+
* allocated resources.
|
659
705
|
*/
|
660
706
|
|
661
707
|
struct s2o_data {
|