ssh経由でリモートホストで実行してるプロセスにSIGINT送りたい時

perl で色々管理スクリプト書いてるんですが、そのなかでこんなコードを書きました。

system("ssh remote 'rsync ...'");

で、rsyncが走ってる途中でやっぱやめたと思ってCtrl+C=SIGINTを送ったんですが、もちろん perl のプロセスは死ぬんですけど、remoteで動いてるrsyncはそのままゾンビになって残ってしまいました。

はて、いろんなところに原因が考えられるなぁということで調べてみました。

host1> ssh host2 'some-command'

host2> strace -p 20279 # some-command's pid
Process 20279 attached - interrupt to quit
read(0,

# then "Ctrl+C" on host1

(host2)
"", 4096)                       = 0
write(2, "Use of uninitialized value in co"..., 85) = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
rt_sigreturn(0)                         = -1 EPIPE (Broken pipe)
rt_sigprocmask(SIG_BLOCK, [PIPE], NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [PIPE], NULL, 8) = 0
write(2, "sigpipe at hogehoge line "..., 35) = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
rt_sigreturn(0)                         = -1 EPIPE (Broken pipe)
exit_group(32)                          = ?
Process 20279 detached

なるほど、ssh に SIGINT を送っても、リモートホストには SIGINT が飛んでないんですね。でも、ssh 自体は死んでしまうのでリモート側のsome-commandは SIGPIPE が送られることになり、今回は結果としてそのシグナルで死んでる様です。未確認ですが、おそらく rsync は SIGPIPE では死んでくれなかったから、ゾンビで動きつづけたんでしょうね。

とすると、ssh でのシグナルのハンドリングを調査しないといけないなーと思ってたんですが、普通にググってたらぴったりの質問が stackoverflow にありました。

  • signals – How to send SIGINT to a remote process over SSH? – Stack Overflow

    host1> ssh -t host2 ‘some-command’

    host2> strace -p 4480 … read(0, 0x2b73920, 4096) = ? ERESTARTSYS (To be restarted) --- SIGINT (Interrupt) @ 0 (0) --- rt_sigreturn(0) = -1 EINTR (Interrupted system call) rt_sigprocmask(SIG_BLOCK, [INT], NULL, 8) = 0 rt_sigprocmask(SIG_UNBLOCK, [INT], NULL, 8) = 0 write(2, “sigint at hogehoge line 4”…, 34) = 34 exit_group(4) = ? Process 4480 detached

というわけで無事 SIGINT がやってきました。

ssh -tというオプションはpseudo-ttyを強制すると書いてあるのですが正直pseudo-ttyがなんなのかよくわからない。。。これもググッてみると分かりやすい例がありました。

なるほどなるほど。-tしない場合、こういうことが起こっちゃうんですね。pseudo-ttyを割り当てることでいい感じに動いてくれます。

というわけで、冒頭のスクリプトも以下の様にsystemで呼ぶコマンドに-tを与えてしまえば無事 SIGINT が送られましたとさ!

system("ssh -t remote 'rsync ...'");