LoginSignup
3
2

More than 1 year has passed since last update.

Rubyのspawnで生成したプロセスを終了できない罠と解決策

Last updated at Posted at 2022-06-04

概要

Ruby言語から、コマンドラインツールを呼び出すときには、systemopen3 を使うが、非同期的に呼び出したい場合はspawn を使うことが多い。しかし、引数のコマンドに空白などの文字が入ると、シェル経由の実行になってしまい、プロセスIDを記録してプログラムを終了しようとしても、思ったように終了できないことがある。これを防ぐためには、コマンドに空白などの文字を入れず、引数を分割する。

pid = spawn("command which you want to execute")

pid = spawn("command", "which", "you", "want", "to", "execute")

もう少し詳しく

Rubyでコマンドを非同期的に実行するためには、spawnを利用して、

pid = spawn("command which you want to execute")

detach を呼び出せばいい。

thr = Process.detach(pid)

これで非同期的にコマンドを実行してくれる。
さらに、Threadの状態をみることで、実行が継続しているかその様子をうかがうこともできる。

thr.alive?

プロセスを終了するときには、

Process.kill(:TERM, pid)

とすればいい。ところが、これを実行して、spawn したプロセスを終了させたにも関わらず、ゾンビが残ってしまうことがある。
調べたところ次のことが判明した。

command が shell のメタ文字

 * ? {} [] <> () ~ & | \ $ ; ' ` " \n

を含む場合、shell 経由で実行されます。そうでなければインタプリタから直接実行されます。

どういうことかというと、空白などのメタ文字が含まれている場合は、

sh -c 'command which you want to execute'

みたいに感じで実行されてしまうのである。こうなってしまうと、pid を記録して、Process.kill(:TERM, pid) しても、sh -c "command" が終了するだけで、肝心の "command" は別のプロセスIDで動いているので終了しない。
これを防ぐためには

pid = spawn("command", "which", "you", "want", "to", "execute")

のようにスペースを入れる変わりに、引数を分割すればいい。これでシェル経由での実行を防ぐことができため、得られたプロセスIDをkillすれば無事に標的のプログラムを終了させることができる。spawnは便利だけどこんな罠があることを知ったので注意したい。

この記事は以上です。

プロセスグループを作成するという方法もある

別の解決策として、起動する子プロセスのプロセスグループを作成するという方法がある。

pid = spawn("command which you want to execute", pgroup: true)

この場合、プロセスグループをキルすれば、子プロセスまで削除することができる。

pgid = Process.getpgid(pid)
Process.kill(:TERM, -pgid) # マイナス値でプロセスグループを指定できる
3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2