rack-timeout と rackアプリケーション

rackアプリケーションには、レスポンスを返すまでの時間にタイムアウトを設定できるrack-timeoutというgemがある。(タイムアウトを迎えると例外を発生させるためログに残る。)

rack-timeoutを使わない場合は、rackサーバ自体に設定するタイムアウトでも似たようなことができて、タイムアウトを迎えると例外を吐くことなくプロセスを殺す。そのためログが残らない。ログが残るようにkillシグナルを受け取ったらログに出力する、という蛮行をしている人もいる。
http://blog.mirakui.com/entry/2012/02/19/how-to-flush-rails-logger-if-unicorn-timeout (この記事に書いている通り、プロセスが暴走したことを見越した運用が要求される。)

さて、rack-timeout を使うとタイムアウトを迎えたリクエストのログを吐くことができてよさそうに見えるが問題があり、処理の内容によっては停止時にプロセスを不安定にさせることがある。
https://github.com/heroku/rack-timeout#timing-out-inherently-unsafe
https://www.schneems.com/2017/02/21/the-oldest-bug-in-ruby-why-racktimeout-might-hose-your-server/

プロセスを不安定にさせる仕組みは、下記コードで説明ができる。
仕組みは簡単でensureブロック内でタイムアウトを迎えるとロックを握ったまま処理を終了していまうからだ。
その後は、ロックを取ろうとするプロセスが永久にスタックしてしまうだろう。(unicornはマルチプロセスなので影響は少ないがpumaだと被害がでかい)

begin
  lock(resouce)
  work!
ensure
  work2!
  release_lock(resource)
end

対策はrack-timeout を使わない、割り込まれたくない処理をThread.handle_interuptで保護をする、というのが考えられる。

(そもそも、なぜrack-timeoutを使うかというとロードバランサーのタイムアウトを超えても計算し続けることは無駄でありサーバリソースの保護という観点と遅い処理の検出、あたりなんだと思う。)