bootsnap 1.7.1 → 1.8.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: b8814f0b4b0ea307767f1a2c10ba813cdf2c28748b2175015d73c52f7754574c
4
- data.tar.gz: 32958adc9d09fc2cff2764efd545e6c3c670a7ca453894f34c2dfae48f7b4467
3
+ metadata.gz: a12930656b479ba9d0675f13bf1edd2a52e0d84314f1e528866110a1a36776df
4
+ data.tar.gz: 33f4c8ea77733e461c9fe95ba75110e9da0b707e818727ebf5c13605d58eec62
5
5
  SHA512:
6
- metadata.gz: 0b8dc0aba09934dc35d6011142b8edcb63043219770993ee9db6117124157001d3f62bc5dd72bad8a9cd9538e90e36f85884262378deea054a38e461b670e211
7
- data.tar.gz: 1e5f2337755ac7f7f52dc155612d2c76c3b17828f2c775e4dedc606cae3d280a16232a9ae1dbee00987cb5846d64277e37079589c96e3d264aca615318d5ce1e
6
+ metadata.gz: c366c8c65f4ce3fd73979b68c1201c2d7872d059a71ac79a561bc464b7fdaf1d0ba00acbe59e491d25d66efe967b3c0a25a9542727a43c101c4d2b719b82407c
7
+ data.tar.gz: 39d2ec6734c6234114c27f8665efe8a6170e6bcc58f98cec25f1c4386a644d0eec135ababc66b771dc0744c7df961f63d95669ebdb08db14c3b4982b320b5224
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.8.0
4
+
5
+ * Improve support for Pysch 4. (#368)
6
+
7
+ # 1.7.7
8
+
9
+ * Fix `require_relative` in evaled code on latest ruby 3.1.0-dev. (#366)
10
+
11
+ # 1.7.6
12
+
13
+ * Fix reliance on `set` to be required.
14
+ * Fix `Encoding::UndefinedConversionError` error for Rails applications when precompiling cache. (#364)
15
+
16
+ # 1.7.5
17
+
18
+ * Handle a regression of Ruby 2.7.3 causing Bootsnap to call the deprecated `untaint` method. (#360)
19
+ * Gracefully handle read-only file system as well as other errors preventing to persist the load path cache. (#358)
20
+
21
+ # 1.7.4
22
+
23
+ * Stop raising errors when encoutering various file system errors. The cache is now best effort,
24
+ if somehow it can't be saved, bootsnapp will gracefully fallback to the original operation (e.g. `Kernel.require`).
25
+ (#353, #177, #262)
26
+
27
+ # 1.7.3
28
+
29
+ * Disable YAML precompilation when encountering YAML tags. (#351)
30
+
31
+ # 1.7.2
32
+
33
+ * Fix compatibility with msgpack < 1. (#349)
34
+
35
+ # 1.7.1
36
+
3
37
  * Warn Ruby 2.5 users if they turn ISeq caching on. (#327, #244)
4
38
  * Disable ISeq caching for the whole 2.5.x series again.
5
39
  * Better handle hashing of Ruby strings. (#318)
@@ -464,8 +464,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
464
464
  fd = open(path, O_RDONLY);
465
465
  if (fd < 0) {
466
466
  *errno_provenance = "bs_fetch:open_cache_file:open";
467
- if (errno == ENOENT) return CACHE_MISS;
468
- return ERROR_WITH_ERRNO;
467
+ return CACHE_MISS;
469
468
  }
470
469
  #ifdef _WIN32
471
470
  setmode(fd, O_BINARY);
@@ -763,9 +762,11 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
763
762
  /* If storage_data isn't a string, we can't cache it */
764
763
  if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
765
764
 
766
- /* Write the cache key and storage_data to the cache directory */
767
- res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
768
- if (res < 0) goto fail_errno;
765
+ /* Attempt to write the cache key and storage_data to the cache directory.
766
+ * We do however ignore any failures to persist the cache, as it's better
767
+ * to move along, than to interrupt the process.
768
+ */
769
+ atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
769
770
 
770
771
  /* Having written the cache, now convert storage_data to output_data */
771
772
  exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
@@ -37,7 +37,11 @@ module Bootsnap
37
37
 
38
38
  def initialize(jobs)
39
39
  @jobs = jobs
40
- @pipe_out, @to_io = IO.pipe
40
+ @pipe_out, @to_io = IO.pipe(binmode: true)
41
+ # Set the writer encoding to binary since IO.pipe only sets it for the reader.
42
+ # https://github.com/rails/rails/issues/16514#issuecomment-52313290
43
+ @to_io.set_encoding(Encoding::BINARY)
44
+
41
45
  @pid = nil
42
46
  end
43
47
 
@@ -8,8 +8,7 @@ module Bootsnap
8
8
  attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
9
9
 
10
10
  def input_to_storage(contents, _)
11
- raise(Uncompilable) if contents.index("!ruby/object")
12
- obj = ::YAML.load(contents)
11
+ obj = strict_load(contents)
13
12
  msgpack_factory.dump(obj)
14
13
  rescue NoMethodError, RangeError
15
14
  # The object included things that we can't serialize
@@ -24,9 +23,16 @@ module Bootsnap
24
23
  end
25
24
 
26
25
  def input_to_output(data, kwargs)
27
- ::YAML.load(data, **(kwargs || {}))
26
+ ::YAML.unsafe_load(data, **(kwargs || {}))
28
27
  end
29
28
 
29
+ def strict_load(payload, *args)
30
+ ast = ::YAML.parse(payload)
31
+ return ast unless ast
32
+ strict_visitor.create(*args).visit(ast)
33
+ end
34
+ ruby2_keywords :strict_load if respond_to?(:ruby2_keywords, true)
35
+
30
36
  def precompile(path, cache_dir: YAML.cache_dir)
31
37
  Bootsnap::CompileCache::Native.precompile(
32
38
  cache_dir,
@@ -46,27 +52,37 @@ module Bootsnap
46
52
  require('msgpack')
47
53
  require('date')
48
54
 
55
+ if Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
56
+ Patch.send(:remove_method, :unsafe_load_file)
57
+ end
58
+ if Patch.method_defined?(:load_file) && ::YAML::VERSION >= '4'
59
+ Patch.send(:remove_method, :load_file)
60
+ end
61
+
49
62
  # MessagePack serializes symbols as strings by default.
50
63
  # We want them to roundtrip cleanly, so we use a custom factory.
51
64
  # see: https://github.com/msgpack/msgpack-ruby/pull/122
52
65
  factory = MessagePack::Factory.new
53
66
  factory.register_type(0x00, Symbol)
54
- factory.register_type(
55
- MessagePack::Timestamp::TYPE, # or just -1
56
- Time,
57
- packer: MessagePack::Time::Packer,
58
- unpacker: MessagePack::Time::Unpacker
59
- )
60
67
 
61
- marshal_fallback = {
62
- packer: ->(value) { Marshal.dump(value) },
63
- unpacker: ->(payload) { Marshal.load(payload) },
64
- }
65
- {
66
- Date => 0x01,
67
- Regexp => 0x02,
68
- }.each do |type, code|
69
- factory.register_type(code, type, marshal_fallback)
68
+ if defined? MessagePack::Timestamp
69
+ factory.register_type(
70
+ MessagePack::Timestamp::TYPE, # or just -1
71
+ Time,
72
+ packer: MessagePack::Time::Packer,
73
+ unpacker: MessagePack::Time::Unpacker
74
+ )
75
+
76
+ marshal_fallback = {
77
+ packer: ->(value) { Marshal.dump(value) },
78
+ unpacker: ->(payload) { Marshal.load(payload) },
79
+ }
80
+ {
81
+ Date => 0x01,
82
+ Regexp => 0x02,
83
+ }.each do |type, code|
84
+ factory.register_type(code, type, marshal_fallback)
85
+ end
70
86
  end
71
87
 
72
88
  self.msgpack_factory = factory
@@ -83,6 +99,17 @@ module Bootsnap
83
99
  end
84
100
  self.supported_options.freeze
85
101
  end
102
+
103
+ def strict_visitor
104
+ self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
105
+ def visit(target)
106
+ if target.tag
107
+ raise Uncompilable, "YAML tags are not supported: #{target.tag}"
108
+ end
109
+ super
110
+ end
111
+ end
112
+ end
86
113
  end
87
114
 
88
115
  module Patch
@@ -106,6 +133,27 @@ module Bootsnap
106
133
  end
107
134
 
108
135
  ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
136
+
137
+ def unsafe_load_file(path, *args)
138
+ return super if args.size > 1
139
+ if kwargs = args.first
140
+ return super unless kwargs.is_a?(Hash)
141
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
142
+ end
143
+
144
+ begin
145
+ ::Bootsnap::CompileCache::Native.fetch(
146
+ Bootsnap::CompileCache::YAML.cache_dir,
147
+ File.realpath(path),
148
+ ::Bootsnap::CompileCache::YAML,
149
+ kwargs,
150
+ )
151
+ rescue Errno::EACCES
152
+ ::Bootsnap::CompileCache.permission_error(path)
153
+ end
154
+ end
155
+
156
+ ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
109
157
  end
110
158
  end
111
159
  end
@@ -10,8 +10,8 @@ module Bootsnap
10
10
  def initialize(store, path_obj, development_mode: false)
11
11
  @development_mode = development_mode
12
12
  @store = store
13
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
14
- @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
+ @mutex = Mutex.new
14
+ @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
15
15
  @has_relative_paths = nil
16
16
  reinitialize
17
17
  end
@@ -178,25 +178,42 @@ module Bootsnap
178
178
 
179
179
  if DLEXT2
180
180
  def search_index(f)
181
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
181
+ try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
182
182
  end
183
183
 
184
184
  def maybe_append_extension(f)
185
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
185
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
186
186
  end
187
187
  else
188
188
  def search_index(f)
189
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
189
+ try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
190
190
  end
191
191
 
192
192
  def maybe_append_extension(f)
193
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
193
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
194
194
  end
195
195
  end
196
196
 
197
- def try_index(f)
198
- if (p = @index[f])
199
- "#{p}/#{f}"
197
+ s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
198
+ if s.respond_to?(:-@)
199
+ if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= '2.7'
200
+ def try_index(f)
201
+ if (p = @index[f])
202
+ -(File.join(p, f).freeze)
203
+ end
204
+ end
205
+ else
206
+ def try_index(f)
207
+ if (p = @index[f])
208
+ -File.join(p, f).untaint
209
+ end
210
+ end
211
+ end
212
+ else
213
+ def try_index(f)
214
+ if (p = @index[f])
215
+ File.join(p, f)
216
+ end
200
217
  end
201
218
  end
202
219
 
@@ -15,11 +15,13 @@ module Bootsnap
15
15
  @lpc_observer.push_paths(self, *entries.map(&:to_s))
16
16
  super
17
17
  end
18
+ alias_method :append, :push
18
19
 
19
20
  def unshift(*entries)
20
21
  @lpc_observer.unshift_paths(self, *entries.map(&:to_s))
21
22
  super
22
23
  end
24
+ alias_method :prepend, :unshift
23
25
 
24
26
  def concat(entries)
25
27
  @lpc_observer.push_paths(self, *entries.map(&:to_s))
@@ -47,8 +47,9 @@ module Kernel
47
47
 
48
48
  alias_method(:require_relative_without_bootsnap, :require_relative)
49
49
  def require_relative(path)
50
+ location = caller_locations(1..1).first
50
51
  realpath = Bootsnap::LoadPathCache.realpath_cache.call(
51
- caller_locations(1..1).first.absolute_path, path
52
+ location.absolute_path || location.path, path
52
53
  )
53
54
  require(realpath)
54
55
  end
@@ -26,7 +26,7 @@ module Bootsnap
26
26
  class LoadedFeaturesIndex
27
27
  def initialize
28
28
  @lfi = {}
29
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
29
+ @mutex = Mutex.new
30
30
 
31
31
  # In theory the user could mutate $LOADED_FEATURES and invalidate our
32
32
  # cache. If this ever comes up in practice — or if you, the
@@ -58,9 +58,9 @@ module Bootsnap
58
58
  end
59
59
 
60
60
  def purge_multi(features)
61
- rejected_hashes = features.map(&:hash).to_set
61
+ rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
62
62
  @mutex.synchronize do
63
- @lfi.reject! { |_, hash| rejected_hashes.include?(hash) }
63
+ @lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
64
64
  end
65
65
  end
66
66
 
@@ -33,10 +33,10 @@ module Bootsnap
33
33
  requirables = []
34
34
  walk(path, nil) do |relative_path, absolute_path, is_directory|
35
35
  if is_directory
36
- dirs << relative_path
36
+ dirs << os_path(relative_path)
37
37
  !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
38
  elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
39
- requirables << relative_path
39
+ requirables << os_path(relative_path)
40
40
  end
41
41
  end
42
42
  [requirables, dirs]
@@ -45,7 +45,7 @@ module Bootsnap
45
45
  def walk(absolute_dir_path, relative_dir_path, &block)
46
46
  Dir.foreach(absolute_dir_path) do |name|
47
47
  next if name.start_with?('.')
48
- relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
48
+ relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
49
49
 
50
50
  absolute_path = "#{absolute_dir_path}/#{name}"
51
51
  if File.directory?(absolute_path)
@@ -57,6 +57,17 @@ module Bootsnap
57
57
  end
58
58
  end
59
59
  end
60
+
61
+ if RUBY_VERSION >= '3.1'
62
+ def os_path(path)
63
+ path.freeze
64
+ end
65
+ else
66
+ def os_path(path)
67
+ path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
68
+ path.freeze
69
+ end
70
+ end
60
71
  end
61
72
  end
62
73
  end
@@ -12,8 +12,7 @@ module Bootsnap
12
12
 
13
13
  def initialize(store_path)
14
14
  @store_path = store_path
15
- # TODO: Remove conditional once Ruby 2.2 support is dropped.
16
- @txn_mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new
15
+ @txn_mutex = Mutex.new
17
16
  @dirty = false
18
17
  load_data
19
18
  end
@@ -63,12 +62,18 @@ module Bootsnap
63
62
 
64
63
  def load_data
65
64
  @data = begin
66
- MessagePack.load(File.binread(@store_path))
67
- # handle malformed data due to upgrade incompatibility
68
- rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
69
- {}
70
- rescue ArgumentError => e
71
- e.message =~ /negative array size/ ? {} : raise
65
+ File.open(@store_path, encoding: Encoding::BINARY) do |io|
66
+ MessagePack.load(io)
67
+ end
68
+ # handle malformed data due to upgrade incompatibility
69
+ rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
70
+ {}
71
+ rescue ArgumentError => error
72
+ if error.message =~ /negative array size/
73
+ {}
74
+ else
75
+ raise
76
+ end
72
77
  end
73
78
  end
74
79
 
@@ -80,10 +85,13 @@ module Bootsnap
80
85
  exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
81
86
  # `encoding:` looks redundant wrt `binwrite`, but necessary on windows
82
87
  # because binary is part of mode.
83
- File.binwrite(tmp, MessagePack.dump(@data), mode: exclusive_write, encoding: Encoding::BINARY)
88
+ File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
89
+ MessagePack.dump(@data, io, freeze: true)
90
+ end
84
91
  FileUtils.mv(tmp, @store_path)
85
92
  rescue Errno::EEXIST
86
93
  retry
94
+ rescue SystemCallError
87
95
  end
88
96
  end
89
97
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Bootsnap
3
- VERSION = "1.7.1"
3
+ VERSION = "1.8.0"
4
4
  end
data/lib/bootsnap.rb CHANGED
@@ -27,7 +27,9 @@ module Bootsnap
27
27
 
28
28
  def self.instrumentation=(callback)
29
29
  @instrumentation = callback
30
- self.instrumentation_enabled = !!callback
30
+ if respond_to?(:instrumentation_enabled=, true)
31
+ self.instrumentation_enabled = !!callback
32
+ end
31
33
  end
32
34
 
33
35
  def self._instrument(event, path)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-04 00:00:00.000000000 Z
11
+ date: 2021-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -42,14 +42,14 @@ dependencies:
42
42
  name: rake-compiler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
155
  requirements: []
156
- rubygems_version: 3.0.3
156
+ rubygems_version: 3.2.20
157
157
  signing_key:
158
158
  specification_version: 4
159
159
  summary: Boot large ruby/rails apps faster