bootsnap 1.4.6 → 1.4.8

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: 2d4f38db9a609c2adb0a0ede991bd993dff7ae59885cb1722eb699658211fd96
4
- data.tar.gz: 9f363c21a154e123693f18e48073451c6cfe6c05ec378c980e6ef770f01e658c
3
+ metadata.gz: bdadfdd9d316f3362a593314fc596293395f5cfdb2f4ea0985f8fc77c54a4c0e
4
+ data.tar.gz: 88e75f2440f3f7fdd3785437e9e3e4494e5bbe4c9e95437f81fde4ea4845b884
5
5
  SHA512:
6
- metadata.gz: 925f595e21911c61ff7cf3a86cb055d25e56bb65a8a6437c513f25bfea3aec6c086259808b5aed3289e5d82f66706f98314f31d2ff3886d85edeb47085d6a918
7
- data.tar.gz: 31507ba8393d47361f8332064a9a39220392a4242e1f3f3c3a88c4de032b51eb8aab8d3769869fec7f9da55400ce773c42aecc52604d5293c9ff7ea3f9f40e54
6
+ metadata.gz: 31d24bdddce9bdb204f7fa346c7089bccac4b11081b8f172515883fc75fc77c1a2becab479753d7ebda52050ee3bd2790c54b3e32fd6100b07d09fa849d07134
7
+ data.tar.gz: 180abf489969e8629dec58d631553287f8f6546efcadb87f9e5caaf286bfc2e9d5c64f90f617e2626640862d5a31f347e1b69d9a225f9b3fdcc279bdb9cf7d66
@@ -1,3 +1,26 @@
1
+ # 1.4.8
2
+
3
+ * [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
4
+
5
+ # 1.4.7
6
+
7
+ * Various performance enhancements
8
+ * Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
9
+
10
+ # 1.4.6
11
+
12
+ * Fix bug that was erroneously considering that files containing `.` in the names were being
13
+ required if a different file with the same name was already being required
14
+
15
+ Example:
16
+
17
+ require 'foo'
18
+ require 'foo.en'
19
+
20
+ Before bootsnap was considering `foo.en` to be the same file as `foo`
21
+
22
+ * Use glibc as part of the ruby_platform cache key
23
+
1
24
  # 1.4.5
2
25
 
3
26
  * MRI 2.7 support
@@ -32,6 +32,8 @@
32
32
 
33
33
  #define KEY_SIZE 64
34
34
 
35
+ #define MAX_CREATE_TEMPFILE_ATTEMPT 3
36
+
35
37
  /*
36
38
  * An instance of this key is written as the first 64 bytes of each cache file.
37
39
  * The mtime and size members track whether the file contents have changed, and
@@ -499,25 +501,32 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
499
501
  {
500
502
  char template[MAX_CACHEPATH_SIZE + 20];
501
503
  char * tmp_path;
502
- int fd, ret;
504
+ int fd, ret, attempt;
503
505
  ssize_t nwrite;
504
506
 
505
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
506
- strcat(tmp_path, ".tmp.XXXXXX");
507
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
508
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
509
+ strcat(tmp_path, ".tmp.XXXXXX");
507
510
 
508
- // mkstemp modifies the template to be the actual created path
509
- fd = mkstemp(tmp_path);
510
- if (fd < 0) {
511
- if (mkpath(tmp_path, 0775) < 0) {
511
+ // mkstemp modifies the template to be the actual created path
512
+ fd = mkstemp(tmp_path);
513
+ if (fd > 0) break;
514
+
515
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
512
516
  *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
513
517
  return -1;
514
518
  }
515
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
516
- if (fd < 0) {
517
- *errno_provenance = "bs_fetch:atomic_write_cache_file:open";
518
- return -1;
519
- }
520
519
  }
520
+ if (fd < 0) {
521
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
522
+ return -1;
523
+ }
524
+
525
+ if (chmod(tmp_path, 0644) < 0) {
526
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
527
+ return -1;
528
+ }
529
+
521
530
  #ifdef _WIN32
522
531
  setmode(fd, O_BINARY);
523
532
  #endif
@@ -800,7 +809,7 @@ try_input_to_storage(VALUE arg)
800
809
  }
801
810
 
802
811
  static VALUE
803
- rescue_input_to_storage(VALUE arg)
812
+ rescue_input_to_storage(VALUE arg, VALUE e)
804
813
  {
805
814
  return uncompilable;
806
815
  }
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative('bootsnap/version')
3
4
  require_relative('bootsnap/bundler')
4
5
  require_relative('bootsnap/load_path_cache')
@@ -46,7 +46,7 @@ module Bootsnap
46
46
  # loadpath.
47
47
  def find(feature)
48
48
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
49
- feature = feature.to_s
49
+ feature = feature.to_s.freeze
50
50
  return feature if absolute_path?(feature)
51
51
  return expand_path(feature) if feature.start_with?('./')
52
52
  @mutex.synchronize do
@@ -178,25 +178,25 @@ 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
197
  def try_index(f)
198
198
  if (p = @index[f])
199
- p + '/' + f
199
+ "#{p}/#{f}"
200
200
  end
201
201
  end
202
202
 
@@ -26,7 +26,7 @@ module Bootsnap
26
26
  super
27
27
  end
28
28
 
29
- # uniq! keeps the first occurance of each path, otherwise preserving
29
+ # uniq! keeps the first occurrence of each path, otherwise preserving
30
30
  # order, preserving the effective load path
31
31
  def uniq!(*args)
32
32
  ret = super
@@ -38,7 +38,11 @@ module Kernel
38
38
  rescue Bootsnap::LoadPathCache::ReturnFalse
39
39
  false
40
40
  rescue Bootsnap::LoadPathCache::FallbackScan
41
- require_with_bootsnap_lfi(path)
41
+ fallback = true
42
+ ensure
43
+ if fallback
44
+ require_with_bootsnap_lfi(path)
45
+ end
42
46
  end
43
47
 
44
48
  alias_method(:require_relative_without_bootsnap, :require_relative)
@@ -56,7 +60,7 @@ module Kernel
56
60
  end
57
61
 
58
62
  # load also allows relative paths from pwd even when not in $:
59
- if File.exist?(relative = File.expand_path(path))
63
+ if File.exist?(relative = File.expand_path(path).freeze)
60
64
  return load_without_bootsnap(relative, wrap)
61
65
  end
62
66
 
@@ -67,7 +71,11 @@ module Kernel
67
71
  rescue Bootsnap::LoadPathCache::ReturnFalse
68
72
  false
69
73
  rescue Bootsnap::LoadPathCache::FallbackScan
70
- load_without_bootsnap(path, wrap)
74
+ fallback = true
75
+ ensure
76
+ if fallback
77
+ load_without_bootsnap(path, wrap)
78
+ end
71
79
  end
72
80
  end
73
81
 
@@ -88,6 +96,10 @@ class Module
88
96
  rescue Bootsnap::LoadPathCache::ReturnFalse
89
97
  false
90
98
  rescue Bootsnap::LoadPathCache::FallbackScan
91
- autoload_without_bootsnap(const, path)
99
+ fallback = true
100
+ ensure
101
+ if fallback
102
+ autoload_without_bootsnap(const, path)
103
+ end
92
104
  end
93
105
  end
@@ -99,7 +99,7 @@ module Bootsnap
99
99
  altname = if extension_elidable?(short)
100
100
  # Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
101
101
  strip_extension_if_elidable(short)
102
- elsif long && (ext = File.extname(long))
102
+ elsif long && (ext = File.extname(long.freeze))
103
103
  # We already know the extension of the actual file this
104
104
  # resolves to, so put that back on.
105
105
  short + ext
@@ -129,7 +129,7 @@ module Bootsnap
129
129
  # to name files in a way that assumes otherwise.
130
130
  # (E.g. It's unlikely that someone will know that their code
131
131
  # will _never_ run on MacOS, and therefore think they can get away
132
- # with callling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
132
+ # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
133
133
  #
134
134
  # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
135
135
  def extension_elidable?(f)
@@ -21,7 +21,7 @@ module Bootsnap
21
21
  attr_reader(:path)
22
22
 
23
23
  def initialize(path)
24
- @path = path.to_s
24
+ @path = path.to_s.freeze
25
25
  end
26
26
 
27
27
  # True if the path exists, but represents a non-directory object
@@ -60,7 +60,7 @@ module Bootsnap
60
60
  end
61
61
 
62
62
  def expanded_path
63
- File.expand_path(path)
63
+ File.expand_path(path).freeze
64
64
  end
65
65
 
66
66
  private
@@ -5,7 +5,6 @@ require_relative('../explicit_require')
5
5
  module Bootsnap
6
6
  module LoadPathCache
7
7
  module PathScanner
8
- ALL_FILES = "/{,**/*/**/}*"
9
8
  REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
