bootsnap 1.24.6 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c89c653bbd5db473e06179c6606268271c8c540a588cca938662f168d64f2d39
4
- data.tar.gz: 449c08c79a258dea90e56646c9d62a06e1d696c065ce96c38a7f50ef2fb83f7b
3
+ metadata.gz: 6227028d2a559d0e5129ef91915f65a064082b2e1326517dd67da2f11d91d194
4
+ data.tar.gz: 1c7cbf82581f9162601a7723812201d240661c0d3477373b820cf7e942b9d416
5
5
  SHA512:
6
- metadata.gz: 2ea80faffec0206b9bb7b3d19c1419b2e7eefc2dc5f4eb03352862b83afa07ed293d0b15f04fc069d89dac1cd4d4636770626e90e6129ffad77f0cfcb8c3f676
7
- data.tar.gz: 82dcb1a4c934a9de29a8aa935ffe2d1a7c6e23ebf36ea743c3d29a4d9a1d8bd42696cdefd37dacdd21a4102f88f0b605d1cc6faef9c15ccdc6d99eef15f4483e
6
+ metadata.gz: 385183b9de17b6f72634b7307fdfc5481108afc06554b48bb2dede97e51ba4275d076357934d338286fceb3a221f1468ec2ea10e2f0f869de5a7b5b97fc444a8
7
+ data.tar.gz: c74bb8bdfe0e6f3615bbc4a6ea4f7c5a7a1d8589a3469da964c25b44451fbb3f54f10521b8549dc6ac400d7fb17ba4a686b5b289115e8b3bac81d7a5449e3c0b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.26.0
4
+
5
+ * Handle top level `Coverage` constant being defined, but without it being the true stdlib `coverage` module.
6
+ * Fix `bootsnap precompile` that could generate a corrupted cache entry if an already cached YAML file was modified
7
+ without changing its size.
8
+ * Workaround a potential Ruby SEGV if `Bootsnap.instrumentation` raised an error.
9
+
10
+ # 1.25.0
11
+
12
+ * Improve YAML parsing cache to more efficiently handle `Time`, `Date` and `DateTime`.
13
+
14
+ * Don't invalidate the compile cache when YJIT is toggled. YJIT is a runtime JIT and doesn't change the
15
+ serialized instruction sequences that are cached, but enabling it (via `--yjit`, `RUBYOPT`, or
16
+ `RubyVM::YJIT.enable`) adds a ` +YJIT` marker to `RUBY_DESCRIPTION` (` +YJIT <token>` on `YJIT_SUPPORT`
17
+ builds), which is part of the cache key. This previously discarded the entire compile cache whenever YJIT
18
+ was enabled at runtime but not at precompile time (or vice versa). The marker is now stripped before hashing.
19
+
20
+ * Fix `CompileCache::Native.fetch` and `.precompile` reading a non-`String` path argument (e.g. a `Pathname`)
21
+ with `RSTRING_PTR`. Regression from 1.24.0.
22
+
3
23
  # 1.24.6
4
24
 
5
25
  * Fix detection of Ruby bug #22023 on some patch versions of Ruby 3.4, and properly apply the workaround.
