Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
【しくじり先生】 RailsのAutoloadingとReloadingの仕組みとやってしまったバグ
Search
Yuta Fujii
August 27, 2021
Programming
2
1.9k
【しくじり先生】 RailsのAutoloadingとReloadingの仕組みとやってしまったバグ
Yuta Fujii
August 27, 2021
Tweet
Share
More Decks by Yuta Fujii
See All by Yuta Fujii
Linuxパスワードクラッキングで学べること
yutafujii0
0
6.4k
configの設定をちょっと変えたら Autoloading and Reloadingのエラーに 思いっきりハマってしまった話
yutafujii0
0
48
プロダクトマネジメント輪読会
yutafujii0
0
300
おさらいWebAPI
yutafujii0
0
170
F8 2019に参加したシリコンバレー訪問の感想
yutafujii0
0
200
Other Decks in Programming
See All in Programming
Rubyと自由とAIと
yotii23
6
1.9k
Expoによるアプリ開発の現在地とReact Server Componentsが切り開く未来
yukukotani
1
210
5分で理解する SOLID 原則 #phpcon_nagoya
shogogg
1
410
React 19アップデートのために必要なこと
uhyo
8
1.6k
未経験でSRE、はじめました! 組織を支える役割と軌跡
curekoshimizu
1
200
Honoのおもしろいミドルウェアをみてみよう
yusukebe
1
240
Learning Kotlin with detekt
inouehi
1
190
Ça bouge du côté des animations CSS !
goetter
2
160
ML.NETで始める機械学習
ymd65536
0
240
Serverless Rust: Your Low-Risk Entry Point to Rust in Production (and the benefits are huge)
lmammino
1
160
オレを救った Cline を紹介する
codehex
13
13k
pylint custom ruleで始めるレビュー自動化
shogoujiie
0
160
Featured
See All Featured
Why Our Code Smells
bkeepers
PRO
336
57k
KATA
mclloyd
29
14k
jQuery: Nuts, Bolts and Bling
dougneiner
63
7.7k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
Designing Experiences People Love
moore
140
23k
Embracing the Ebb and Flow
colly
84
4.6k
Git: the NoSQL Database
bkeepers
PRO
428
65k
Making Projects Easy
brettharned
116
6k
StorybookのUI Testing Handbookを読んだ
zakiyama
28
5.5k
Docker and Python
trallard
44
3.3k
Code Reviewing Like a Champion
maltzj
521
39k
Making the Leap to Tech Lead
cromwellryan
133
9.1k
Transcript
【しくじり先生】 RailsのAutoloadingとReloadingの仕組み とやってしまったバグ 2021.08.27
@__yutafujii__ • 定数のAutoloadingとReloadingの仕組み • 開発環境でのサーバー処理 • しくじりました話 Contents
@__yutafujii__ • HR領域に関連するWebシステム • 2019年に入社して開発・運用を行っている • 当時のRailsのバージョンは5.2 • しばらくしてフロントをVueで書くようになった 前提知識
class Dog < Animal
いきなりですが
@__yutafujii__ このRubyコードを実行すると...
@__yutafujii__ →知らない定数があるとエラーが出る
@__yutafujii__ 事前にそれら定数が書かれているファイルをrequireする
@__yutafujii__ • このコードはなぜ動くのか なぜRailsはrequireを書かなくていいのか?
もうひとつ
@__yutafujii__ なぜRailsはファイル修正したらすぐ反映されるのか?
@__yutafujii__ 今日はこの話を... https://railsguides.jp/autoloading_and_reloading_constants.html
Zeitwerk Classic →
@__yutafujii__ Autoloading / Reloadingの方法は過渡期にある Classic Classic Zeitwerk Zeitwerk <= 5.2
6.0 / 6.1 7.0 (deprecated)
Zeitwerk
ZeitwerkモードのAutoloading
loader = Zeitwerk::Loader.new loader.push_dir(...) # ...にautoload_paths loader.setup # ready!
def define(parent, cname, abspath) parent.autoload(cname, abspath) cref = [parent, cname]
c2a[cref] = abspath a2c[abspath] = cref end
None
[Object, :User] => "/Users/fxn/blog/app/models/user.rb" [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"
@__yutafujii__ 最初にディレクトリを探索して 定数と定義ファイルをHashで保持しているから なぜRailsはrequireを書かなくていいのか? Answer
ZeitwerkモードのReloading
ActiveSupport::FileUpdateChecker
Rails::Application::Finisher
autoloaders.main.reload # Zeitwerk::Loaderインスタンス
def unload_autoload(parent, cname) parent.__send__(:remove_const, cname) end
@__yutafujii__ Answer なぜRailsはファイル修正したらすぐ反映されるのか? ファイル修正を検知した場合, 前述のHashテーブルを再更新
ちなみにRails 5.2までは Classic こ う だ っ た
@__yutafujii__ 未知の定数に出会った時, それがありそうなファイルパスを探して読み込むから Autoloading Answer 実際には autoload_path に入っているパス Classic
@__yutafujii__ Answer rails server ファイル修正を検知した場合, 修正が加わったクラスやモジュールを定数リストから落とし 次のリクエスト受理時にAutoloadingを再度走らせるから Reloading Classic
@__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? 未知の定数に遭遇 → NameError まず推論 ファイル修正 → 反映されない 必要に応じてリロード Autoloading Reloading
Classic
@__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? 未知の定数に遭遇 → NameError まず推論 ファイル修正 → 反映されない 必要に応じてリロード • 既にある処理を変えている:メソッドのオーバーライト
◦ Moduleクラスのconst_missing Autoloading Classic
@__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? ActiveSupport::Dependencies Classic
@__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? 未知の定数に遭遇 → NameError まず推論 ファイル修正 → 反映されない 必要に応じてリロード • これまでにない処理がある:メソッドの追加
◦ consoleでのreload!メソッド ◦ serverでのreload & autoloadのメカニズム(後述) Reloading Classic
@__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? ActiveSupport::FileUpdateChecker Classic
@__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? Rails::Application::Finisher Classic
開発環境でのサーバーの挙動
@__yutafujii__ 開発環境サーバーのレスポンス 受理 HTTP POST /admin/books_tags
@__yutafujii__ 開発環境サーバーのレスポンス 受理 routing namespace :admin do resources :books_tags end
@__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 controller_name = ‘admin/books_tags_controller’ controller_name.constantize
Classic
@__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 controller_name = ‘admin/books_tags_controller’ controller_name.constantize
# => Admin::BooksTagsController const_missing発火 Classic
@__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 処理 module Admin class
BooksTagsController def create @tagging = BookTag.new # … end end end Classic
@__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 処理 module Admin class
BooksTagsController def create @tagging = BookTag.new # … end end end const_missing発火 Classic
@__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 処理 レスポンス 200 OK
POST /admin/books_tags
作業してファイル修正すると...
@__yutafujii__ ファイルに変更が加わっているとき 受理 routing controller 特定 処理 レスポンス 修正ファイルをunload unload対象の定数もリストから削除
Classic
@__yutafujii__ ファイルに変更が加わっているとき 受理 routing controller 特定 処理 レスポンス const_missing const_missing
Autoloading Autoloading Classic
@__yutafujii__ Answer Reloadingの仕組み Classic
設定ミスによりAPI開発で しくじってしまった...
ある日,普通に開発していると...
@__yutafujii__ 突如現れるエラー A copy of XXXXXX has been removed from
the module tree....
@__yutafujii__ 対処方法として見つかったもの① https://tech.unifa-e.com/entry/2017/08/09/183519 ググって見つかったブログより抜粋
@__yutafujii__ すると,次第に「なぜそこで起きている?!」という場所でこのエラーが登場するように なる レベルアップしていくエラー Api::One::Parent::ChildController
@__yutafujii__ 対処方法として見つかったもの② http://sugilog.hatenablog.com/entry/20110806/1312584149 ググって見つかったブログより抜粋 config.cache_class = true
@__yutafujii__ 対処方法として見つかったもの② config/development.rb
@__yutafujii__ この設定をすると,ソースコードを書き換えた時に変更が反映されなくなる Reloadingが効かない しかし
本当の原因
@__yutafujii__ 本当の原因 config.reload_classes_only_on_change = false
@__yutafujii__ 本当の原因
@__yutafujii__ 本当の原因 config.reload_classes_only_on_change = false の場合 リスエスト処理が完了するたびに 次のリクエストで都度定数を全削除してしまう
@__yutafujii__ リクエストの都度Reloadingされる 受理 routing controller 特定 処理 レスポンス const_missing Autoloading
Classic remove_const
その結果, リクエストが同時に飛んでくると
@__yutafujii__ リクエストが同時に飛んでくると 受理 routing controller 特定 処理 レスポンス const_missing Autoloading(1)
Classic remove_const(1) 受理 routing controller 特定 処理 レスポンス const_missing Autoloading(2) remove_const(2)
@__yutafujii__ 実際に起きていたこと GET /api/books GET /api/tags Vueインスタンス created() controller 特定
controller 特定 Subdomain::Api::BooksController Subdomain::Api::TagsController
@__yutafujii__ 実際に起きていたこと GET /api/books GET /api/tags Vueインスタンス created() Subdomain::Api::BooksController Subdomain::Api::TagsController
Race Conditionの発生 controller 特定 controller 特定
片方が Subdomain Subdomain::Api Subdomain::Api::BooksController を読み込む間にもう片方が remove_const する
同一Class/Moduleのオブジェクトが 2個できてしまった Subdomain::Api
@__yutafujii__ 実際に起きていたこと
@__yutafujii__ Inflector.constantize(from_mod_name).equal?(from_mod) 実際に起きていたこと
全ては3年前に始まっていた
@__yutafujii__ 3年前の2018年3月に追加されていた
ところでこのrace conditionって
@__yutafujii__ イシューは上がっていたがそれ自体は解決されなかった https://github.com/rails/rails/issues/33209
@__yutafujii__ イシューは上がっていたがそれ自体は解決されなかった https://github.com/rails/rails/issues/33209
@__yutafujii__ • 新たなautoloadの方法 登場するZeitwerk https://github.com/fxn/zeitwerk
@__yutafujii__ Classicモードはいずれ使えなくなる • Zeitwerkモードに早めに移行しておこう https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#classic-mode-is-deprecated
@__yutafujii__ 移行方法 • 実際にはテストが大量にこけたりしたが無事に移行済み https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#enabling-zeitwerk-mode
@__yutafujii__ Classicモードはいずれ使えなくなる https://github.com/rails/rails/commit/0d523d83657ce7066f25d87f6f094e804590e1e8ants.html#classic-mode-is-deprecated
85 828 → ActiveSupport::Dependencies
ありがとうございました
余談
@__yutafujii__ 本当の原因
@__yutafujii__ Application(選考)というModelを使っていたため Answer この設定が必要だった背景(推測)
@__yutafujii__ どうしても「選考」というデータが重要であり,自然な英訳で「Application」というモデル が作成された しかし,Applicationという名前のクラスはRailsのアプリケーション作成時に一つ作成さ れる HRに関するシステムだったので
@__yutafujii__ この設定が必要だった背景(推測) config/application.rb
@__yutafujii__ config/application.rbとapp/models/application.rb ソースコードでApplicationが使われている箇所では,app/models/application.rbをうま くautoloadしてくれなかった
@__yutafujii__ そうしたわけで,こうした設定が追加されていた
@__yutafujii__ requireしてしまうとreloadされない Never be require d https://guides.rubyonrails.org/autoloading_and_reloading_constants_classic_mode.html#autoloading-and-require
@__yutafujii__ だからこの設定がさらに追加されたのだろう
@__yutafujii__ その結果
@__yutafujii__ • 定数のAutoloading/Reloadingは変わった • Classicモードだったら早めに移行しておこう • ソースコードの勉強になった • きっと誰もしくじらないだろう まとめ
@__yutafujii__ 参考 RAILS GUIDES https://guides.rubyonrails.org/autoloading_and_reloading_constants_classic_mode.html https://guides.rubyonrails.org/autoloading_and_reloading_constants.html Rails GitHub https://github.com/rails/rails/blob/main/activesupport/lib/active_support/dependencies.rb https://github.com/rails/rails/blob/main/activesupport/lib/active_support/inflector/methods.rb
Rails Issue https://github.com/rails/rails/issues/33209 Zeitwerk GitHub https://github.com/fxn/zeitwerk#pronunciation バグ検証メモ https://zenn.dev/yutafujii/scraps/cd5500cd468a39
ありがとうございました