10
9
  NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
11
10
  ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
@@ -16,34 +15,48 @@ module Bootsnap
16
15
  ''
17
16
  end
18
17
 
19
- def self.call(path)
20
- path = path.to_s
21
-
22
- relative_slice = (path.size + 1)..-1
23
- # If the bundle path is a descendent of this path, we do additional
24
- # checks to prevent recursing into the bundle path as we recurse
25
- # through this path. We don't want to scan the bundle path because
26
- # anything useful in it will be present on other load path items.
27
- #
28
- # This can happen if, for example, the user adds '.' to the load path,
29
- # and the bundle path is '.bundle'.
30
- contains_bundle_path = BUNDLE_PATH.start_with?(path)
31
-
32
- dirs = []
33
- requirables = []
34
-
35
- Dir.glob(path + ALL_FILES).each do |absolute_path|
36
- next if contains_bundle_path && absolute_path.start_with?(BUNDLE_PATH)
37
- relative_path = absolute_path.slice(relative_slice)
38
-
39
- if File.directory?(absolute_path)
40
- dirs << relative_path
41
- elsif REQUIRABLE_EXTENSIONS.include?(File.extname(relative_path))
42
- requirables << relative_path
18
+ class << self
19
+ def call(path)
20
+ path = File.expand_path(path.to_s).freeze
21
+ return [[], []] unless File.directory?(path)
22
+
23
+ # If the bundle path is a descendent of this path, we do additional
24
+ # checks to prevent recursing into the bundle path as we recurse
25
+ # through this path. We don't want to scan the bundle path because
26
+ # anything useful in it will be present on other load path items.
27
+ #
28
+ # This can happen if, for example, the user adds '.' to the load path,
29
+ # and the bundle path is '.bundle'.
30
+ contains_bundle_path = BUNDLE_PATH.start_with?(path)
31
+
32
+ dirs = []
33
+ requirables = []
34
+ walk(path, nil) do |relative_path, absolute_path, is_directory|
35
+ if is_directory
36
+ dirs << relative_path
37
+ !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
+ elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
39
+ requirables << relative_path
40
+ end
43
41
  end
