Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rubygem-bootsnap for openSUSE:Factory checked in at 2024-06-24 20:54:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-bootsnap (Old) and /work/SRC/openSUSE:Factory/.rubygem-bootsnap.new.18349 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "rubygem-bootsnap" Mon Jun 24 20:54:50 2024 rev:24 rq:1182765 version:1.18.3 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-bootsnap/rubygem-bootsnap.changes 2024-02-26 19:49:43.651315299 +0100 +++ /work/SRC/openSUSE:Factory/.rubygem-bootsnap.new.18349/rubygem-bootsnap.changes 2024-06-24 20:56:25.715156968 +0200 @@ -1,0 +2,29 @@ +Fri Jun 21 09:33:29 UTC 2024 - Dan Čermák <dan.cermak@posteo.net> + +- # 1.18.3 + +* Fix the cache corruption issue in the revalidation feature. See #474. + The cache revalidation feature remains opt-in for now, until it is more battle tested. + +# 1.18.2 + +* Disable stale cache entries revalidation by default as it seems to cause cache corruption issues. See #471 and #474. + Will be re-enabled in a future version once the root cause is identified. +* Fix a potential compilation issue on some systems. See #470. + +# 1.18.1 + +* Handle `EPERM` errors when opening files with `O_NOATIME`. + +# 1.18.0 + +* `Bootsnap.instrumentation` now receive `:hit` events. +* Add `Bootsnap.log_stats!` to print hit rate statistics on process exit. Can also be enabled with `BOOTSNAP_STATS=1`. +* Revalidate stale cache entries by digesting the source content. + This should significantly improve performance in environments where `mtime` isn't preserved (e.g. CI systems doing a git clone, etc). + See #468. +* Open source files and cache entries with `O_NOATIME` when available to reduce disk accesses. See #469. +* `bootsnap precompile --gemfile` now look for `.rb` files in the whole gem and not just the `lib/` directory. See #466. + + +------------------------------------------------------------------- Old: ---- bootsnap-1.17.1.gem New: ---- bootsnap-1.18.3.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-bootsnap.spec ++++++ --- /var/tmp/diff_new_pack.jiQa2n/_old 2024-06-24 20:56:27.179210526 +0200 +++ /var/tmp/diff_new_pack.jiQa2n/_new 2024-06-24 20:56:27.195211111 +0200 @@ -24,7 +24,7 @@ # Name: rubygem-bootsnap -Version: 1.17.1 +Version: 1.18.3 Release: 0 %define mod_name bootsnap %define mod_full_name %{mod_name}-%{version} ++++++ bootsnap-1.17.1.gem -> bootsnap-1.18.3.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/CHANGELOG.md new/CHANGELOG.md --- old/CHANGELOG.md 2024-01-12 12:03:46.000000000 +0100 +++ new/CHANGELOG.md 2024-01-31 15:42:25.000000000 +0100 @@ -1,5 +1,30 @@ # Unreleased +# 1.18.3 + +* Fix the cache corruption issue in the revalidation feature. See #474. + The cache revalidation feature remains opt-in for now, until it is more battle tested. + +# 1.18.2 + +* Disable stale cache entries revalidation by default as it seems to cause cache corruption issues. See #471 and #474. + Will be re-enabled in a future version once the root cause is identified. +* Fix a potential compilation issue on some systems. See #470. + +# 1.18.1 + +* Handle `EPERM` errors when opening files with `O_NOATIME`. + +# 1.18.0 + +* `Bootsnap.instrumentation` now receive `:hit` events. +* Add `Bootsnap.log_stats!` to print hit rate statistics on process exit. Can also be enabled with `BOOTSNAP_STATS=1`. +* Revalidate stale cache entries by digesting the source content. + This should significantly improve performance in environments where `mtime` isn't preserved (e.g. CI systems doing a git clone, etc). + See #468. +* Open source files and cache entries with `O_NOATIME` when available to reduce disk accesses. See #469. +* `bootsnap precompile --gemfile` now look for `.rb` files in the whole gem and not just the `lib/` directory. See #466. + # 1.17.1 * Fix a compatibility issue with the `prism` library that ships with Ruby 3.3. See #463. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/README.md new/README.md --- old/README.md 2024-01-12 12:03:46.000000000 +0100 +++ new/README.md 2024-01-31 15:42:25.000000000 +0100 @@ -81,6 +81,7 @@ - `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches. - `BOOTSNAP_READONLY` configure bootsnap to not update the cache on miss or stale entries. - `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR. +- `BOOTSNAP_STATS` log hit rate statistics on exit. Can't be used if `BOOTSNAP_LOG` is enabled. - `BOOTSNAP_IGNORE_DIRECTORIES` a comma separated list of directories that shouldn't be scanned. Useful when you have large directories of non-ruby files inside `$LOAD_PATH`. It defaults to ignore any directory named `node_modules`. @@ -99,8 +100,8 @@ Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" } ``` -`event` is either `:miss` or `:stale`. You can also call `Bootsnap.log!` as a shortcut to -log all events to STDERR. +`event` is either `:hit`, `:miss`, `:stale` or `:revalidated`. +You can also call `Bootsnap.log!` as a shortcut to log all events to STDERR. To turn instrumentation back off you can set it to nil: Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ext/bootsnap/bootsnap.c new/ext/bootsnap/bootsnap.c --- old/ext/bootsnap/bootsnap.c 2024-01-12 12:03:46.000000000 +0100 +++ new/ext/bootsnap/bootsnap.c 2024-01-31 15:42:25.000000000 +0100 @@ -18,8 +18,19 @@ #include <sys/types.h> #include <errno.h> #include <fcntl.h> +#include <unistd.h> #include <sys/stat.h> +#ifdef __APPLE__ + // The symbol is present, however not in the headers + // See: https://github.com/Shopify/bootsnap/issues/470 + extern int fdatasync(int); +#endif + +#ifndef O_NOATIME +#define O_NOATIME 0 +#endif + /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to * 981 for the cache dir */ #define MAX_CACHEPATH_SIZE 1000 @@ -30,7 +41,7 @@ #define MAX_CREATE_TEMPFILE_ATTEMPT 3 #ifndef RB_UNLIKELY - #define RB_UNLIKELY(x) (x) +#define RB_UNLIKELY(x) (x) #endif /* @@ -54,8 +65,10 @@ uint32_t ruby_revision; uint64_t size; uint64_t mtime; - uint64_t data_size; /* not used for equality */ - uint8_t pad[24]; + uint64_t data_size; // + uint64_t digest; + uint8_t digest_set; + uint8_t pad[15]; } __attribute__((packed)); /* @@ -69,7 +82,7 @@ STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE); /* Effectively a schema version. Bumping invalidates all previous caches */ -static const uint32_t current_version = 4; +static const uint32_t current_version = 5; /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a * new OS ABI, etc. */ @@ -87,25 +100,36 @@ static VALUE rb_mBootsnap_CompileCache_Native; static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE; static ID instrumentation_method; -static VALUE sym_miss; -static VALUE sym_stale; +static VALUE sym_hit, sym_miss, sym_stale, sym_revalidated; static bool instrumentation_enabled = false; static bool readonly = false; +static bool revalidation = false; +static bool perm_issue = false; /* Functions exposed as module functions on Bootsnap::CompileCache::Native */ static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled); static VALUE bs_readonly_set(VALUE self, VALUE enabled); +static VALUE bs_revalidation_set(VALUE self, VALUE enabled); static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v); static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args); static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler); /* Helpers */ +enum cache_status { + miss, + hit, + stale, +}; static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]); static int bs_read_key(int fd, struct bs_cache_key * key); -static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2); +static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2); +static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data); +static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance); + +static void bs_cache_key_digest(struct bs_cache_key * key, const VALUE input_data); static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args); static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler); -static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance); +static int open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance); 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); static uint32_t get_ruby_revision(void); static uint32_t get_ruby_platform(void); @@ -161,14 +185,14 @@ instrumentation_method = rb_intern("_instrument"); + sym_hit = ID2SYM(rb_intern("hit")); sym_miss = ID2SYM(rb_intern("miss")); - rb_global_variable(&sym_miss); - sym_stale = ID2SYM(rb_intern("stale")); - rb_global_variable(&sym_stale); + sym_revalidated = ID2SYM(rb_intern("revalidated")); rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1); rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1); + rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1); rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0); rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4); rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3); @@ -185,6 +209,14 @@ return enabled; } +static inline void +bs_instrumentation(VALUE event, VALUE path) +{ + if (RB_UNLIKELY(instrumentation_enabled)) { + rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path); + } +} + static VALUE bs_readonly_set(VALUE self, VALUE enabled) { @@ -192,6 +224,13 @@ return enabled; } +static VALUE +bs_revalidation_set(VALUE self, VALUE enabled) +{ + revalidation = RTEST(enabled); + return enabled; +} + /* * Bootsnap's ruby code registers a hook that notifies us via this function * when compile_option changes. These changes invalidate all existing caches. @@ -290,17 +329,59 @@ * The data_size member is not compared, as it serves more of a "header" * function. */ -static int -cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2) +static enum cache_status cache_key_equal_fast_path(struct bs_cache_key *k1, + struct bs_cache_key *k2) { + if (k1->version == k2->version && + k1->ruby_platform == k2->ruby_platform && + k1->compile_option == k2->compile_option && + k1->ruby_revision == k2->ruby_revision && k1->size == k2->size) { + if (k1->mtime == k2->mtime) { + return hit; + } + if (revalidation) { + return stale; + } + } + return miss; +} + +static int cache_key_equal_slow_path(struct bs_cache_key *current_key, + struct bs_cache_key *cached_key, + const VALUE input_data) +{ + bs_cache_key_digest(current_key, input_data); + return current_key->digest == cached_key->digest; +} + +static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance) { - return ( - k1->version == k2->version && - k1->ruby_platform == k2->ruby_platform && - k1->compile_option == k2->compile_option && - k1->ruby_revision == k2->ruby_revision && - k1->size == k2->size && - k1->mtime == k2->mtime - ); + old_key->mtime = current_key->mtime; + lseek(cache_fd, 0, SEEK_SET); + ssize_t nwrite = write(cache_fd, old_key, KEY_SIZE); + if (nwrite < 0) { + *errno_provenance = "update_cache_key:write"; + return -1; + } + +#ifdef HAVE_FDATASYNC + if (fdatasync(cache_fd) < 0) { + *errno_provenance = "update_cache_key:fdatasync"; + return -1; + } +#endif + + return 0; +} + +/* + * Fills the cache key digest. + */ +static void bs_cache_key_digest(struct bs_cache_key *key, + const VALUE input_data) { + if (key->digest_set) + return; + key->digest = fnv1a_64(input_data); + key->digest_set = 1; } /* @@ -356,17 +437,34 @@ return bs_precompile(path, path_v, cache_path, handler); } + +static int bs_open_noatime(const char *path, int flags) { + int fd = 1; + if (!perm_issue) { + fd = open(path, flags | O_NOATIME); + if (fd < 0 && errno == EPERM) { + errno = 0; + perm_issue = true; + } + } + + if (perm_issue) { + fd = open(path, flags); + } + return fd; +} + /* * Open the file we want to load/cache and generate a cache key for it if it * was loaded. */ static int -open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance) +open_current_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance) { struct stat statbuf; int fd; - fd = open(path, O_RDONLY); + fd = bs_open_noatime(path, O_RDONLY); if (fd < 0) { *errno_provenance = "bs_fetch:open_current_file:open"; return fd; @@ -389,6 +487,7 @@ key->ruby_revision = current_ruby_revision; key->size = (uint64_t)statbuf.st_size; key->mtime = (uint64_t)statbuf.st_mtime; + key->digest_set = false; return fd; } @@ -432,7 +531,12 @@ { int fd, res; - fd = open(path, O_RDONLY); + if (readonly || !revalidation) { + fd = bs_open_noatime(path, O_RDONLY); + } else { + fd = bs_open_noatime(path, O_RDWR); + } + if (fd < 0) { *errno_provenance = "bs_fetch:open_cache_file:open"; return CACHE_MISS; @@ -677,7 +781,8 @@ int res, valid_cache = 0, exception_tag = 0; const char * errno_provenance = NULL; - VALUE input_data; /* data read from source file, e.g. YAML or ruby source */ + VALUE status = Qfalse; + VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */ VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */ @@ -695,20 +800,44 @@ cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance); if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) { /* This is ok: valid_cache remains false, we re-populate it. */ - if (RB_UNLIKELY(instrumentation_enabled)) { - rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v); - } + bs_instrumentation(cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v); } else if (cache_fd < 0) { exception_message = rb_str_new_cstr(cache_path); goto fail_errno; } else { /* True if the cache existed and no invalidating changes have occurred since * it was generated. */ - valid_cache = cache_key_equal(¤t_key, &cached_key); - if (RB_UNLIKELY(instrumentation_enabled)) { - if (!valid_cache) { - rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v); + + switch(cache_key_equal_fast_path(¤t_key, &cached_key)) { + case hit: + status = sym_hit; + valid_cache = true; + break; + case miss: + valid_cache = false; + break; + case stale: + valid_cache = false; + if ((input_data = bs_read_contents(current_fd, current_key.size, + &errno_provenance)) == Qfalse) { + exception_message = path_v; + goto fail_errno; + } + valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data); + if (valid_cache) { + if (!readonly) { + if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) { + exception_message = path_v; + goto fail_errno; + } + } + status = sym_revalidated; } + break; + }; + + if (!valid_cache) { + status = sym_stale; } } @@ -722,7 +851,7 @@ else if (res == CACHE_UNCOMPILABLE) { /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output` This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */ - if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){ + if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) { exception_message = path_v; goto fail_errno; } @@ -741,7 +870,7 @@ /* Cache is stale, invalid, or missing. Regenerate and write it out. */ /* Read the contents of the source file into a buffer */ - if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse){ + if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) { exception_message = path_v; goto fail_errno; } @@ -763,6 +892,7 @@ * We do however ignore any failures to persist the cache, as it's better * to move along, than to interrupt the process. */ + bs_cache_key_digest(¤t_key, input_data); atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance); /* Having written the cache, now convert storage_data to output_data */ @@ -792,6 +922,7 @@ goto succeed; /* output_data is now the correct return. */ #define CLEANUP \ + if (status != Qfalse) bs_instrumentation(status, path_v); \ if (current_fd >= 0) close(current_fd); \ if (cache_fd >= 0) close(cache_fd); @@ -800,6 +931,12 @@ return output_data; fail_errno: CLEANUP; + if (errno_provenance) { + exception_message = rb_str_concat( + rb_str_new_cstr(errno_provenance), + rb_str_concat(rb_str_new_cstr(": "), exception_message) + ); + } exception = rb_syserr_new_str(errno, exception_message); rb_exc_raise(exception); __builtin_unreachable(); @@ -818,13 +955,16 @@ static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler) { + if (readonly) { + return Qfalse; + } + struct bs_cache_key cached_key, current_key; - char * contents = NULL; int cache_fd = -1, current_fd = -1; int res, valid_cache = 0, exception_tag = 0; const char * errno_provenance = NULL; - VALUE input_data; /* data read from source file, e.g. YAML or ruby source */ + VALUE input_data = Qfalse; /* data read from source file, e.g. YAML or ruby source */ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */ /* Open the source file and generate a cache key for it */ @@ -840,7 +980,26 @@ } else { /* True if the cache existed and no invalidating changes have occurred since * it was generated. */ - valid_cache = cache_key_equal(¤t_key, &cached_key); + switch(cache_key_equal_fast_path(¤t_key, &cached_key)) { + case hit: + valid_cache = true; + break; + case miss: + valid_cache = false; + break; + case stale: + valid_cache = false; + if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) { + goto fail; + } + valid_cache = cache_key_equal_slow_path(¤t_key, &cached_key, input_data); + if (valid_cache) { + if (update_cache_key(¤t_key, &cached_key, cache_fd, &errno_provenance)) { + goto fail; + } + } + break; + }; } if (valid_cache) { @@ -867,6 +1026,7 @@ if (!RB_TYPE_P(storage_data, T_STRING)) goto fail; /* Write the cache key and storage_data to the cache directory */ + bs_cache_key_digest(¤t_key, input_data); res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance); if (res < 0) goto fail; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ext/bootsnap/extconf.rb new/ext/bootsnap/extconf.rb --- old/ext/bootsnap/extconf.rb 2024-01-12 12:03:46.000000000 +0100 +++ new/ext/bootsnap/extconf.rb 2024-01-31 15:42:25.000000000 +0100 @@ -3,21 +3,28 @@ require "mkmf" if %w[ruby truffleruby].include?(RUBY_ENGINE) - $CFLAGS << " -O3 " - $CFLAGS << " -std=c99" + have_func "fdatasync", "unistd.h" + + unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/) + append_cppflags ["-D_GNU_SOURCE"] # Needed of O_NOATIME + end + + append_cflags ["-O3", "-std=c99"] # ruby.h has some -Wpedantic fails in some cases # (e.g. https://github.com/Shopify/bootsnap/issues/15) unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"]) - $CFLAGS << " -Wall" - $CFLAGS << " -Werror" - $CFLAGS << " -Wextra" - $CFLAGS << " -Wpedantic" - - $CFLAGS << " -Wno-unused-parameter" # VALUE self has to be there but we don't care what it is. - $CFLAGS << " -Wno-keyword-macro" # hiding return - $CFLAGS << " -Wno-gcc-compat" # ruby.h 2.6.0 on macos 10.14, dunno - $CFLAGS << " -Wno-compound-token-split-by-macro" + append_cflags([ + "-Wall", + "-Werror", + "-Wextra", + "-Wpedantic", + + "-Wno-unused-parameter", # VALUE self has to be there but we don't care what it is. + "-Wno-keyword-macro", # hiding return + "-Wno-gcc-compat", # ruby.h 2.6.0 on macos 10.14, dunno + "-Wno-compound-token-split-by-macro", + ]) end create_makefile("bootsnap/bootsnap") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bootsnap/cli.rb new/lib/bootsnap/cli.rb --- old/lib/bootsnap/cli.rb 2024-01-12 12:03:46.000000000 +0100 +++ new/lib/bootsnap/cli.rb 2024-01-31 15:42:25.000000000 +0100 @@ -60,14 +60,16 @@ precompile_json_files(main_sources) if compile_gemfile - # Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling. - gem_exclude = Regexp.union([exclude, "/spec/", "/test/"].compact) - precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude) - # Gems that include JSON or YAML files usually don't put them in `lib/`. # So we look at the gem root. + # Similarly, gems that include Rails engines generally file Ruby files in `app/`. + # However some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling. + gem_exclude = Regexp.union([exclude, "/spec/", "/test/", "/features/"].compact) + gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems/[^/]+} - gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq + gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] || p }.uniq + + precompile_ruby_files(gem_paths, exclude: gem_exclude) precompile_yaml_files(gem_paths, exclude: gem_exclude) precompile_json_files(gem_paths, exclude: gem_exclude) end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bootsnap/compile_cache.rb new/lib/bootsnap/compile_cache.rb --- old/lib/bootsnap/compile_cache.rb 2024-01-12 12:03:46.000000000 +0100 +++ new/lib/bootsnap/compile_cache.rb 2024-01-31 15:42:25.000000000 +0100 @@ -9,7 +9,7 @@ Error = Class.new(StandardError) - def self.setup(cache_dir:, iseq:, yaml:, json:, readonly: false) + def self.setup(cache_dir:, iseq:, yaml:, json:, readonly: false, revalidation: false) if iseq if supported? require_relative "compile_cache/iseq" @@ -39,6 +39,7 @@ if supported? && defined?(Bootsnap::CompileCache::Native) Bootsnap::CompileCache::Native.readonly = readonly + Bootsnap::CompileCache::Native.revalidation = revalidation end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bootsnap/version.rb new/lib/bootsnap/version.rb --- old/lib/bootsnap/version.rb 2024-01-12 12:03:46.000000000 +0100 +++ new/lib/bootsnap/version.rb 2024-01-31 15:42:25.000000000 +0100 @@ -1,5 +1,5 @@ # frozen_string_literal: true module Bootsnap - VERSION = "1.17.1" + VERSION = "1.18.3" end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/bootsnap.rb new/lib/bootsnap.rb --- old/lib/bootsnap.rb 2024-01-12 12:03:46.000000000 +0100 +++ new/lib/bootsnap.rb 2024-01-31 15:42:25.000000000 +0100 @@ -11,6 +11,16 @@ class << self attr_reader :logger + def log_stats! + stats = {hit: 0, revalidated: 0, miss: 0, stale: 0} + self.instrumentation = ->(event, _path) { stats[event] += 1 } + Kernel.at_exit do + stats.each do |event, count| + $stderr.puts "bootsnap #{event}: #{count}" + end + end + end + def log! self.logger = $stderr.method(:puts) end @@ -18,9 +28,9 @@ def logger=(logger) @logger = logger self.instrumentation = if logger.respond_to?(:debug) - ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") } + ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") unless event == :hit } else - ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") } + ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") unless event == :hit } end end @@ -41,6 +51,7 @@ load_path_cache: true, ignore_directories: nil, readonly: false, + revalidation: false, compile_cache_iseq: true, compile_cache_yaml: true, compile_cache_json: true @@ -60,6 +71,7 @@ yaml: compile_cache_yaml, json: compile_cache_json, readonly: readonly, + revalidation: revalidation, ) end @@ -110,6 +122,8 @@ if ENV["BOOTSNAP_LOG"] log! + elsif ENV["BOOTSNAP_STATS"] + log_stats! end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2024-01-12 12:03:46.000000000 +0100 +++ new/metadata 2024-01-31 15:42:25.000000000 +0100 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: bootsnap version: !ruby/object:Gem::Version - version: 1.17.1 + version: 1.18.3 platform: ruby authors: - Burke Libbey autorequire: bindir: exe cert_chain: [] -date: 2024-01-12 00:00:00.000000000 Z +date: 2024-01-31 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: msgpack @@ -83,7 +83,7 @@ - !ruby/object:Gem::Version version: '0' requirements: [] -rubygems_version: 3.5.4 +rubygems_version: 3.5.5 signing_key: specification_version: 4 summary: Boot large ruby/rails apps faster