Rubyのシグナルトラップを使う時のマナー
シグナルトラップの中からLoggerに書き込むと、 log writing failed. can't be called from trap context
という出力が出て、書き込みに失敗します。
これはtrapの中でmutexを使っているせい、らしいです。
require "logger"
logger = Logger.new($stdout)
trap "TERM" do
logger.info "got signal"
end
Process.kill :TERM, $$
昔は、mutexを使っていない、ただの File#wirte を行う https://rubygems.org/gems/mono_logger をtrapの中から使うというワークアラウンドが用いられていました。
今も一部で現役かもしれませんが、現代ではtrapの中からloggerへ書き込むことは避ける方向にあると思います。少なくともsidekiqはtrapの中でmono_loggerは使っておらず後述の実装を採用しています。
https://blog.tmtms.net/entry/2014/09/23/ruby-signal
昔の回避方法は、trap内でローカル変数でのフラグを立てて、そのフラグをポーリングしていたようですが、今はpipeを用いるのがオシャレだと思います。(N=1)
実装例は以下のとおりです。
基本的な方針は、trapの中ではpipe経由で文字列を送信して、メインスレッドで例外を投げて次の処理に繋げます。
self_read, self_write = IO.pipe
%w(TERM INT USR1 USR2).each do |sig|
begin
trap sig do
self_write.puts(sig)
end
rescue Interrupt
logger.info "シグナルを受け付けました"
end
end
# ここでThreadを作るとかする
begin
while(readable_io = IO.select([self_read]))
signal = readable_io.first[0].gets.strip
case signal
when 'USR2', 'INT', 'TERM'
raise Interrupt
end
end
rescue Interrupt
# 終了処理をする
end
この実装ではtrapの中でmutexに触らず、最低限のことだけやるので安全です。
おわり
-
category:
- ruby