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 {
         |