42
+ [requirables, dirs]
44
43
  end
45
44
 
46
- [requirables, dirs]
45
+ def walk(absolute_dir_path, relative_dir_path, &block)
46
+ Dir.foreach(absolute_dir_path) do |name|
47
+ next if name.start_with?('.')
48
+ relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
49
+
50
+ absolute_path = "#{absolute_dir_path}/#{name}"
51
+ if File.directory?(absolute_path)
52
+ if yield relative_path, absolute_path, true
53
+ walk(absolute_path, relative_path, &block)
54
+ end
55
+ else
56
+ yield relative_path, absolute_path, false
57
+ end
58
+ end
59
+ end
47
60
  end
48
61
  end
49
62
  end
@@ -15,15 +15,15 @@ module Bootsnap
15
15
 
16
16
  def realpath(caller_location, path)
17
17
  base = File.dirname(caller_location)
18
- file = find_file(File.expand_path(path, base))
19
- dir = File.dirname(file)
20
- File.join(dir, File.basename(file))
18
+ abspath = File.expand_path(path, base).freeze
19
+ find_file(abspath)
21
20
  end
22
21
 
23
22
  def find_file(name)
24
- ['', *CACHED_EXTENSIONS].each do |ext|
23
+ return File.realpath(name).freeze if File.exist?(name)
24
+ CACHED_EXTENSIONS.each do |ext|
25
25
  filename = "#{name}#{ext}"
26
- return File.realpath(filename) if File.exist?(filename)
26
+ return File.realpath(filename).freeze if File.exist?(filename)
27
27
  end
28
28
  name
29
29
  end
@@ -64,7 +64,7 @@ module Bootsnap
64
64
  def load_data
65
65
  @data = begin
66
66
  MessagePack.load(File.binread(@store_path))
67
- # handle malformed data due to upgrade incompatability
67
+ # handle malformed data due to upgrade incompatibility
68
68
  rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
69
69
  {}
70
70
  rescue ArgumentError => e
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Bootsnap
3
- VERSION = "1.4.6"
3
+ VERSION = "1.4.8"
4
4
  end
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.4.6
4
+ version: 1.4.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-24 00:00:00.000000000 Z
11
+ date: 2020-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake-compiler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -102,26 +102,9 @@ extensions:
102
102
  - ext/bootsnap/extconf.rb
103
103
  extra_rdoc_files: []
