Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【しくじり先生】 RailsのAutoloadingとReloadingの仕組みとやってしまったバグ

【しくじり先生】 RailsのAutoloadingとReloadingの仕組みとやってしまったバグ

Aedb2d72d275bbb27a7419e22ea6f70e?s=128

Yuta Fujii

August 27, 2021
Tweet

Transcript

  1. 【しくじり先生】 RailsのAutoloadingとReloadingの仕組み とやってしまったバグ 2021.08.27

  2. @__yutafujii__ • 定数のAutoloadingとReloadingの仕組み • 開発環境でのサーバー処理 • しくじりました話 Contents

  3. @__yutafujii__ • HR領域に関連するWebシステム • 2019年に入社して開発・運用を行っている • 当時のRailsのバージョンは5.2 • しばらくしてフロントをVueで書くようになった 前提知識

  4. class Dog < Animal

  5. いきなりですが

  6. @__yutafujii__ このRubyコードを実行すると...

  7. @__yutafujii__ →知らない定数があるとエラーが出る

  8. @__yutafujii__ 事前にそれら定数が書かれているファイルをrequireする

  9. @__yutafujii__ • このコードはなぜ動くのか なぜRailsはrequireを書かなくていいのか?

  10. もうひとつ

  11. @__yutafujii__ なぜRailsはファイル修正したらすぐ反映されるのか?

  12. @__yutafujii__ 今日はこの話を... https://railsguides.jp/autoloading_and_reloading_constants.html

  13. Zeitwerk Classic →

  14. @__yutafujii__ Autoloading / Reloadingの方法は過渡期にある Classic Classic Zeitwerk Zeitwerk <= 5.2

    6.0 / 6.1 7.0 (deprecated)
  15. Zeitwerk

  16. ZeitwerkモードのAutoloading

  17. loader = Zeitwerk::Loader.new loader.push_dir(...) # ...にautoload_paths loader.setup # ready!

  18. def define(parent, cname, abspath) parent.autoload(cname, abspath) cref = [parent, cname]

    c2a[cref] = abspath a2c[abspath] = cref end
  19. None
  20. [Object, :User] => "/Users/fxn/blog/app/models/user.rb" [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"

  21. @__yutafujii__ 最初にディレクトリを探索して 定数と定義ファイルをHashで保持しているから なぜRailsはrequireを書かなくていいのか? Answer

  22. ZeitwerkモードのReloading

  23. ActiveSupport::FileUpdateChecker

  24. Rails::Application::Finisher

  25. autoloaders.main.reload # Zeitwerk::Loaderインスタンス

  26. def unload_autoload(parent, cname) parent.__send__(:remove_const, cname) end

  27. @__yutafujii__ Answer なぜRailsはファイル修正したらすぐ反映されるのか? ファイル修正を検知した場合, 前述のHashテーブルを再更新

  28. ちなみにRails 5.2までは Classic こ う だ っ た

  29. @__yutafujii__ 未知の定数に出会った時, それがありそうなファイルパスを探して読み込むから Autoloading Answer 実際には autoload_path に入っているパス Classic

  30. @__yutafujii__ Answer rails server ファイル修正を検知した場合, 修正が加わったクラスやモジュールを定数リストから落とし 次のリクエスト受理時にAutoloadingを再度走らせるから Reloading Classic

  31. @__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? 未知の定数に遭遇 → NameError まず推論 ファイル修正 → 反映されない 必要に応じてリロード Autoloading Reloading

    Classic
  32. @__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? 未知の定数に遭遇 → NameError まず推論 ファイル修正 → 反映されない 必要に応じてリロード • 既にある処理を変えている:メソッドのオーバーライト

    ◦ Moduleクラスのconst_missing Autoloading Classic
  33. @__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? ActiveSupport::Dependencies Classic

  34. @__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? 未知の定数に遭遇 → NameError まず推論 ファイル修正 → 反映されない 必要に応じてリロード • これまでにない処理がある:メソッドの追加

    ◦ consoleでのreload!メソッド ◦ serverでのreload & autoloadのメカニズム(後述) Reloading Classic
  35. @__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? ActiveSupport::FileUpdateChecker Classic

  36. @__yutafujii__ RailsはAutoloadingとReloadingのために何をしたのか? Rails::Application::Finisher Classic

  37. 開発環境でのサーバーの挙動

  38. @__yutafujii__ 開発環境サーバーのレスポンス 受理 HTTP POST /admin/books_tags

  39. @__yutafujii__ 開発環境サーバーのレスポンス 受理 routing namespace :admin do resources :books_tags end

  40. @__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 controller_name = ‘admin/books_tags_controller’ controller_name.constantize

    Classic
  41. @__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 controller_name = ‘admin/books_tags_controller’ controller_name.constantize

    # => Admin::BooksTagsController const_missing発火 Classic
  42. @__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 処理 module Admin class

    BooksTagsController def create @tagging = BookTag.new # … end end end Classic
  43. @__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 処理 module Admin class

    BooksTagsController def create @tagging = BookTag.new # … end end end const_missing発火 Classic
  44. @__yutafujii__ 開発環境サーバーのレスポンス 受理 routing controller 特定 処理 レスポンス 200 OK

    POST /admin/books_tags
  45. 作業してファイル修正すると...

  46. @__yutafujii__ ファイルに変更が加わっているとき 受理 routing controller 特定 処理 レスポンス 修正ファイルをunload unload対象の定数もリストから削除

    Classic
  47. @__yutafujii__ ファイルに変更が加わっているとき 受理 routing controller 特定 処理 レスポンス const_missing const_missing

    Autoloading Autoloading Classic
  48. @__yutafujii__ Answer Reloadingの仕組み Classic

  49. 設定ミスによりAPI開発で しくじってしまった...

  50. ある日,普通に開発していると...

  51. @__yutafujii__ 突如現れるエラー A copy of XXXXXX has been removed from

    the module tree....
  52. @__yutafujii__ 対処方法として見つかったもの① https://tech.unifa-e.com/entry/2017/08/09/183519 ググって見つかったブログより抜粋

  53. @__yutafujii__ すると,次第に「なぜそこで起きている?!」という場所でこのエラーが登場するように なる レベルアップしていくエラー Api::One::Parent::ChildController

  54. @__yutafujii__ 対処方法として見つかったもの② http://sugilog.hatenablog.com/entry/20110806/1312584149 ググって見つかったブログより抜粋 config.cache_class = true

  55. @__yutafujii__ 対処方法として見つかったもの② config/development.rb

  56. @__yutafujii__ この設定をすると,ソースコードを書き換えた時に変更が反映されなくなる Reloadingが効かない しかし

  57. 本当の原因

  58. @__yutafujii__ 本当の原因 config.reload_classes_only_on_change = false

  59. @__yutafujii__ 本当の原因

  60. @__yutafujii__ 本当の原因 config.reload_classes_only_on_change = false の場合 リスエスト処理が完了するたびに 次のリクエストで都度定数を全削除してしまう

  61. @__yutafujii__ リクエストの都度Reloadingされる 受理 routing controller 特定 処理 レスポンス const_missing Autoloading

    Classic remove_const
  62. その結果, リクエストが同時に飛んでくると

  63. @__yutafujii__ リクエストが同時に飛んでくると 受理 routing controller 特定 処理 レスポンス const_missing Autoloading(1)

    Classic remove_const(1) 受理 routing controller 特定 処理 レスポンス const_missing Autoloading(2) remove_const(2)
  64. @__yutafujii__ 実際に起きていたこと GET /api/books GET /api/tags Vueインスタンス created() controller 特定

    controller 特定 Subdomain::Api::BooksController Subdomain::Api::TagsController
  65. @__yutafujii__ 実際に起きていたこと GET /api/books GET /api/tags Vueインスタンス created() Subdomain::Api::BooksController Subdomain::Api::TagsController

    Race Conditionの発生 controller 特定 controller 特定
  66. 片方が Subdomain Subdomain::Api Subdomain::Api::BooksController を読み込む間にもう片方が remove_const する

  67. 同一Class/Moduleのオブジェクトが 2個できてしまった Subdomain::Api

  68. @__yutafujii__ 実際に起きていたこと

  69. @__yutafujii__ Inflector.constantize(from_mod_name).equal?(from_mod) 実際に起きていたこと

  70. 全ては3年前に始まっていた

  71. @__yutafujii__ 3年前の2018年3月に追加されていた

  72. ところでこのrace conditionって

  73. @__yutafujii__ イシューは上がっていたがそれ自体は解決されなかった https://github.com/rails/rails/issues/33209

  74. @__yutafujii__ イシューは上がっていたがそれ自体は解決されなかった https://github.com/rails/rails/issues/33209

  75. @__yutafujii__ • 新たなautoloadの方法 登場するZeitwerk https://github.com/fxn/zeitwerk

  76. @__yutafujii__ Classicモードはいずれ使えなくなる • Zeitwerkモードに早めに移行しておこう https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#classic-mode-is-deprecated

  77. @__yutafujii__ 移行方法 • 実際にはテストが大量にこけたりしたが無事に移行済み https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#enabling-zeitwerk-mode

  78. @__yutafujii__ Classicモードはいずれ使えなくなる https://github.com/rails/rails/commit/0d523d83657ce7066f25d87f6f094e804590e1e8ants.html#classic-mode-is-deprecated

  79. 85 828 → ActiveSupport::Dependencies

  80. ありがとうございました

  81. 余談

  82. @__yutafujii__ 本当の原因

  83. @__yutafujii__ Application(選考)というModelを使っていたため Answer この設定が必要だった背景(推測)

  84. @__yutafujii__ どうしても「選考」というデータが重要であり,自然な英訳で「Application」というモデル が作成された しかし,Applicationという名前のクラスはRailsのアプリケーション作成時に一つ作成さ れる HRに関するシステムだったので

  85. @__yutafujii__ この設定が必要だった背景(推測) config/application.rb

  86. @__yutafujii__ config/application.rbとapp/models/application.rb ソースコードでApplicationが使われている箇所では,app/models/application.rbをうま くautoloadしてくれなかった

  87. @__yutafujii__ そうしたわけで,こうした設定が追加されていた

  88. @__yutafujii__ requireしてしまうとreloadされない Never be require d https://guides.rubyonrails.org/autoloading_and_reloading_constants_classic_mode.html#autoloading-and-require

  89. @__yutafujii__ だからこの設定がさらに追加されたのだろう

  90. @__yutafujii__ その結果

  91. @__yutafujii__ • 定数のAutoloading/Reloadingは変わった • Classicモードだったら早めに移行しておこう • ソースコードの勉強になった • きっと誰もしくじらないだろう まとめ

  92. @__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
  93. ありがとうございました