実験したバージョン1:
$ ruby -v
ruby 2.1.4p265 (2014-10-27 revision 48166) [x86_64-darwin14.0]
基本
is_end = false
Signal.trap(:INT) { is_end = true }
until is_end
puts 'start'
sleep(1)
puts 'end'
end
$ ruby 1.rb
loop start
loop end
loop start
^Cloop end
$ ruby 1.rb
loop start
^C^C^C^C^C^C^C^C^C^C^C^Cloop end
普通な感じです。
例えばPHPはカスタムハンドラを設定した場合pcntl_signal_dispatchを呼ばないと一向にシグナルが処理されませんが、そういったものを仕込む必要も無いようです。
puts仕込んでみる
is_end = false
Signal.trap(:INT) do
puts 'INT'
is_end = true
end
until is_end
puts 'loop start'
sleep(1)
puts 'loop end'
end
$ ruby 2.rb
loop start
^CINT
loop end
$ ruby 2.rb
loop start
^CINT
^CINT
^CINT
^CINT
^CINT
loop end
シグナルが送られただけ処理していることが分かります。
デフォルトのシグナル
Signal.trapでシグナルハンドラをセットしますが、これは1つしか指定できないようです。
元々セットされていたハンドラはSignal.trapの戻り値になります。
h1 = Signal.trap(:INT) { puts 'INT1' }
h2 = Signal.trap(:INT) { puts 'INT2' }
h3 = Signal.trap(:INT, 'SIG_DFL')
h4 = Signal.trap(:INT, 'DEFAULT')
puts h1
puts h2
puts h3
puts h4
h2.call()
h3.call()
$ ruby 3.rb
DEFAULT
#<Proc:0x007fd0cb9db1f8@3.rb:2>
#<Proc:0x007fd0cb9db1a8@3.rb:3>
DEFAULT
INT1
INT2
初期値はDEFAULT
で、Signal.trap(:INT, 'DEFAULT')
(もしくはSIG_DFL
を指定)すると戻せます。
シグナルハンドラに割り込んでみる
is_end = false
Signal.trap(:INT) do
puts 'handler1'
sleep(1)
is_end = true
end
h1 = Signal.trap(:INT) do
puts 'handler2'
sleep(1)
Signal.trap(:INT, h1)
# シグナルを再発火
Process.kill(:INT, $$)
end
until is_end
puts 'loop start'
sleep(1)
puts 'loop end'
end
$ ruby 4.rb
loop start
loop end
loop start
^Chandler2
handler1
loop end
$ ruby 4.rb
loop start
loop end
loop start
^Chandler2
^C^C^C^C^C^Chandler1
handler1
handler1
handler1
handler1
handler1
handler1
loop end
ハンドラを再登録後、Process.killでシグナルを再発火することで、元のハンドラも実行しています2。
関数化してみる
def interrupt_signal(signal, &block)
h = Signal.trap(signal) do
block.call
Signal.trap(signal, h)
Process.kill(signal, $$)
end
end
is_end = false
Signal.trap(:INT, nil)
interrupt_signal(:INT) { is_end = true }
interrupt_signal(:INT) { puts '1' }
interrupt_signal(:INT) { puts '2' }
interrupt_signal(:INT) { puts '3' }
interrupt_signal(:INT) { puts '4' }
interrupt_signal(:INT) { puts '5' }
until is_end
puts 'loop start'
sleep(1)
puts 'loop end'
end
$ ruby 5.rb
loop start
loop end
loop start
^C5
4
3
2
1
loop end
$ ruby 5.rb
loop start
loop end
loop start
^C5
4
3
2
1
^C^C^C^C^C^C^C^C^Cloop end
一応使えそうな感じにはなりました。
ちなみにググると...
シグナルハンドラ内ではMutex#synchronizeを使うべき、などのプラクティスが出てきますが、それらも怪しいようです。
1.9.3 ではシグナル発生の度にハンドラが実行されますが(上のスクリプトだと if の条件を満たさないので無視される)、2.x ではハンドラ実行中に発生したシグナルは溜められていて、ハンドラブロックを抜けると通知されるようです。
http://tmtms.hatenablog.com/entry/2014/09/23/ruby-signal
上記の実験からしても、この解釈が正しいそうだと思っています。
ハンドラ監視用スレッドを別で用意するべきかどうかは、現時点ではあまり判断ついてないです。