プログラマ探偵の事件簿:ゾンビの増殖
気づかないうちにゾンビプロセスが増えていくために本プロセスの動作が危うくなることがある。そんな隠れたバグの恐怖の話である。
事件の始まり
夏休みの自由研究のためにパソコンでラジオを聞ける
の実験をしていた時のことである。テストプログラムで受信した電波からラジの音を再生しようとした。コマンドから起動してみたが何も音がでない。プログラムが動作しているかプロセスのリストを確認した瞬間である。
謎のゾンビプロセス(bash)が増殖していた!
ゾンビの親は誰か?
ゾンビプロセスは、子のプロセスを起動した親のプロセスが子のプロセスの終了の面倒を見ないために発生する。
「最近、子供の面倒をみない親が増えているのがゾンビが増える原因」
と助手の猫が天から言っているが、的外れのようだ。
ゾンビプロセスの問題は、親のプロセスを探すことである。そのためには、動作しているプログラムを止めてみる。親のプロセスが死ねば、ゾンビも消える。最初は、この時試していた電波受信のテストプログラムを止めた。しかし、ゾンビは消えない。デバッグのために動作させているTWSNMP FCのプロセスを止めてみた。するとゾンビが消えた。親は、TWSNNP FCだった!
2年前のミスがゾンビを生んだ!
TWSNMP FCで外部プロセスを起動する処理は?と考えた時に、
を思い出した。ソースコードを調べてみると
func ExecNotifyCmd(cmd string, level int) error {
cl := strings.Split(cmd, " ")
if len(cl) < 1 {
return fmt.Errorf("notify ExecCmd is empty")
}
if filepath.Base(cl[0]) != cl[0] {
return fmt.Errorf("notify ExecCmd has path")
}
c := filepath.Join(datastore.GetDataStorePath(), "cmd", filepath.Base(cl[0]))
strLevel := fmt.Sprintf("%d", level)
if len(cl) == 1 {
return exec.Command(c).Start()
}
for i, v := range cl {
if v == "$level" {
cl[i] = strLevel
}
}
return exec.Command(c, cl[1:]...).Start()
}
案の定、
exec.Command(c).Start()
で子のプロセスを起動しただけで終わりの面倒をみていない!これが原因だ!このソースコードは2年前に開発した復刻版TWSNMPからコピペしたものである。
「過去の自分のコードでも安易にコピペすれば災いがある」
と助手の猫が言っている。今度はあっている。
子の面倒をみる親に回心して解決
子のプロセスの終了の面倒を見るためには、
exec.Command(c).Start()
を
exec.Command(c).Run()
にすればよいが、それだと、子がいつまでも遊んでいると親はずっと待つことになる。TWSNMP FCのポーリングで使っている方法で一定時間しか子の遊びを待たない方法で解決したはずだ。
修正して何分かの間はゾンビが発生していない。しばらく様子を見ることにする。