@@ -79,7 +79,7 @@ struct bs_cache_key {
79
79
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
80
80
 
81
81
  /* Effectively a schema version. Bumping invalidates all previous caches */
82
- static const uint32_t bootsnap_cache_version = 7;
82
+ static const uint32_t bootsnap_cache_version = 8;
83
83
 
84
84
  /* Invalidates cache when switching ruby version, platform or ABI */
85
85
  static uint64_t base_ruby_version_digest = 0;
@@ -318,7 +318,13 @@ static inline void
318
318
  bs_instrumentation(VALUE event, VALUE path)
319
319
  {
320
320
  if (RB_UNLIKELY(instrumentation_enabled)) {
321
+ // If the funcall raises and rescues an error we will lose the errinfo
322
+ // that exists now so we save it before and restore it after.
323
+ VALUE saved_errinfo = rb_errinfo();
321
324
  rb_funcall(rb_mBootsnap, instrumentation_method, 2, event, path);
325
+ // If the ruby code in instrumentation_method raised we won't reach this point.
326
+ // If it didn't raise it _might_ have rescued so we should restore errinfo.
327
+ rb_set_errinfo(saved_errinfo);
322
328
  }
323
329
  }
324
330
 
@@ -384,7 +390,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
384
390
  static uint64_t
385
391
  get_ruby_version_digest(void)
386
392
  {
387
- uint64_t hash = fnv1a_64_str(rb_const_get(rb_cObject, rb_intern("RUBY_DESCRIPTION")));
393
+ uint64_t hash = fnv1a_64_str(rb_const_get(rb_mBootsnap, rb_intern("RUBY_CACHE_KEY")));
388
394
  hash = fnv1a_64_iter(hash, (unsigned char *)&bootsnap_cache_version, sizeof(bootsnap_cache_version));
389
395
  return hash;
390
396
  }
@@ -395,12 +401,14 @@ get_ruby_version_digest(void)
395
401
  * be stored.
396
402
  *
397
403
  * The path will look something like: <cachedir>/12/34567890abcdef
404
+ *
405
+ * `path_v` must already have been through FilePathValue(): the callers read it
406
+ * with RSTRING_PTR() too, so the conversion has to happen in the frame that
407
+ * owns the variable.
398
408
  */
399
409
  static void
400
410
  bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE])
