ruby から外部コマンドを起動する spawn
ruby で外部コマンドを呼び出すときに spawn を使って管理してみた。
spawn の特徴とか。
だいぶ昔に導入されたこの機能については、次のスライドが良いみたい。https://staff.aist.go.jp/tanaka-akira/pub/spawn-2009-04.pdf
1.8 から 1.9 の頃のはなし。
- `cmd`
- system “cmd”
- exec “cmd”,,,
- open/popen/popen3
- fork
ここにさらに spawn が加わってた。
spawn でプロセス起動
コマンドラインを指定する。
spawn " sleep 10"
特徴として、実行して投げっぱなし。終了待ちしたりしない。
実行後のプロセスを見てみると。
PID TTY STAT TIME COMMAND 30278 ? S 0:00 sshd: takuya@pts/0 30279 pts/0 Ss+ 0:00 \_ -bash 24760 pts/4 Ss+ 0:02 /bin/bash 31470 pts/0 S 0:00 sleep 10
sleep の実行は即座に実行されている。終了待ちしてない。
実行後に親プロセスが正常終了するので、親がinit になる。(つまり、孤児プロセス)
spawn でプロセス起動して、終了待ちをする。
今度は、プロセス起動をして、終了待ちをする。 終了待ちするには Process.wait( pid ) か Process.waitall を使う。
i = spawn(" sleep 10 " ) Process.wait i
実行後のプロセスツリーは次のようになる。
PID TTY STAT TIME COMMAND 30278 ? S 0:00 sshd: takuya@pts/0 30279 pts/0 Ss 0:00 \_ -bash 31569 pts/0 Sl+ 0:00 \_ ruby test.rb 31571 pts/0 S+ 0:00 \_ sleep 10
今度は、ruby の子プロセスになっている状態がハッキリわかる。前例のspawnのみは、終了待ちをしないため、rubyが終了してinitが親になっていた。
spanwで起動したプロセスを終了する。
次に、子プロセスが実行中に、SIGNALを送って終了させてみることにする。
これは、時間のかかる処理を待ってる時に待ちきれなくて、止めるなどの利用場面。
i = spawn(" sleep 10 " ) ##spawn sleep 5 ## Process.kill(:TERM, i) ## 終了させる sleep 5
状態が2つできるので、それぞれ追いかけていく。
- spawn 直後から 子プロセスの終了待ち
- 子プロセスを強制終了直後
起動直後の状態
起動直後の状態は次のようになっている。
PID TTY STAT TIME COMMAND 30278 ? S 0:00 sshd: takuya@pts/0 30279 pts/0 Ss 0:00 \_ -bash 31649 pts/0 Sl+ 0:00 \_ ruby test.rb 31651 pts/0 S+ 0:00 \_ sleep 10
起動したプロセスを待ち合わせているので、ruby は sleep を子供に持ったまま、終了待ちをしている。
SIGTERM 送信後の状態。
起動したプロセスにTERMを送信して、終了した状態。
PID TTY STAT TIME COMMAND 30278 ? S 0:00 sshd: takuya@pts/0 30279 pts/0 Ss 0:00 \_ -bash 31649 pts/0 Sl+ 0:00 \_ ruby test.rb 31651 pts/0 Z+ 0:00 \_ [sleep] <defunct>
sleep は終了し、ruby だけが残る。
ここまでのまとめ
spawn したプロセスは子プロセス。
特に指定しないかぎり。ruby から 子プロセスとしてプログラムが実行される。
spawn の待ち合わせはしない。
spawn したら直ぐにruby に戻ってくる。waitで明示的に待たないかぎり、終了待ちはしない。
子プロセスのpid を覚えておく
spawn したプロセスのpid を覚えておくと終了が出来る。
もし、ruby だけを ctrl+C で止めたらどうなるのか?
子プロセスは、孤児になり、init(親)に拾われる。ruby が先に終了した時と同じ。
ruby から spawn したプロセスは ctrl+C では止められない。
ただし、wait している場合はCTRL+Cでwait 中プロセスを止めてくれる。
ただしruby へ kill -INT interrupt を送信した時は止まらなかったり、
ただし、プロセスグループ作ってたら止まらなかったり、自分が親だったら止まったり。
なんでだろうな?このへん良く分からない。
UNIXのプロセス管理をチャント復習したうえで、ruby の spawn がどうしてるのかを調べないとわからなさそう。
プロセスをいっぱい起動してみる。
次に、プロセスを次々と起動してみる。
pids = [] pids << spawn("sleep","10") pids << spawn("sleep","10") pids << spawn("sleep","10") pids << spawn("sleep","10") pids << spawn("sleep","10") Process.waitall
次々と起動した結果がコレ。
PID TTY STAT TIME COMMAND 30278 ? S 0:00 sshd: takuya@pts/0 30279 pts/0 Ss 0:00 \_ -bash 30467 pts/0 T 0:07 \_ vim test.rb 31800 pts/0 Sl+ 0:00 \_ ruby test.rb 31802 pts/0 S+ 0:00 \_ sleep 10 31804 pts/0 S+ 0:00 \_ sleep 10 31806 pts/0 S+ 0:00 \_ sleep 10 31808 pts/0 S+ 0:00 \_ sleep 10 31810 pts/0 S+ 0:00 \_ sleep 10
これは、小さなバッチ処理を複数起動するのに便利。
また、起動したプロセスをまとめて Process.waitlall
で待ち合わせ出来るのが楽チンでいいな。
プロセスのグループ管理。
プロセスを実行するときに親を決められる。
pids = [] pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) pids.each{|i| puts "子 #{i} <- グループ #{Process.getpgid(i)} " } puts "ruby の pid = #{Process.pid}" Process.waitall
いっぱい起動しても、チャント管理できる。
たくさん起動して、全部に終了シグナルを送る
プロセスグループのpid を-1 した値とシグナルを Process.kill で送ると、グループ全部にシグナルを送って終了してくれる。
i = spawn("sleep", "10") pids = [] pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) pids << spawn("sleep","60" , :pgroup => Process.pid ) sleep 3 Process.kill(:TERM, -1*Process.pid)
孫プロセスはどうなるのか、そのうち試したい。
:pgroup にtrueを設定するとプロセスグループを新規作成してその中で動く
pids = [] pids << spawn("sleep","60" , :pgroup => true ) pids << spawn("sleep","60" , :pgroup => true ) pids << spawn("sleep","60" , :pgroup => true ) pids << spawn("sleep","60" , :pgroup => true ) pids << spawn("sleep","60" , :pgroup => true ) puts "ruby の pid = #{Process.pid}"
このへんも良く分からない、子プロセスの親が自分じゃないのにwaitall で待てる?
spawn したプロセスをどうするか
複数起動するときは グループを指定する。
終了待ちが必要なら wait する。
それでも終了しなかったり、不要になれば シグナルを送る。
一定時間経過したら終了する。
WEBカメラなどをストリーミングして不要になったらプロセスを消すとか出来そう
id = spawn(cmd, Process.pid) watch_thread = Process.detatch(id) begin Timeout.timeout(3000) do watch_thread.join end rescue Timeout::Error Process.kill(:TERM, -1*Process.pid) end
SIGNAL を受けてプロセスグループを管理することも出来そう
trap :INT do Process.kill(:TERM, -1*Process.pid) end
リアルタイム性はないけれど、GPIOをずっと監視するスレッドやネットワークdaemon 作ったり消したり出来そうじゃん。楽しそう。
参考資料 trap / sigal / spawn について。
http://yukithm.blogspot.jp/2014/03/kill.html
http://route477.net/d/?date=20090705