シグナルハンドリング実験

実験したバージョン[^1]:

```sh
$ ruby -v
ruby 2.1.4p265 (2014-10-27 revision 48166) [x86_64-darwin14.0]
```

# 基本

```rb:1.rb
is_end = false

Signal.trap(:INT) { is_end = true }

until is_end
  puts 'start'
  sleep(1)
  puts 'end'
end
```

```sh
$ 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](http://php.net/manual/ja/function.pcntl-signal-dispatch.php)を呼ばないと一向にシグナルが処理されませんが、そういったものを仕込む必要も無いようです。

# puts仕込んでみる

```rb:2.rb
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
```

```sh
$ ruby 2.rb
loop start
^CINT
loop end

$ ruby 2.rb
loop start
^CINT
^CINT
^CINT
^CINT
^CINT
loop end
```

シグナルが送られただけ処理していることが分かります。

# デフォルトのシグナル

[Signal.trap](http://docs.ruby-lang.org/ja/2.1.0/class/Signal.html#M_TRAP)でシグナルハンドラをセットしますが、これは1つしか指定できないようです。
元々セットされていたハンドラはSignal.trapの戻り値になります。

```rb:3.rb
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()
```

```sh
$ ruby 3.rb
DEFAULT
#<Proc:0x007fd0cb9db1f8@3.rb:2>
#<Proc:0x007fd0cb9db1a8@3.rb:3>
DEFAULT
INT1
INT2
```

初期値は`DEFAULT`で、`Signal.trap(:INT, 'DEFAULT')`（もしくは`SIG_DFL`を指定）すると戻せます。

# シグナルハンドラに割り込んでみる

```rb:4.rb
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
```

```sh
$ 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](http://docs.ruby-lang.org/ja/2.1.0/class/Process.html#M_KILL)でシグナルを再発火することで、元のハンドラも実行しています[^2]。

# 関数化してみる

```rb:5.rb
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
```

```sh
$ 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

上記の実験からしても、この解釈が正しいそうだと思っています。

ハンドラ監視用スレッドを別で用意するべきかどうかは、現時点ではあまり判断ついてないです。


[^1]: 手元にインストールされていたのがコレというだけで深い意味はありません。シグナルハンドリングは結構バージョンによって変わるという話もあるようなので要注意かもしれません。
[^2]: 実はこれが正しい方法なのか確証は持てていませんが、これという代替案も浮かばず。