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
2.1k
【しくじり先生】 RailsのAutoloadingとReloadingの仕組みとやってしまったバグ
Yuta Fujii
August 27, 2021
Tweet
Share
More Decks by Yuta Fujii
See All by Yuta Fujii
Linuxパスワードクラッキングで学べること
yutafujii0
0
6.7k
configの設定をちょっと変えたら Autoloading and Reloadingのエラーに 思いっきりハマってしまった話
yutafujii0
0
50
プロダクトマネジメント輪読会
yutafujii0
0
320
おさらいWebAPI
yutafujii0
0
180
F8 2019に参加したシリコンバレー訪問の感想
yutafujii0
0
210
Other Decks in Programming
See All in Programming
NixOS + Kubernetesで構築する自宅サーバーのすべて
ichi_h3
0
720
10年もののAPIサーバーにおけるCI/CDの改善の奮闘
mbook
0
820
Things You Thought You Didn’t Need To Care About That Have a Big Impact On Your Job
hollycummins
0
220
ALL CODE BASE ARE BELONG TO STUDY
uzulla
15
2.3k
kiroとCodexで最高のSpec駆動開発を!!数時間で web3ネイティブなミニゲームを作ってみたよ!
mashharuki
0
170
「ちょっと古いから」って避けてた技術書、今だからこそ読もう
mottyzzz
10
6.6k
AI Coding Meetup #3 - 導入セッション / ai-coding-meetup-3
izumin5210
0
3.3k
XP, Testing and ninja testing ZOZ5
m_seki
3
630
非同期jobをtransaction内で 呼ぶなよ!絶対に呼ぶなよ!
alstrocrack
0
850
Catch Up: Go Style Guide Update
andpad
0
220
TFLintカスタムプラグインで始める Terraformコード品質管理
bells17
2
160
overlayPreferenceValue で実現する ピュア SwiftUI な AdMob ネイティブ広告
uhucream
0
180
Featured
See All Featured
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3.1k
Optimizing for Happiness
mojombo
379
70k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
23
1.5k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.5k
Done Done
chrislema
185
16k
The Language of Interfaces
destraynor
162
25k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.4k
GitHub's CSS Performance
jonrohan
1032
470k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
32
2.3k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
9
590
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
7
900
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
30
2.9k
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
ありがとうございました