Don't use Timeout module

一定時間を超えた時に処理を中断したい、という処理はよくあると思うんですが、その時にTimeoutモジュールを使ってはいけない、という話です。

例えば、下記のような実装がそうです。

imeout.timeout(5) do
  User.find_each {|x| x.run }
end

なぜかというと、Timeoutモジュールが実行中の内容に関係なく割り込みをしてしまい、メインスレッドがスタックしてしまう可能性があるからです。
https://www.schneems.com/2017/02/21/the-oldest-bug-in-ruby-why-racktimeout-might-hose-your-server/

Timeoutといえば、rackミドルウェアで実装したrack-timeoutがあります。このリポジトリにもそれらのリスクについて書かれています。
https://github.com/sharpstone/rack-timeout/blob/master/doc/risks.md

じゃあTimeoutを使わないで、代わりの実装どうすればええねんって話なんですが、基本的には各クライアントライブラリのタイムアウト設定を使いましょう。
詳しくは https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts へ。

では、上記にあげたコードで安全に停止する方法について書きます。
ちょっと冗長になるんですが、1回の処理が終わる毎に時間を確認するとよいです。

class SafeTimeout
  def self.timer(limit: )
    @end_at = limit
    yield(self.new(@end_at))
  end

  def initialize(end_at)
    @end_at = end_at
  end

  def timeout?
    Time.zone.now > @end_at
  end
end
SafeTimeout.timer(limit: 25.minutes.from_now) do |timer|
  User.find_each do |x|
    if timer.timeout?
      Rails.logger.error('timeout!!!!!!!')
      break
    else
      x.run
    end
  end
end

関連する記事: http://blog.jiikko.com/105

おわり