go-run meets pstree

以下の内容のメモ

  • 現在の環境1で実行されるprocessの概観を掴む方法
  • go runがtmp directoryにbuildした結果のバイナリをsubprocessとして実行していること

go runが実行するprocess

例えば以下の様なテキトウなfile serverのようなコードがあるとする2

main.go

package main

import (
    "log"
    "net/http"
)

func main() {
    log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}

これをgo runで実行する。

$ go run main.go

/usr/share/doc 以下を公開しているので以下の様な形でgoのtos.htmlが取れる。

$ http -b :8080/go/tos.html
<!--{
    "Title": "Terms of service"
}-->

<p>
The Go website (the "Website") is hosted by Google.
By using and/or visiting the Website, you consent to be bound by Google's general
<a href="//www.google.com/intl/en/policies/terms/">Terms of Service</a>
and Google's general
<a href="//www.google.com/intl/en/privacy/privacy-policy.html">Privacy Policy</a>.
</p>

まぁ今回の記事の主題はそこではない。とりあえず今回はgo runで実行されたprocessがあるというところが重要。

実行されるprocessの概観を掴む

実行した環境で Ctrl + z をして実行中のプロセスをバックグラウンドに持っていく。そしてこの環境で実行されているprocessを知りたい。

# ctrl + z
[1]+  Stopped                 go run main.go

ここで ps --forest が便利。個人的には初手 ps -j --forest が一番わかり易いと思った(linux環境)。

$ ps -j --forest
  PID  PGID   SID TTY          TIME CMD
 9349  9349  9349 pts/2    00:00:00 bash
 8612  8612  9349 pts/2    00:00:00  \_ go
 8665  8612  9349 pts/2    00:00:00  |   \_ main
 8795  8795  9349 pts/2    00:00:00  \_ ps

なるほどたしかにgo runはmain.goのprocessを生成している。そんなわけでgo runで立ち上げたprocessをCtrl cなどで終わらせようとした時にもたつくような感じがあったりするわけっぽい(体感的なものかもだけど)。ちなみにたまたまこの記事を書く時に調べて --forest オプションの存在を知った。ps奥が深い。

ここでpid(process id)の他にpgid(process group id)の方に注目、go run側のprocessを殺してもそのprocessが生成しているsub processまで殺せない場合があるかもしれない、幸いpgidは一緒なので一気にprocessをkillしたい場合にはpgidを指定して実行してあげるのが良さそう。

$ pkill -TERM -g 8612
$ fg
go run main.go  (wd: ~/venvs/my/individual-sandbox/daily/20190806/example_go/03fileserver)
Terminated

ところでこのページを読んでkillでもpgidを指定できることを知った(それとは関係なくプロセスのことをあまり知らないひとの勉強にこのweb book(?)良さそうだなーと想ったりした)。

たしかにmanにも書いてある。kill -<pgid>でできたんだ。今までpkillを使っていた。

$ man kill
...
              -n     where  n  is larger than 1.  All processes in process group n are signaled.  When
                     an argument of the form '-n' is given, and it is meant to denote a process group,
                     either  a  signal  must be specified first, or the argument must be preceded by a
                     '--' option, otherwise it will be taken as the signal to send.

pstree, pkill

ところでps, killだけでできそうならpstree, pkill不要じゃんとかおもったりもしたんだけれど。イメージ的にこういう認識。

  • ps, kill -- よりprimitive。詳細が異なる
  • pstree, pkill, (pgrep) -- より抽象度が高め。便利(?)

どうやらmacの方のpsには--forestオプションの存在が無いらしい。そういう意味ではpstreeでの確認方法も知っておくと良いかもしれない。デフォルトではスレッド自体も表示してしまって邪魔なので-Tオプションで無視するようにしている。pid,pgidは知りたいですよね。

# 実行しなおしたのでpidなどが変わっている
$ ps aux | ioknife rest | grep "go run"
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
me       9561  0.2  0.1 780048 20996 pts/1    Sl+  16:19   0:00 go run main.go

$ pstree -T -g -p 9561
go(9561,9561)───main(9617,9561)

ちなみに逆方向も実行できて祖先方向も見れる(-s)。

$ pstree -T -g -p -s 9561
systemd(1,1)───systemd(1475,1475)───gnome-terminal-(1855,1855)───bash(1883,1883)───screen(2403+

psの--forestオプションを含めて記事などを書くときに便利だと想う。

clientがrequestしてそれが終了したらserverを終了させたい

実は元々の裏テーマとして以下の様なことがしたかった。

  • serverを立ち上げる (e.g. 冒頭のファイルサーバー)
  • clientを立ち上げる (e.g. curl, httpieなどでのrequest)
  • clientのrequestが終了したらserverを終了させたい。

これのためにioknifeにtooコマンドを作っていたりしたのだけれど。

github.com

$ ioknife too --cmd <server> --cmd <client>
Ctrl+c でSIGINT

まぁmakefileとかでも自由にやりたいよね。ということで色々考えたりしていたのだった。暫定的には以下の様なものなのだけれど。まぁterminated的なメッセージがエラー扱いなので微妙だなーと思ったりした。

Makefile

default:
  ioknife too --cmd "go run main.go" --cmd "make client"& echo $$! > x.pid && wait $$(cat x.pid)

client:
  sleep 1
  http GET :8080/go/tos.html
  pkill -TERM -g $$(ps -o pgid -p $$(cat x.pid) | ioknife rest) || echo ok

こんな感じ。

$ make
ioknife too --cmd "go run main.go" --cmd "make client"& echo $! > x.pid && wait $(cat x.pid)
[1] make     make[1]: Entering directory /$HOME/venvs/my/individual-sandbox/daily/20190806/example_go/03fileserver'
[1] make     sleep 1
[1] make     http GET :8080/go/tos.html
[1] make     <!--{
[1] make        "Title": "Terms of service"
[1] make     }-->
[1] make     
[1] make     <p>
[1] make     The Go website (the "Website") is hosted by Google.
[1] make     By using and/or visiting the Website, you consent to be bound by Google's general
[1] make     <a href="//www.google.com/intl/en/policies/terms/">Terms of Service</a>
[1] make     and Google's general
[1] make     <a href="//www.google.com/intl/en/privacy/privacy-policy.html">Privacy Policy</a>.
[1] make     </p>
[1] make     pkill -TERM -g $(ps -o pgid -p $(cat x.pid) | ioknife rest) || echo ok
[1] make      PGID
INFO:ioknife.signalhandle:send signal (Signals.SIGTERM)
make: *** [Makefile:2: default] Terminated
Terminated

さいごに

そんなわけでioknifeのtooにSIGHUPあたりを送ったら全体にSIGTERMを送るみたいな機能を付けても良いかもなーとおもったりした3。まぁ再帰的には使えないけれど。


  1. ここではログインしているシェルの意味

  2. godocのexampleにあるようなテキトウなコード

  3. 全ての立ち上げたsub processがSIGHUPでのgraceful stopに対応しているということは期待できないのでSIGTERM