Save 37% off PRO during our Black Friday Sale! »

タイムアウトにご用心 / Timeout might break application state

Fb1b9f3d7332a7a7e262b70013b5f7dd?s=47 Fumiaki MATSUSHIMA
October 10, 2021
1.4k

タイムアウトにご用心 / Timeout might break application state

https://kaigionrails.doorkeeper.jp/events/127339

Kaigi on Rails _2021_ new LT 発表資料

Fb1b9f3d7332a7a7e262b70013b5f7dd?s=128

Fumiaki MATSUSHIMA

October 10, 2021
Tweet

Transcript

  1. @mtsmfm タイムアウトにご用心

  2. 松島 史秋 GitHub, Twitter @mtsmfm

  3. https://ninirb.github.io

  4. https://www.meetup.com/ja-JP/GraphQL-Tokyo/

  5. 伝えたいこと タイムアウトしたプロセスを再利 用してはいけない

  6. どんな問題が起きるか - タイムアウト後、アプリケーションの状態が崩壊 する可能性がある

  7. User.where(name: "foo").not_deleted /users_count /users?name=foo User.not_deleted.count

  8. User.where(name: "foo").not_deleted /users_count 1. リクエストがくる /users?name=foo User.not_deleted.count

  9. User.where(name: "foo").not_deleted /users_count User.not_deleted.count 1. リクエストがくる 2. not_deleted scope を組み立

    て中にタイムアウトになる /users?name=foo
  10. User.where(name: "foo").not_deleted /users_count 1. リクエストがくる 2. not_deleted scope を組み立 て中にタイムアウトになる

    /users?name=foo 3. 別のリクエストがくる User.not_deleted.count
  11. User.where(name: "foo").not_deleted /users_count 1. リクエストがくる 2. not_deleted scope を組み立 て中にタイムアウトになる

    4. タイムアウトしたときにスタックされ ていた where が適用される /users?name=foo 3. 別のリクエストがくる User.where(name: "foo").not_deleted.count User.not_deleted.count
  12. どんな問題が起きるか - タイムアウト後、アプリケーションの状態が崩壊 する可能性がある - タイムアウトのタイミング次第では、その瞬間の状態が 保持されてしまうケースがある - 例えば、User.count と書いてあるのに、タイムアウト以

    後、そのプロセスは User.where(name: "foo").count 相当の処理をする
  13. なぜそうなるのか - ensure 中に Thread#raise されるとそこで ensure が全部実行されることなく終わってしま うから

  14. class Stack def stack @stack ||= [] end def with(name)

    stack.push(name) yield stack ensure stack.pop end end stack = Stack.new stack.with('foo') do p stack.stack #=> ["foo"] stack.with('bar') do p stack.stack #=> ["foo", "bar"] end p stack.stack #=> ["foo"] end
  15. ここでタイムア ウトしても セーフ class Stack def stack @stack ||= []

    end def with(name) stack.push(name) yield stack ensure stack.pop end end
  16. ここでタイムア ウトすると pop されない class Stack def stack @stack ||=

    [] end def with(name) stack.push(name) yield stack ensure stack.pop end end
  17. Rails のバグ? - 違う - あらゆるタイミングで Thread#raise されること を考慮するのは厳しいので諦めている -

    https://github.com/rails/rails/pull/17607#issuecomme nt-70538060 - Scope 以外にも ensure で状態をリセットすると ころはタイムアウトで壊れる可能性有
  18. どんなときに踏みやすいか - 自分で Timeout.timeout を書くことはそんなに ない気がする - 注意すべきは gem、特に Web

    サーバとか Rack middleware とか
  19. rack-timeout の罠 - rails new して書かれている Puma にはタイム アウト機構はなく、rack-timeout が事実上デファ

    クト - Heroku のチュートリアルでも紹介されていたり - デフォルト設定だとまさにやばいケースに該当 する
  20. rack-timeout の罠 - term_on_timeout: true にすれば、タイムアウト したら TERM で殺してくれる -

    これはデフォルトではない - Puma を Clustered mode で動かしていない場合、true にするとサーバが完全に死ぬ
  21. https://github.com/sharpstone/rack-timeout/issues/169

  22. デフォルトやばいのでは? - 警告だけでも提案したが通らなかった - rack-timeout gem が歴史的に TERM するわけではな い

    - 危ないという説明も既にある (doc/risks.md) - Puma のデフォルトは Clustered mode じゃない
  23. 伝えたいこと - タイムアウトしたプロセスを再利用してはいけな い - Middleware っぽいところでやってると特に危ない - rack-timeout は

    term_on_timeout を必ず true に - 具体的な再現コードなどは https://zenn.dev/mtsmfm/articles/d55d739104e87c
  24. Credits Background pattern from Toptal Subtle Patterns https://www.toptal.com/designers/subtlepatterns/