bootsnap 1.18.3 → 1.18.5

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: 4fa4ab785277ee01a1c8ee75b43f0efb93db42bffcdacc1c8505a65efa03dede
4
- data.tar.gz: 8aaaca48ae257b563580023c8fa36a59463f4c30f5463c14f6b8b94bf5fe27df
3
+ metadata.gz: 49e48977ddbf373c70db5988627366223140c8718fcf36cc4dd55d82a20716d4
4
+ data.tar.gz: 23b7644313a2c0d2df0a2b6c3816a1634a2f3f94f8c12388c6b674608b92aa82
5
5
  SHA512:
6
- metadata.gz: 27b48d27d3330c8565952a2fbb979e71013b1e9585bcb3284656192808c304f2874c32a135b14895eec61a7ef038fa71fa111964a56e7aaedc9ff507ef307686
7
- data.tar.gz: c3d83a0b068f2908a6298c7cd8e1a660f1228a7ddbfb9409cb3f6c174319f3974388ce73756c04062b17b97a37e199a07bccbb0b8dc1c6224998c58c51194b27
6
+ metadata.gz: e50126e1a8a1cfa22cee67fc04efd056a2f792a0aee08cbd73ff0a7e5cec0e8f2902b0ddc08a89a3d2a12c119bf042290acd0ec28642c5c6acc86adaa317ef31
7
+ data.tar.gz: cc8c691e916b5df79c073d0285154df4c3483c09b6fe085d2bde5ff11e1e710a01b68d86db3bbfd253b3cce95b278720e47f94e8989941c899bb80d4a3e7151e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.18.5
4
+
5
+ * Attempt to detect a QEMU bug that can cause `bootsnap precompile` to hang forever when building ARM64 docker images
6
+ from x86_64 machines. See #495.
7
+ * Improve CLI to detect cgroup CPU limits and avoid spawning too many worker processes.
8
+
9
+ # 1.18.4
10
+
11
+ * Allow using bootsnap without bundler. See #488.
12
+ * Fix startup failure if the cache directory points to a broken symlink.
13
+
3
14
  # 1.18.3
4
15
 
5
16
  * Fix the cache corruption issue in the revalidation feature. See #474.
@@ -32,7 +43,7 @@
32
43
 
33
44
  # 1.17.0
34
45
 
35
- * Ensure `$LOAD_PATH.dup` is Ractor shareable to fix an conflict with `did_you_mean`.
46
+ * Ensure `$LOAD_PATH.dup` is Ractor shareable to fix a conflict with `did_you_mean`.
36
47
  * Allow to ignore directories using absolute paths.
37
48
  * Support YAML and JSON CompileCache on TruffleRuby.
38
49
  * Support LoadPathCache on TruffleRuby.
data/README.md CHANGED
@@ -188,7 +188,7 @@ result too, raising a `LoadError` without touching the filesystem at all.
188
188
 
189
189
  Ruby has complex grammar and parsing it is not a particularly cheap operation. Since 1.9, Ruby has
190
190
  translated ruby source to an internal bytecode format, which is then executed by the Ruby VM. Since