104
104
  files:
105
- - ".github/CODEOWNERS"
106
- - ".github/probots.yml"
107
- - ".gitignore"
108
- - ".rubocop.yml"
109
- - ".travis.yml"
110
105
  - CHANGELOG.md
111
- - CODE_OF_CONDUCT.md
112
- - CONTRIBUTING.md
113
- - Gemfile
114
106
  - LICENSE.txt
115
- - README.jp.md
116
107
  - README.md
117
- - Rakefile
118
- - bin/ci
119
- - bin/console
120
- - bin/setup
121
- - bin/test-minimal-support
122
- - bin/testunit
123
- - bootsnap.gemspec
124
- - dev.yml
125
108
  - ext/bootsnap/bootsnap.c
126
109
  - ext/bootsnap/bootsnap.h
127
110
  - ext/bootsnap/extconf.rb
@@ -144,7 +127,6 @@ files:
144
127
  - lib/bootsnap/load_path_cache/store.rb
145
128
  - lib/bootsnap/setup.rb
146
129
  - lib/bootsnap/version.rb
147
- - shipit.rubygems.yml
148
130
  homepage: https://github.com/Shopify/bootsnap
149
131
  licenses:
150
132
  - MIT
@@ -167,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
149
  - !ruby/object:Gem::Version
168
150
  version: '0'
169
151
  requirements: []
170
- rubygems_version: 3.0.6
152
+ rubygems_version: 3.0.2
171
153
  signing_key:
172
154
  specification_version: 4
173
155
  summary: Boot large ruby/rails apps faster