401
411
  {
402
- FilePathValue(path_v);
403
-
404
412
  Check_Type(cachedir_v, T_STRING);
405
413
  Check_Type(path_v, T_STRING);
406
414
 
@@ -454,11 +462,34 @@ static int cache_key_equal_slow_path(struct bs_cache_key *current_key,
454
462
  return current_key->digest == cached_key->digest;
455
463
  }
456
464
 
465
+ static ssize_t
466
+ resumable_write(int fd, const void *buf, size_t count)
467
+ {
468
+ size_t total_written = 0;
469
+ const char *ptr = buf;
470
+
471
+ while (count > 0) {
472
+ ssize_t res = write(fd, ptr, count);
473
+
474
+ if (res < 0) {
475
+ if (errno == EINTR) {
476
+ continue; // Interrupted by signal, try again
477
+ }
478
+ return -1; // Real error
479
+ }
480
+
481
+ ptr += res;
482
+ count -= res;
483
+ total_written += res;
484
+ }
485
+ return total_written;
486
+ }
487
+
457
488
  static int update_cache_key(struct bs_cache_key *current_key, struct bs_cache_key *old_key, int cache_fd, const char ** errno_provenance)
458
489
  {
459
490
  old_key->mtime = current_key->mtime;
460
491
  lseek(cache_fd, 0, SEEK_SET);
461
- ssize_t nwrite = write(cache_fd, old_key, KEY_SIZE);
492
+ ssize_t nwrite = resumable_write(cache_fd, old_key, KEY_SIZE);
462
493
  if (nwrite < 0) {
463
494
  *errno_provenance = "update_cache_key:write";
464
495
  return -1;
@@ -492,6 +523,8 @@ static void bs_cache_key_digest(struct bs_cache_key *key,
492
523
  static VALUE
493
524
  bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args)
494
525
  {
526
+ FilePathValue(path_v);
527
+
495
528
  char cache_path[MAX_CACHEPATH_SIZE];
496
529
 
497
530
  /* generate cache path to cache_path */
@@ -508,6 +541,8 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE
508
541
  static VALUE
509
542
  bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler)
510
543
  {
544
+ FilePathValue(path_v);
545
+
511
546
  char cache_path[MAX_CACHEPATH_SIZE];
512
547
  /* generate cache path to cache_path */
513
548
  bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
@@ -719,6 +754,17 @@ mkpath(char * file_path, mode_t mode)
719
754
  return 0;
720
755
  }
721
756
 
757
+ static int
758
+ bs_fchmod(int fd, const char *path, mode_t mode)
759
+ {
760
+ #ifdef _WIN32
761
+ setmode(fd, O_BINARY);
762
+ return chmod(path, mode);
763
+ #else
764
+ return fchmod(fd, mode);
765
+ #endif
766
+ }
767
+
722
768
  /*
723
769
  * Write a cache header/key and a compiled artifact to a given cache path by
724
770
  * writing to a tmpfile and then renaming the tmpfile over top of the final
@@ -732,13 +778,18 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
732
778
  int fd, ret, attempt;
733
779
  ssize_t nwrite;
734
780
 
781
+ uint64_t data_size = RSTRING_LEN(data);
782
+ if (data_size > (uint32_t)-1) {
783
+ return 0; // Don't cache.
784
+ }
785
+
735
786
  for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
736
787
  tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
737
788
  strcat(tmp_path, ".tmp.XXXXXX");
738
789
 
739
790
  // mkstemp modifies the template to be the actual created path
740
791
  fd = mkstemp(tmp_path);
741
- if (fd > 0) break;
792
+ if (fd >= 0) break;
742
793
 
743
794
  if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
744
795
  *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
@@ -750,50 +801,42 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
750
801
  return -1;
751
802
  }
752
803
 
753
- if (chmod(tmp_path, 0644) < 0) {
804
+ if (bs_fchmod(fd, tmp_path, 0644 & ~current_umask) < 0) {
805
+ close(fd);
754
806
  *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
755
807
  return -1;
756
808
  }
757
809
 
758
- #ifdef _WIN32
759
- setmode(fd, O_BINARY);
760
- #endif
761
-
762
- uint64_t data_size = RSTRING_LEN(data);
763
- if (data_size > (uint32_t)-1) {
764
- return 0; // Don't cache.
765
- }
766
-
767
810
  key->data_size = (uint32_t)data_size;
768
- nwrite = write(fd, key, KEY_SIZE);
811
+ nwrite = resumable_write(fd, key, KEY_SIZE);
769
812
  if (nwrite < 0) {
813
+ close(fd);
770
814
  *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
771
815
  return -1;
772
816
  }
773
817
  if (nwrite != KEY_SIZE) {
818
+ close(fd);
774
819
  *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
775
820
  errno = EIO; /* Lies but whatever */
776
821
  return -1;
777
822
  }
778
823
 
779
- nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
824
+ nwrite = resumable_write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
780
825
  if (nwrite < 0) return -1;
781
826
  if (nwrite != RSTRING_LEN(data)) {
827
+ close(fd);
782
828
  *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
783
829
  errno = EIO; /* Lies but whatever */
784
830
  return -1;
785
831
  }
786
832
 
787
833
  close(fd);
834
+
788
835
  ret = rename(tmp_path, path);
789
836
  if (ret < 0) {
790
837
  *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
791
838
  return -1;
792
839
  }
793
- ret = chmod(path, 0664 & ~current_umask);
794
- if (ret < 0) {
795
- *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
796
- }
797
840
  return ret;
798
841
  }
799
842
 
@@ -1099,7 +1142,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
1099
1142
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
1100
1143
 
1101
1144
  /* Read the contents of the source file into a buffer */
1102
- if ((input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
1145
+ if (input_data == Qfalse && (input_data = bs_read_contents(current_fd, current_key.size, &errno_provenance)) == Qfalse) goto fail;
1103
1146
 
1104
1147
  /* Try to compile the input_data using input_to_storage(input_data) */
1105
1148
  exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
@@ -172,11 +172,11 @@ module Bootsnap
172
172
 
173
173
  if RUBY_VERSION < "3.1."
174
174
  def self.coverage_on?
175
- defined?(Coverage) && Coverage.running?
175
+ defined?(Coverage.running?) && Coverage.running?
176
176
  end
177
177
  else
178
178
  def self.coverage_on?
179
- defined?(Coverage) && Coverage.state != :idle
179
+ defined?(Coverage.state) && Coverage.state != :idle
180
180
  end
181
181
  end
182
182
 
@@ -47,14 +47,6 @@ module Bootsnap
47
47
  SUPPORTED_INTERNAL_ENCODINGS.include?(Encoding.default_internal)
48
48
  end
49
49
 
50
- module EncodingAwareSymbols
51
- extend self
52
-
53
- def unpack(payload)
54
- (+payload).force_encoding(Encoding::UTF_8).to_sym
55
- end
56
- end
57
-
58
50
  def init!
59
51
  require "yaml"
60
52
  require "msgpack"
@@ -86,28 +78,78 @@ module Bootsnap
86
78
  0x00,
87
79
  Symbol,
88
80
  packer: :to_msgpack_ext,
89
- unpacker: EncodingAwareSymbols.method(:unpack).to_proc,
81
+ unpacker: :from_msgpack_ext,
82
+ optimized_symbol_parsing: true,
90
83
  )
91
84
 
92
- if defined? MessagePack::Timestamp
93
- factory.register_type(
94
- MessagePack::Timestamp::TYPE, # or just -1
95
- Time,
96
- packer: MessagePack::Time::Packer,
97
- unpacker: MessagePack::Time::Unpacker,
98
- )
85
+ factory.register_type(
86
+ MessagePack::Timestamp::TYPE, # or just -1
87
+ Time,
88
+ packer: MessagePack::Time::Packer,
89
+ unpacker: MessagePack::Time::Unpacker,
90
+ )
99
91
 
100
- marshal_fallback = {
101
- packer: ->(value) { Marshal.dump(value) },
102
- unpacker: ->(payload) { Marshal.load(payload) },
103
- }
104
- {
105
- Date => 0x01,
106
- Regexp => 0x02,
107
- }.each do |type, code|
108
- factory.register_type(code, type, marshal_fallback)
109
- end
110
- end
92
+ factory.register_type(
93
+ 0x01,
94
+ Date,
95
+ packer: lambda { |date, packer|
96
+ packer.write(date.year)
97
+ packer.write(date.month)
98
+ packer.write(date.day)
99
+ },
100
+ unpacker: lambda { |unpacker|
101
+ ::Date.new(unpacker.read, unpacker.read, unpacker.read)
102
+ },
103
+ recursive: true,
104
+ )
105
+
106
+ factory.register_type(
107
+ 0x02,
108
+ Regexp,
109
+ packer: ->(value) { Marshal.dump(value) },
110
+ unpacker: ->(payload) { Marshal.load(payload) },
111
+ )
112
+
113
+ factory.register_type(
114
+ 0x03,
115
+ DateTime,
116
+ packer: lambda { |dt, packer|
117
+ packer.write(dt.year)
118
+ packer.write(dt.month)
119
+ packer.write(dt.day)
120
+ packer.write(dt.hour)
121
+ packer.write(dt.minute)
122
+
123
+ sec = dt.sec + dt.sec_fraction
124
+ packer.write(sec.numerator)
125
+ packer.write(sec.denominator)
126
+
127
+ offset = dt.offset
128
+ packer.write(offset.numerator)
129
+ packer.write(offset.denominator)
130
+ },
131
+ unpacker: lambda { |unpacker|
132
+ ::DateTime.new(
133
+ unpacker.read, # year
134
+ unpacker.read, # month
135
+ unpacker.read, # day
136
+ unpacker.read, # hour
137
+ unpacker.read, # minute
138
+ Rational(unpacker.read, unpacker.read), # sec fraction
139
+ Rational(unpacker.read, unpacker.read), # offset fraction
140
+ )
141
+ },
142
+ recursive: true,
143
+ )
144
+
145
+ require "msgpack/bigint"
146
+ factory.register_type(
147
+ 0x04,
148
+ Integer,
149
+ packer: MessagePack::Bigint.method(:to_msgpack_ext),
150
+ unpacker: MessagePack::Bigint.method(:from_msgpack_ext),
151
+ oversized_integer_extension: true,
152
+ )
111
153
 
112
154
  self.msgpack_factory = factory
113
155
 
@@ -116,7 +158,7 @@ module Bootsnap
116
158
  if params.include?([:key, :symbolize_names])
117
159
  supported_options << :symbolize_names
118
160
  end
119
- if params.include?([:key, :freeze]) && factory.load(factory.dump("yaml"), freeze: true).frozen?
161
+ if params.include?([:key, :freeze])
120
162
  supported_options << :freeze
121
163
  end
122
164
  supported_options.freeze
@@ -135,6 +177,14 @@ module Bootsnap
135
177
 
136
178
  NoTagsVisitor.new(scanner, loader).visit(ast)
137
179
  end
180
+
181
+ def msgpack_unpacker_options(kwargs)
182
+ return kwargs unless kwargs&.key?(:symbolize_names)
183
+
184
+ kwargs = kwargs.dup
185
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
186
+ kwargs
187
+ end
138
188
  end
139
189
 
140
190
  module Psych4
@@ -164,11 +214,9 @@ module Bootsnap
164
214
  end
165
215
 
166
216
  def storage_to_output(data, kwargs)
167
- if kwargs&.key?(:symbolize_names)
168
- kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
169
- end
170
-
171
- unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
217
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(
218
+ CompileCache::YAML.msgpack_unpacker_options(kwargs),
219
+ )
172
220
  unpacker.feed(data)
173
221
  _safe_loaded = unpacker.unpack
174
222
  result = unpacker.unpack
@@ -202,11 +250,9 @@ module Bootsnap
202
250
  end
203
251
 
204
252
  def storage_to_output(data, kwargs)
205
- if kwargs&.key?(:symbolize_names)
206
- kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
207
- end
208
-
209
- unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
253
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(
254
+ CompileCache::YAML.msgpack_unpacker_options(kwargs),
255
+ )
210
256
  unpacker.feed(data)
211
257
  safe_loaded = unpacker.unpack
212
258
  if safe_loaded
@@ -282,10 +328,9 @@ module Bootsnap
282
328
  end
283
329
 
284
330
  def storage_to_output(data, kwargs)
285
- if kwargs&.key?(:symbolize_names)
286
- kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
287
- end
288
- unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
331
+ unpacker = CompileCache::YAML.msgpack_factory.unpacker(
332
+ CompileCache::YAML.msgpack_unpacker_options(kwargs),
333
+ )
289
334
  unpacker.feed(data)
290
335
  _safe_loaded = unpacker.unpack
291
336
  unpacker.unpack
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.24.6"
4
+ VERSION = "1.26.0"
5
5
  end
data/lib/bootsnap.rb CHANGED
@@ -6,6 +6,9 @@ require_relative "bootsnap/bundler"
6
6
  module Bootsnap
7
7
  InvalidConfiguration = Class.new(StandardError)
8
8
 
9
+ # JITs shouldn't change the cache key as they have no impact on iseq generation
10
+ RUBY_CACHE_KEY = RUBY_DESCRIPTION.gsub(/\s\+\wJIT/, "").freeze
11
+
9
12
  class << self
10
13
  attr_reader :cache_dir, :logger
11
14
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.24.6
4
+ version: 1.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1.2'
18
+ version: '1.5'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '1.2'
25
+ version: '1.5'
26
26
  description: Boot large ruby/rails apps faster
27
27
  email:
28
28
  - burke.libbey@shopify.com
@@ -81,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  requirements: []
84
- rubygems_version: 3.6.9
84
+ rubygems_version: 4.0.16
85
85
  specification_version: 4
86
86
  summary: Boot large ruby/rails apps faster
87
87
  test_files: []