191
- 2.3.0, Ruby [exposes an API](https://ruby-doc.org/core-2.3.0/RubyVM/InstructionSequence.html) that
191
+ 2.3.0, Ruby [exposes an API](https://docs.ruby-lang.org/en/master/RubyVM/InstructionSequence.html) that
192
192
  allows caching that bytecode. This allows us to bypass the relatively-expensive compilation step on
193
193
  subsequent loads of the same file.
194
194
 
@@ -331,7 +331,7 @@ To do so you can use the `bootsnap precompile` command.
331
331
  Example:
332
332
 
333
333
  ```bash
334
- $ bundle exec bootsnap precompile --gemfile app/ lib/
334
+ $ bundle exec bootsnap precompile --gemfile app/ lib/ config/
335
335
  ```
336
336
 
337
337
  ## When not to use Bootsnap
@@ -82,7 +82,7 @@ struct bs_cache_key {
82
82
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
83
83
 
84
84
  /* Effectively a schema version. Bumping invalidates all previous caches */
85
- static const uint32_t current_version = 5;
85
+ static const uint32_t current_version = 6;
86
86
 
87
87
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
88
88
  * new OS ABI, etc. */
@@ -146,15 +146,6 @@ struct s2o_data;
146
146
  struct i2o_data;
147
147
  struct i2s_data;
148
148
 
149
- /* https://bugs.ruby-lang.org/issues/13667 */
150
- extern VALUE rb_get_coverages(void);
151
- static VALUE
152
- bs_rb_coverage_running(VALUE self)
153
- {
154
- VALUE cov = rb_get_coverages();
155
- return RTEST(cov) ? Qtrue : Qfalse;
156
- }
157
-
158
149
  static VALUE
159
150
  bs_rb_get_path(VALUE self, VALUE fname)
160
151
  {
@@ -193,7 +184,6 @@ Init_bootsnap(void)
193
184
  rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
194
185
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
195
186
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
196
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
197
187
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
198
188
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
199
189
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
@@ -922,9 +912,9 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
922
912
  goto succeed; /* output_data is now the correct return. */
923
913
 
924
914
  #define CLEANUP \
925
- if (status != Qfalse) bs_instrumentation(status, path_v); \
926
915
  if (current_fd >= 0) close(current_fd); \
927
- if (cache_fd >= 0) close(cache_fd);
916
+ if (cache_fd >= 0) close(cache_fd); \
917
+ if (status != Qfalse) bs_instrumentation(status, path_v);
928
918
 
929
919
  succeed:
930
920
  CLEANUP;
@@ -1,16 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "etc"
4
+ require "rbconfig"
5
+ require "io/wait" unless IO.method_defined?(:wait_readable)
6
+
3
7
  module Bootsnap
4
8
  class CLI
5
9
  class WorkerPool
6
10
  class << self
7
11
  def create(size:, jobs:)
12
+ size ||= default_size
8
13
  if size > 0 && Process.respond_to?(:fork)
9
14
  new(size: size, jobs: jobs)
10
15
  else
11
16
  Inline.new(jobs: jobs)
12
17
  end
13
18
  end
19
+
20
+ def default_size
21
+ nprocessors = Etc.nprocessors
22
+ size = [nprocessors, cpu_quota || nprocessors].min
23
+ case size
24
+ when 0, 1
25
+ 0
26
+ else
27
+ if fork_defunct?
28
+ $stderr.puts "warning: faulty fork(2) detected, probably in cross platform docker builds. " \
29
+ "Disabling parallel compilation."
30
+ 0
31
+ else
32
+ size
33
+ end
34
+ end
35
+ end
36
+
37
+ def cpu_quota
38
+ if RbConfig::CONFIG["target_os"].include?("linux")
39
+ if File.exist?("/sys/fs/cgroup/cpu.max")
40
+ # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
41
+ cpu_max = File.read("/sys/fs/cgroup/cpu.max")
42
+ return nil if cpu_max.start_with?("max ") # no limit
43
+
44
+ max, period = cpu_max.split.map(&:to_f)
45
+ max / period
46
+ elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
47
+ # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
48
+ max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
49
+ # If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions
50
+ # https://docs.kernel.org/scheduler/sched-bwc.html#management
51
+ return nil if max <= 0
52
+
53
+ period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f
54
+ max / period
55
+ end
56
+ end
57
+ end
58
+
59
+ def fork_defunct?
60
+ return true unless ::Process.respond_to?(:fork)
61
+
62
+ # Ref: https://github.com/Shopify/bootsnap/issues/495
63
+ # The second forked process will hang on some QEMU environments
64
+ r, w = IO.pipe
65
+ pids = 2.times.map do
66
+ ::Process.fork do
67
+ exit!(true)
68
+ end
69
+ end
70
+ w.close
71
+ r.wait_readable(1) # Wait at most 1s
72
+
73
+ defunct = false
74
+
75
+ pids.each do |pid|
76
+ _pid, status = ::Process.wait2(pid, ::Process::WNOHANG)
77
+ if status.nil? # Didn't exit in 1s
78
+ defunct = true
79
+ Process.kill(:KILL, pid)
80
+ ::Process.wait2(pid)
81
+ end
82
+ end
83
+
84
+ defunct
85
+ end
14
86
  end
15
87
 
16
88
  class Inline
data/lib/bootsnap/cli.rb CHANGED
@@ -4,7 +4,6 @@ require "bootsnap"
4
4
  require "bootsnap/cli/worker_pool"
5
5
  require "optparse"
6
6
  require "fileutils"
7
- require "etc"
8
7
 
9
8
  module Bootsnap
10
9
  class CLI
@@ -29,23 +28,23 @@ module Bootsnap
29
28
  self.compile_gemfile = false
30
29
  self.exclude = nil
31
30
  self.verbose = false
32
- self.jobs = Etc.nprocessors
31
+ self.jobs = nil
33
32
  self.iseq = true
34
33
  self.yaml = true
35
34
  self.json = true
36
35
  end
37
36
 
38
37
  def precompile_command(*sources)
39
- require "bootsnap/compile_cache/iseq"
40
- require "bootsnap/compile_cache/yaml"
41
- require "bootsnap/compile_cache/json"
38
+ require "bootsnap/compile_cache"
42
39
 
43
40
  fix_default_encoding do
44
- Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
45
- Bootsnap::CompileCache::YAML.init!
46
- Bootsnap::CompileCache::YAML.cache_dir = cache_dir
47
- Bootsnap::CompileCache::JSON.init!
48
- Bootsnap::CompileCache::JSON.cache_dir = cache_dir
41
+ Bootsnap::CompileCache.setup(
42
+ cache_dir: cache_dir,
43
+ iseq: iseq,
44
+ yaml: yaml,
45
+ json: json,
46
+ revalidation: true,
47
+ )
49
48
 
50
49
  @work_pool = WorkerPool.create(size: jobs, jobs: {
51
50
  ruby: method(:precompile_ruby),
@@ -222,6 +221,9 @@ module Bootsnap
222
221
 
223
222
  def parser
224
223
  @parser ||= OptionParser.new do |opts|
224
+ opts.version = Bootsnap::VERSION
225
+ opts.program_name = "bootsnap"
226
+
225
227
  opts.banner = "Usage: bootsnap COMMAND [ARGS]"
226
228
  opts.separator ""
227
229
  opts.separator "GLOBAL OPTIONS"
@@ -84,7 +84,7 @@ module Bootsnap
84
84
  module InstructionSequenceMixin
85
85
  def load_iseq(path)
86
86
  # Having coverage enabled prevents iseq dumping/loading.
87
- return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
87
+ return nil if defined?(Coverage) && Coverage.running?
88
88
 
89
89
  Bootsnap::CompileCache::ISeq.fetch(path.to_s)
90
90
  rescue RuntimeError => error
@@ -25,6 +25,11 @@ module Bootsnap
25
25
  # This is useful before bootsnap is fully-initialized to load gems that it
26
26
  # depends on, without forcing full LOAD_PATH traversals.
27
27
  def self.with_gems(*gems)
28
+ # Ensure the gems are activated (their paths are in $LOAD_PATH)
29
+ gems.each do |gem_name|
30
+ gem gem_name
31
+ end
32
+
28
33
  orig = $LOAD_PATH.dup
29
34
  $LOAD_PATH.clear
30
35
  gems.each do |gem|
@@ -142,7 +142,7 @@ module Bootsnap
142
142
  # will _never_ run on MacOS, and therefore think they can get away
143
143
  # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
144
144
  #
145
- # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
145
+ # See <https://docs.ruby-lang.org/en/master/Kernel.html#method-i-require>.
146
146
  def extension_elidable?(feature)
147
147
  feature.to_s.end_with?(".rb", ".so", ".o", ".dll", ".dylib")
148
148
  end
@@ -122,6 +122,8 @@ module Bootsnap
122
122
  stack.reverse_each do |dir|
123
123
  Dir.mkdir(dir)
124
124
  rescue SystemCallError
125
+ # Check for broken symlinks. Calling File.realpath will raise Errno::ENOENT if that is the case
126
+ File.realpath(dir) if File.symlink?(dir)
125
127
  raise unless File.directory?(dir)
126
128
  end
127
129
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.18.3"
4
+ VERSION = "1.18.5"
5
5
  end
data/lib/bootsnap.rb CHANGED
@@ -83,7 +83,7 @@ module Bootsnap
83
83
  env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["ENV"]
84
84
  development_mode = ["", nil, "development"].include?(env)
85
85
 
86
- unless ENV["DISABLE_BOOTSNAP"]
86
+ if enabled?("BOOTSNAP")
87
87
  cache_dir = ENV["BOOTSNAP_CACHE_DIR"]
88
88
  unless cache_dir
89
89
  config_dir_frame = caller.detect do |line|
@@ -112,11 +112,12 @@ module Bootsnap
112
112
  setup(
113
113
  cache_dir: cache_dir,
114
114
  development_mode: development_mode,
115
- load_path_cache: !ENV["DISABLE_BOOTSNAP_LOAD_PATH_CACHE"],
116
- compile_cache_iseq: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
117
- compile_cache_yaml: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
118
- compile_cache_json: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
119
- readonly: !!ENV["BOOTSNAP_READONLY"],
115
+ load_path_cache: enabled?("BOOTSNAP_LOAD_PATH_CACHE"),
116
+ compile_cache_iseq: enabled?("BOOTSNAP_COMPILE_CACHE"),
117
+ compile_cache_yaml: enabled?("BOOTSNAP_COMPILE_CACHE"),
118
+ compile_cache_json: enabled?("BOOTSNAP_COMPILE_CACHE"),
119
+ readonly: bool_env("BOOTSNAP_READONLY"),
120
+ revalidation: bool_env("BOOTSNAP_REVALIDATE"),
120
121
  ignore_directories: ignore_directories,
121
122
  )
122
123
 
@@ -148,5 +149,16 @@ module Bootsnap
148
149
 
149
150
  # Allow the C extension to redefine `rb_get_path` without warning.
150
151
  alias_method :rb_get_path, :rb_get_path # rubocop:disable Lint/DuplicateMethods
152
+
153
+ private
154
+
155
+ def enabled?(key)
156
+ !ENV["DISABLE_#{key}"]
157
+ end
158
+
159
+ def bool_env(key, default: false)
160
+ value = ENV.fetch(key) { default }
161
+ !["0", "false", false].include?(value)
162
+ end
151
163
  end
152
164
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.18.3
4
+ version: 1.18.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-01-31 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: msgpack
@@ -68,7 +67,6 @@ metadata:
68
67
  changelog_uri: https://github.com/Shopify/bootsnap/blob/main/CHANGELOG.md
69
68
  source_code_uri: https://github.com/Shopify/bootsnap
70
69
  allowed_push_host: https://rubygems.org
71
- post_install_message:
72
70
  rdoc_options: []
73
71
  require_paths:
74
72
  - lib
@@ -83,8 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
81
  - !ruby/object:Gem::Version
84
82
  version: '0'
85
83
  requirements: []
86
- rubygems_version: 3.5.5
87
- signing_key:
84
+ rubygems_version: 3.6.8
88
85
  specification_version: 4
89
86
  summary: Boot large ruby/rails apps faster
90
87
  test_files: []