@@ -1,2 +0,0 @@
1
- # mvm:maintainer
2
- * @burke
@@ -1,2 +0,0 @@
1
- enabled:
2
- - cla
data/.gitignore DELETED
@@ -1,17 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- *.bundle
11
- *.so
12
- *.o
13
- *.a
14
- *.gem
15
- *.db
16
- mkmf.log
17
- .rubocop-*
@@ -1,20 +0,0 @@
1
- inherit_from:
2
- - http://shopify.github.io/ruby-style-guide/rubocop.yml
3
-
4
- AllCops:
5
- Exclude:
6
- - 'vendor/**/*'
7
- - 'tmp/**/*'
8
- TargetRubyVersion: '2.3'
9
-
10
- # This doesn't take into account retrying from an exception
11
- Lint/HandleExceptions:
12
- Enabled: false
13
-
14
- # allow String.new to create mutable strings
15
- Style/EmptyLiteral:
16
- Enabled: false
17
-
18
- # allow the use of globals which makes sense in a CLI app like this
19
- Style/GlobalVars:
20
- Enabled: false
@@ -1,21 +0,0 @@
1
- language: ruby
2
- sudo: false
3
-
4
- os:
5
- - linux
6
- - osx
7
-
8
- rvm:
9
- - ruby-2.4
10
- - ruby-2.5
11
- - ruby-head
12
-
13
- matrix:
14
- allow_failures:
15
- - rvm: ruby-head
16
- include:
17
- - rvm: jruby
18
- os: linux
19
- env: MINIMAL_SUPPORT=1
20
-
21
- script: bin/ci
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at burke@libbey.me. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
@@ -1,21 +0,0 @@
1
- # Contributing to Bootsnap
2
-
3
- We love receiving pull requests!
4
-
5
- ## Standards
6
-
7
- * PR should explain what the feature does, and why the change exists.
8
- * PR should include any carrier specific documentation explaining how it works.
9
- * Code _must_ be tested, including both unit and remote tests where applicable.
10
- * Be consistent. Write clean code that follows [Ruby community standards](https://github.com/bbatsov/ruby-style-guide).
11
- * Code should be generic and reusable.
12
-
13
- If you're stuck, ask questions!
14
-
15
- ## How to contribute
16
-
17
- 1. Fork it ( https://github.com/Shopify/bootsnap/fork )
18
- 2. Create your feature branch (`git checkout -b my-new-feature`)
19
- 3. Commit your changes (`git commit -am 'Add some feature'`)
20
- 4. Push to the branch (`git push origin my-new-feature`)
21
- 5. Create a new Pull Request
data/Gemfile DELETED
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
- source 'https://rubygems.org'
3
-
4
- # Specify your gem's dependencies in bootsnap.gemspec
5
- gemspec
6
-
7
- group :development do
8
- gem 'rubocop'
9
- end
@@ -1,231 +0,0 @@
1
- # Bootsnap [![Build Status](https://travis-ci.org/Shopify/bootsnap.svg?branch=master)](https://travis-ci.org/Shopify/bootsnap)
2
-
3
- Bootsnap は RubyVM におけるバイトコード生成やファイルルックアップ等の時間のかかる処理を最適化するためのライブラリです。ActiveSupport や YAML もサポートしています。[内部動作](#内部動作)もご覧ください。
4
-
5
- 注意書き: このライブラリは英語話者によって管理されています。この README は日本語ですが、日本語でのサポートはしておらず、リクエストにお答えすることもできません。バイリンガルの方がサポートをサポートしてくださる場合はお知らせください!:)
6
-
7
- ### パフォーマンス
8
-
9
- * [Discourse](https://github.com/discourse/discourse) では、約6秒から3秒まで、約50%の起動時間短縮が確認されています。
10
- * 小さなアプリケーションでも、50%の改善(3.6秒から1.8秒)が確認されています。
11
- * 非常に巨大でモノリシックなアプリである Shopify のプラットフォームでは、約25秒から6.5秒へと約75%短縮されました。
12
-
13
- ## 使用方法
14
-
15
- この gem は macOS と Linux で作動します。まずは、`bootsnap` を `Gemfile` に追加します:
16
-
17
- ```ruby
18
- gem 'bootsnap', require: false
19
- ```
20
-
21
- Rails を使用している場合は、以下のコードを、`config/boot.rb` 内にある `require 'bundler/setup'` の直後に追加してください。
22
-
23
- ```ruby
24
- require 'bootsnap/setup'
25
- ```
26
-
27
- 単に `gem 'bootsnap', require: 'bootsnap/setup'` と指定することも技術的には可能ですが、最大限のパフォーマンス改善を得るためには Bootsnap をできるだけ早く読み込むことが重要です。
28
-
29
- この require の仕組みは[こちら](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb)で確認できます。
30
-
31
- Rails を使用していない場合、または、より多くの設定を変更したい場合は、以下のコードを `require 'bundler/setup'` の直後に追加してください(早く読み込まれるほど、より多くのものを最適化することができます)。
32
-
33
- ```ruby
34
- require 'bootsnap'
35
- env = ENV['RAILS_ENV'] || "development"
36
- Bootsnap.setup(
37
-  cache_dir:           'tmp/cache',         # キャッシュファイルを保存する path
38
-  development_mode:     env == 'development', # 現在の作業環境、例えば RACK_ENV, RAILS_ENV など。
39
- load_path_cache: true, # キャッシュで LOAD_PATH を最適化する。
40
-  autoload_paths_cache: true,                 # キャッシュで ActiveSupport による autoload を行う。
41
-  disable_trace:       true,                 # (アルファ) `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`をセットする。
42
-  compile_cache_iseq:   true,                 # ISeq キャッシュをコンパイルする
43
-  compile_cache_yaml:   true                 # YAML キャッシュをコンパイルする
44
- )
45
- ```
46
-
47
- **ヒント**: `require 'bootsnap'` を `BootLib::Require.from_gem('bootsnap', 'bootsnap')` で、 [こちらのトリック](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require)を使って置き換えることができます。こうすると、巨大な`$LOAD_PATH`がある場合でも、起動時間を最短化するのに役立ちます。
48
-
49
- 注意: Bootsnap と [Spring](https://github.com/rails/spring) は別領域の問題を扱うツールです。Bootsnap は個々のソースファイルの読み込みを高速化します。一方で、Spring は起動されたRailsプロセスのコピーを保持して次回の起動時に起動プロセスの一部を完全にスキップします。2つのツールはうまく連携しており、どちらも新しく生成された Rails アプリケーションにデフォルトで含まれています。
50
-
51
- ### 環境
52
- Bootsnapのすべての機能はセットアップ時の設定に従って開発、テスト、プロダクション、および他のすべての環境で有効化されます。Shopify では、この gem を問題なくすべての環境で安全に使用しています。
53
-
54
- 特定の環境で機能を無効にする場合は、必要に応じて適切な ENV 変数または設定を考慮して設定を変更することをおすすめします。
55
-
56
- ## 内部動作
57
-
58
- Bootsnap は、処理に時間のかかるメソッドの結果をキャッシュすることで最適化しています。これは、大きく分けて2つのカテゴリに分けられます。
59
-
60
- * [Path Pre-Scanning](#path-pre-scanning)
61
- * `Kernel#require` と `Kernel#load` を `$LOAD_PATH` フルスキャンを行わないように変更します。
62
- * `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` を `ActiveSupport::Dependencies.autoload_paths` のフルスキャンを行わないようにオーバーライドします。
63
- * [Compilation caching](#compilation-caching)
64
- * Ruby バイトコードのコンパイル結果をキャッシュするためのメソッド `RubyVM::InstructionSequence.load_iseq` が実装されています。
65
- * `YAML.load_file` を YAML オブジェクトのロード結果を MessagePack でキャッシュするように変更します。 MessagePack でサポートされていないタイプが使われている場合は Marshal が使われます。
66
-
67
- ### Path Pre-Scanning
68
-
69
- _(このライブラリは [bootscale](https://github.com/byroot/bootscale) という別のライブラリを元に開発されました)_
70
-
71
- Bootsnap の初期化時、あるいはパス(例えば、`$LOAD_PATH`)の変更時に、`Bootsnap::LoadPathCache` がキャッシュから必要なエントリーのリストを読み込みます。または、必要に応じてフルスキャンを実行し結果をキャッシュします。
72
- その後、たとえば `require 'foo'` を評価する場合, Ruby は `$LOAD_PATH` `['x', 'y', ...]` のすべてのエントリーを繰り返し評価することで `x/foo.rb`, `y/foo.rb` などを探索します。これに対して Bootsnap は、キャッシュされた require 可能なファイルと `$LOAD_PATH` を見ることで、Rubyが最終的に選択するであろうパスで置き換えます。
73
-
74
- この動作によって生成された syscall を見ると、最終的な結果は以前なら次のようになります。
75
-
76
- ```
77
- open x/foo.rb # (fail)
78
- # (imagine this with 500 $LOAD_PATH entries instead of two)
79
- open y/foo.rb # (success)
80
- close y/foo.rb
81
- open y/foo.rb
82
- ...
83
- ```
84
-
85
- これが、次のようになります:
86
-
87
- ```
88
- open y/foo.rb
89
- ...
90
- ```
91
-
92
- `autoload_paths_cache` オプションが `Bootsnap.setup` に与えられている場合、`ActiveSupport::Dependencies.autoload_paths` をトラバースする方法にはまったく同じ最適化が使用されます。
93
-
94
- `*_path_cache` を機能させるオーバーライドを図にすると、次のようになります。
95
-
96
- ![Bootsnapの説明図](https://cloud.githubusercontent.com/assets/3074765/24532120/eed94e64-158b-11e7-9137-438d759b2ac8.png)
97
-
98
- Bootsnap は、 `$LOAD_PATH` エントリを安定エントリと不安定エントリの2つのカテゴリに分類します。不安定エントリはアプリケーションが起動するたびにスキャンされ、そのキャッシュは30秒間だけ有効になります。安定エントリーに期限切れはありません。コンテンツがスキャンされると、決して変更されないものとみなされます。
99
-
100
- 安定していると考えられる唯一のディレクトリは、Rubyのインストールプレフィックス (`RbConfig::CONFIG['prefix']`, または `/usr/local/ruby` や `~/.rubies/x.y.z`)下にあるものと、`Gem.path` (たとえば `~/.gem/ruby/x.y.z`) や `Bundler.bundle_path` 下にあるものです。他のすべては不安定エントリと分類されます。
101
-
102
- [`Bootsnap::LoadPathCache::Cache`](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb) に加えて次の図では、エントリの解決がどのように機能するかを理解するのに役立つかもしれません。経路探索は以下のようになります。
103
-
104
- ![パス探索の仕組み](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png)
105
-
106
- また、`LoadError` のスキャンがどれほど重いかに注意を払うことも大切です。もし Ruby が `require 'something'` を評価し、そのファイルが `$LOAD_PATH` にない場合は、それを知るために `2 * $LOAD_PATH.length` のファイルシステムアスセスが必要になります。Bootsnap は、ファイルシステムにまったく触れずに `LoadError` を投げ、この結果をキャッシュします。
107
-
108
- ## Compilation Caching
109
-
110
- *(このコンセプトのより分かりやすい解説は [yomikomu](https://github.com/ko1/yomikomu) をお読み下さい。)*
111
-
112
- Ruby には複雑な文法が実装されており、構文解析は簡単なオペレーションではありません。1.9以降、Ruby は Ruby ソースを内部のバイトコードに変換した後、Ruby VM によって実行してきました。2.3.0 以降、[RubyはAPIを公開し](https://ruby-doc.org/core-2.3.0/RubyVM/InstructionSequence.html)、そのバイトコードをキャッシュすることができるようになりました。これにより、同じファイルが複数ロードされた時の、比較的時間のかかる部分をバイパスすることができます。
113
-
114
- また、アプリケーションの起動時に YAML ドキュメントの読み込みに多くの時間を費やしていることを発見しました。そして、 MessagePack と Marshal は deserialization にあたって YAML よりもはるかに高速であるということに気付きました。そこで、YAML ドキュメントを、Ruby バイトコードと同じコンパイルキャッシングの最適化を施すことで、高速化しています。Ruby の "バイトコード" フォーマットに相当するものは MessagePack ドキュメント (あるいは、MessagePack をサポートしていないタイプの YAML ドキュメントの場合は、Marshal stream)になります。
115
-
116
- これらのコンパイル結果は、入力ファイル(FNV1a-64)のフルパスのハッシュを取って生成されたファイル名で、キャッシュディレクトリに保存されます。
117
-
118
- Bootsnap 無しでは、ファイルを `require` するために生成された syscall の順序は次のようになっていました:
119
-
120
- ```
121
- open /c/foo.rb -> m
122
- fstat64 m
123
- close m
124
- open /c/foo.rb -> o
125
- fstat64 o
126
- fstat64 o
127
- read o
128
- read o
129
- ...
130
- close o
131
- ```
132
-
133
- しかし Bootsnap では、次のようになります:
134
-
135
- ```
136
- open /c/foo.rb -> n
137
- fstat64 n
138
- close n
139
- open /c/foo.rb -> n
140
- fstat64 n
141
- open (cache) -> m
142
- read m
143
- read m
144
- close m
145
- close n
146
- ```
147
-
148
- これは一見劣化していると思われるかもしれませんが、性能に大きな違いがあります。
149
-
150
- *(両方のリストの最初の3つの syscalls -- `open`, `fstat64`, `close` -- は本質的に有用ではありません。[このRubyパッチ](https://bugs.ruby-lang.org/issues/13378)は、Boosnap と組み合わせることによって、それらを最適化しています)*
151
-
152
- Bootsnap は、64バイトのヘッダーとそれに続くキャッシュの内容を含んだキャッシュファイルを書き込みます。ヘッダーは、次のいくつかのフィールドで構成されるキャッシュキーです。
153
-
154
- - `version`、Bootsnapにハードコードされる基本的なスキーマのバージョン
155
- - `ruby_platform`、`RUBY_PLATFORM`(x86_64-linux-gnuなど)変数とglibcバージョン(Linuxの場合)またはOSバージョン(BSD、macOSの場合は` uname -v`)のハッシュ
156
- - `compile_option`、`RubyVM::InstructionSequence.compile_option` の返り値
157
- - `ruby_revision`、コンパイルされたRubyのバージョン
158
- - `size`、ソースファイルのサイズ
159
- - `mtime`、コンパイル時のソースファイルの最終変更タイムスタンプ
160
- - `data_size`、バッファに読み込む必要のあるヘッダーに続くバイト数。
161
-
162
- キーが有効な場合、キャッシュがファイルからロードされます。そうでない場合、キャッシュは再生成され、現在のキャッシュを破棄します。
163
-
164
- # 最終的なキャッシュ結果
165
-
166
- 次のファイル構造があるとします。
167
-
168
- ```
169
- /
170
- ├── a
171
- ├── b
172
- └── c
173
- └── foo.rb
174
- ```
175
-
176
- そして、このような `$LOAD_PATH` があるとします。
177
-
178
- ```
179
- ["/a", "/b", "/c"]
180
- ```
181
-
182
- Bootsnap なしで `require 'foo'` を呼び出すと、Ruby は次の順序で syscalls を生成します:
183
-
184
- ```
185
- open /a/foo.rb -> -1
186
- open /b/foo.rb -> -1
187
- open /c/foo.rb -> n
188
- close n
189
- open /c/foo.rb -> m
190
- fstat64 m
191
- close m
192
- open /c/foo.rb -> o
193
- fstat64 o
194
- fstat64 o
195
- read o
196
- read o
197
- ...
198
- close o
199
- ```
200
-
201
- しかし Bootsnap では、次のようになります:
202
-
203
- ```
204
- open /c/foo.rb -> n
205
- fstat64 n
206
- close n
207
- open /c/foo.rb -> n
208
- fstat64 n
209
- open (cache) -> m
210
- read m
211
- read m
212
- close m
213
- close n
214
- ```
215
-
216
- Bootsnap なしで `require 'nope'` を呼び出すと、次のようになります:
217
-
218
- ```
219
- open /a/nope.rb -> -1
220
- open /b/nope.rb -> -1
221
- open /c/nope.rb -> -1
222
- open /a/nope.bundle -> -1
223
- open /b/nope.bundle -> -1
224
- open /c/nope.bundle -> -1
225
- ```
226
-
227
- ...そして、Bootsnap で `require 'nope'` を呼び出すと、次のようになります...
228
-
229
- ```
230
- # (nothing!)
231
- ```
data/Rakefile DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
- require('rake/extensiontask')
3
- require('bundler/gem_tasks')
4
-
5
- gemspec = Gem::Specification.load('bootsnap.gemspec')
6
- Rake::ExtensionTask.new do |ext|
7
- ext.name = 'bootsnap'
8
- ext.ext_dir = 'ext/bootsnap'
9
- ext.lib_dir = 'lib/bootsnap'
10
- ext.gem_spec = gemspec
11
- end
12
-
13
- task(default: :compile)
data/bin/ci DELETED
@@ -1,10 +0,0 @@
1
- #!/bin/bash
2
-
3
- set -euxo pipefail
4
-
5
- if [[ "${MINIMAL_SUPPORT-0}" -eq 1 ]]; then
6
- exec bin/test-minimal-support
7
- else
8
- rake
9
- exec bin/testunit
10
- fi
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require("bundler/setup")
5
- require("bootsnap")
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require("irb")
15
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
-
3
- set -euxo pipefail
4
-
5
- cd test/minimal_support
6
- bundle
7
- BOOTSNAP_CACHE_DIR=/tmp bundle exec ruby -w -I ../../lib bootsnap_setup.rb
@@ -1,8 +0,0 @@
1
- #!/bin/bash
2
-
3
- if [[ $# -eq 0 ]]; then
4
- exec ruby -I"test" -w -e 'Dir.glob("./test/**/*_test.rb").each { |f| require f }' -- "$@"
5
- else
6
- path=$1
7
- exec ruby -I"test" -w -e "require '${path#test/}'" -- "$@"
8
- fi
@@ -1,46 +0,0 @@
1
- # coding: utf-8
2
- # frozen_string_literal: true
3
- lib = File.expand_path('../lib', __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require('bootsnap/version')
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = "bootsnap"
9
- spec.version = Bootsnap::VERSION
10
- spec.authors = ["Burke Libbey"]
11
- spec.email = ["burke.libbey@shopify.com"]
12
-
13
- spec.license = "MIT"
14
-
15
- spec.summary = "Boot large ruby/rails apps faster"
16
- spec.description = spec.summary
17
- spec.homepage = "https://github.com/Shopify/bootsnap"
18
-
19
- spec.metadata = {
20
- 'bug_tracker_uri' => 'https://github.com/Shopify/bootsnap/issues',
21
- 'changelog_uri' => 'https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md',
22
- 'source_code_uri' => 'https://github.com/Shopify/bootsnap',
23
- }
24
-
25
- spec.files = %x(git ls-files -z).split("\x0").reject do |f|
26
- f.match(%r{^(test|spec|features)/})
27
- end
28
- spec.require_paths = %w(lib)
29
-
30
- spec.required_ruby_version = '>= 2.3.0'
31
-
32
- if RUBY_PLATFORM =~ /java/
33
- spec.platform = 'java'
34
- else
35
- spec.platform = Gem::Platform::RUBY
36
- spec.extensions = ['ext/bootsnap/extconf.rb']
37
- end
38
-
39
- spec.add_development_dependency("bundler")
40
- spec.add_development_dependency('rake', '~> 10.0')
41
- spec.add_development_dependency('rake-compiler', '~> 0')
42
- spec.add_development_dependency("minitest", "~> 5.0")
43
- spec.add_development_dependency("mocha", "~> 1.2")
44
-
45
- spec.add_runtime_dependency("msgpack", "~> 1.0")
46
- end
data/dev.yml DELETED
@@ -1,10 +0,0 @@
1
- env:
2
- BOOTSNAP_PEDANTIC: '1'
3
-
4
- up:
5
- - ruby: 2.6.0
6
- - bundler
7
- commands:
8
- build: rake compile
9
- test: 'rake compile && exec bin/testunit'
10
- style: 'exec rubocop -D'
File without changes