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
おわり
-
category:
- ruby