bootsnap 1.4.5 → 1.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +227 -0
- data/LICENSE.txt +1 -1
- data/README.md +57 -20
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +301 -155
- data/ext/bootsnap/extconf.rb +22 -14
- data/lib/bootsnap/bundler.rb +2 -0
- data/lib/bootsnap/cli/worker_pool.rb +136 -0
- data/lib/bootsnap/cli.rb +281 -0
- data/lib/bootsnap/compile_cache/iseq.rb +64 -19
- data/lib/bootsnap/compile_cache/json.rb +93 -0
- data/lib/bootsnap/compile_cache/yaml.rb +333 -42
- data/lib/bootsnap/compile_cache.rb +26 -8
- data/lib/bootsnap/explicit_require.rb +5 -3
- data/lib/bootsnap/load_path_cache/cache.rb +65 -37
- data/lib/bootsnap/load_path_cache/change_observer.rb +19 -3
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +28 -83
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +2 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +63 -29
- data/lib/bootsnap/load_path_cache/path.rb +42 -19
- data/lib/bootsnap/load_path_cache/path_scanner.rb +60 -29
- data/lib/bootsnap/load_path_cache/store.rb +64 -23
- data/lib/bootsnap/load_path_cache.rb +31 -38
- data/lib/bootsnap/setup.rb +3 -36
- data/lib/bootsnap/version.rb +3 -1
- data/lib/bootsnap.rb +127 -36
- metadata +15 -99
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/README.jp.md +0 -231
- data/Rakefile +0 -12
- data/bin/ci +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -45
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -106
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
- data/shipit.rubygems.yml +0 -0
data/.gitignore
DELETED
data/.rubocop.yml
DELETED
@@ -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.2'
|
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
|
data/.travis.yml
DELETED
@@ -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
|
data/CODE_OF_CONDUCT.md
DELETED
@@ -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/
|
data/CONTRIBUTING.md
DELETED
@@ -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
data/README.jp.md
DELETED
@@ -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
|
-
- `os_version`、(macOS, BSDの) 現在のカーネルバージョンか 、(Linuxの) glibc のバージョンのハッシュ
|
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,12 +0,0 @@
|
|
1
|
-
require('rake/extensiontask')
|
2
|
-
require('bundler/gem_tasks')
|
3
|
-
|
4
|
-
gemspec = Gem::Specification.load('bootsnap.gemspec')
|
5
|
-
Rake::ExtensionTask.new do |ext|
|
6
|
-
ext.name = 'bootsnap'
|
7
|
-
ext.ext_dir = 'ext/bootsnap'
|
8
|
-
ext.lib_dir = 'lib/bootsnap'
|
9
|
-
ext.gem_spec = gemspec
|
10
|
-
end
|
11
|
-
|
12
|
-
task(default: :compile)
|
data/bin/ci
DELETED
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require("bundler/setup")
|
4
|
-
require("bootsnap")
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require("irb")
|
14
|
-
IRB.start(__FILE__)
|
data/bin/setup
DELETED
data/bin/test-minimal-support
DELETED
data/bin/testunit
DELETED
data/bootsnap.gemspec
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require('bootsnap/version')
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "bootsnap"
|
8
|
-
spec.version = Bootsnap::VERSION
|
9
|
-
spec.authors = ["Burke Libbey"]
|
10
|
-
spec.email = ["burke.libbey@shopify.com"]
|
11
|
-
|
12
|
-
spec.license = "MIT"
|
13
|
-
|
14
|
-
spec.summary = "Boot large ruby/rails apps faster"
|
15
|
-
spec.description = spec.summary
|
16
|
-
spec.homepage = "https://github.com/Shopify/bootsnap"
|
17
|
-
|
18
|
-
spec.metadata = {
|
19
|
-
'bug_tracker_uri' => 'https://github.com/Shopify/bootsnap/issues',
|
20
|
-
'changelog_uri' => 'https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md',
|
21
|
-
'source_code_uri' => 'https://github.com/Shopify/bootsnap',
|
22
|
-
}
|
23
|
-
|
24
|
-
spec.files = %x(git ls-files -z).split("\x0").reject do |f|
|
25
|
-
f.match(%r{^(test|spec|features)/})
|
26
|
-
end
|
27
|
-
spec.require_paths = %w(lib)
|
28
|
-
|
29
|
-
spec.required_ruby_version = '>= 2.0.0'
|
30
|
-
|
31
|
-
if RUBY_PLATFORM =~ /java/
|
32
|
-
spec.platform = 'java'
|
33
|
-
else
|
34
|
-
spec.platform = Gem::Platform::RUBY
|
35
|
-
spec.extensions = ['ext/bootsnap/extconf.rb']
|
36
|
-
end
|
37
|
-
|
38
|
-
spec.add_development_dependency("bundler")
|
39
|
-
spec.add_development_dependency('rake', '~> 10.0')
|
40
|
-
spec.add_development_dependency('rake-compiler', '~> 0')
|
41
|
-
spec.add_development_dependency("minitest", "~> 5.0")
|
42
|
-
spec.add_development_dependency("mocha", "~> 1.2")
|
43
|
-
|
44
|
-
spec.add_runtime_dependency("msgpack", "~> 1.0")
|
45
|
-
end
|
data/dev.yml
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
module Bootsnap
|
2
|
-
module LoadPathCache
|
3
|
-
module CoreExt
|
4
|
-
module ActiveSupport
|
5
|
-
def self.without_bootsnap_cache
|
6
|
-
prev = Thread.current[:without_bootsnap_cache] || false
|
7
|
-
Thread.current[:without_bootsnap_cache] = true
|
8
|
-
yield
|
9
|
-
ensure
|
10
|
-
Thread.current[:without_bootsnap_cache] = prev
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.allow_bootsnap_retry(allowed)
|
14
|
-
prev = Thread.current[:without_bootsnap_retry] || false
|
15
|
-
Thread.current[:without_bootsnap_retry] = !allowed
|
16
|
-
yield
|
17
|
-
ensure
|
18
|
-
Thread.current[:without_bootsnap_retry] = prev
|
19
|
-
end
|
20
|
-
|
21
|
-
module ClassMethods
|
22
|
-
def autoload_paths=(o)
|
23
|
-
super
|
24
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o)
|
25
|
-
end
|
26
|
-
|
27
|
-
def search_for_file(path)
|
28
|
-
return super if Thread.current[:without_bootsnap_cache]
|
29
|
-
begin
|
30
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
|
31
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
32
|
-
nil # doesn't really apply here
|
33
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
34
|
-
nil # doesn't really apply here
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def autoloadable_module?(path_suffix)
|
39
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix)
|
40
|
-
end
|
41
|
-
|
42
|
-
def remove_constant(const)
|
43
|
-
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
44
|
-
end
|
45
|
-
|
46
|
-
def require_or_load(*)
|
47
|
-
CoreExt::ActiveSupport.allow_bootsnap_retry(true) do
|
48
|
-
super
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# If we can't find a constant using the patched implementation of
|
53
|
-
# search_for_file, try again with the default implementation.
|
54
|
-
#
|
55
|
-
# These methods call search_for_file, and we want to modify its
|
56
|
-
# behaviour. The gymnastics here are a bit awkward, but it prevents
|
57
|
-
# 200+ lines of monkeypatches.
|
58
|
-
def load_missing_constant(from_mod, const_name)
|
59
|
-
CoreExt::ActiveSupport.allow_bootsnap_retry(false) do
|
60
|
-
super
|
61
|
-
end
|
62
|
-
rescue NameError => e
|
63
|
-
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
64
|
-
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
65
|
-
|
66
|
-
# This function can end up called recursively, we only want to
|
67
|
-
# retry at the top-level.
|
68
|
-
raise(e) if Thread.current[:without_bootsnap_retry]
|
69
|
-
# If we already had cache disabled, there's no use retrying
|
70
|
-
raise(e) if Thread.current[:without_bootsnap_cache]
|
71
|
-
# NoMethodError is a NameError, but we only want to handle actual
|
72
|
-
# NameError instances.
|
73
|
-
raise(e) unless e.class == NameError
|
74
|
-
# We can only confidently handle cases when *this* constant fails
|
75
|
-
# to load, not other constants referred to by it.
|
76
|
-
raise(e) unless e.name == const_name
|
77
|
-
# If the constant was actually loaded, something else went wrong?
|
78
|
-
raise(e) if from_mod.const_defined?(const_name)
|
79
|
-
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
80
|
-
end
|
81
|
-
|
82
|
-
# Signature has changed a few times over the years; easiest to not
|
83
|
-
# reiterate it with version polymorphism here...
|
84
|
-
def depend_on(*)
|
85
|
-
super
|
86
|
-
rescue LoadError => e
|
87
|
-
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
88
|
-
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
89
|
-
|
90
|
-
# If we already had cache disabled, there's no use retrying
|
91
|
-
raise(e) if Thread.current[:without_bootsnap_cache]
|
92
|
-
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
module ActiveSupport
|
101
|
-
module Dependencies
|
102
|
-
class << self
|
103
|
-
prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Bootsnap
|
4
|
-
module LoadPathCache
|
5
|
-
class RealpathCache
|
6
|
-
def initialize
|
7
|
-
@cache = Hash.new { |h, k| h[k] = realpath(*k) }
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(*key)
|
11
|
-
@cache[key]
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def realpath(caller_location, path)
|
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))
|
21
|
-
end
|
22
|
-
|
23
|
-
def find_file(name)
|
24
|
-
['', *CACHED_EXTENSIONS].each do |ext|
|
25
|
-
filename = "#{name}#{ext}"
|
26
|
-
return File.realpath(filename) if File.exist?(filename)
|
27
|
-
end
|
28
|
-
name
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/shipit.rubygems.yml
DELETED
File without changes
|