Go その3 Advent Calendar 2015 - Qiitaの最終日25日目の記事になります。かなり遅いぎりぎりの時間ですみません。
God.rb -> Kami
Rubyに God というプロセスモニタリングツールがあります。DSLで書いた設定に応じてプロセスを起動・監視し、対象が落ちていたら、立ち上げ直すような機能を持っています。Rubyも良い言語ではありますが、Goはプロセスモニタリングツールを記述するのに適した言語だと思います。
ということでGodのアイデアを元にGoでKamiというアプリケーションを作り始めました。Electronを探す日常 - Qiitaという、別のアドベントカレンダーの記事を書いていたなぜか20日目の記事のはずが5日遅れという恐ろしい事態になっていたため、まだ作り始めたところです。
どうやってKamiを作るのか
Goはワンバイナリのポータブルなアプリケーションを作成できるので、プロセスモニターに最適だと思うんですが、標準ライブラリはあまりOSに踏み込んだ機能は実装されていません。たとえばDaemon化機能なんかは望まれているものの標準にはありません。今回は時間の都合でDaemon化は棚上げにしておきます。
子プロセスの起動・制御などを見ていきましょう。
exec.Command
細かく制御可能な子プロセス起動方法となるとos/exec
パッケージのexec.Command
を使うことになります。
c := exec.Command("ls", "-al")
見ての通り、コマンドと引数を渡す形のインターフェースになります。戻り値は*Cmdで、Path, Args, Env, Dirなどのメンバーを持ちます。
c.Env
Env
メンバーを操作することで任意の環境変数を持った状態で子プロセスが起動します。
c.Env = []string("HOGE=FUGA")
c.Env = os.Environ()
c.Env = append(os.Environ(), "HOGE=FUGA")
注意しなければならないのは従来の環境変数を維持するためには明示的にos.Environ()
をたたき、現時点の環境変数を取得しておく必要があります。そこに追加なり修正なりする形で"NAME=VALUE"形式の文字列で指定する必要があります。
c.Dir
Dir
メンバーをいじることで、実行時ディレクトリを変更することができます。未指定の場合はカレントは変わらず、そして相対指定の場合は親プロセス(つまり自分自身)のカレントからの相対になります。
c.Dir = "/"
実行する
exec.Command()を実行するだけではただ初期化しただけに過ぎず、プロセスはまだ起動していません。Run()
をする必要があります。
c.Run()
実は*Cmdには、Process
というメンバーもあるんですが、run
実行後じゃないと初期化されません。
fmt.Println(c.Process.Pid)
Process
は、さらにPid
というメンバーを持っていてPIDを取得することができます。
終了待ちをする
Process
にはもう一つ重要な役割があります。Process.Wait()
で、子プロセスの終了待ちを行います。ブロッキングをするのでGoRoutinで動かすのがよいかもしれません。
state, err := c.Process.Wait()
if err != nil {
fmt.Fprintf(os.Stderr, "? process wait error: %s\n", err)
return
} else {
// 終了した
}
stateはProcessStateで、終了したかを判定するExited()
なんかを持ちますが、Wait()でブロッキングをしているため意味があるのかは疑問です。ほかにいくつかあるメソッドにより終了時ステータス、実行時間などの情報を取得できます。
pty
UNIXのプロセスはTTYと密着しすぎているため、PTY(擬似端末 - Wikipedia)という仕組みを使って引きはがすことにします。GoでPTYを使うには、kr/ptyが良さそうなのですが、一つだけ問題があって、stdoutもstderrもごちゃまぜにしてしまっているので、forkしてその点を修正してみたものがerukiti/ptyになります。今回はこれを使います。
f, e, err := pty.Start2(c)
c.Run()の代わりにpty.Start2(c)をたたきます。fが標準入出力、eが標準エラー出力になります。プロセスモニタとしては出力内容はさほど気にすることはないのですが、godなんかは出力結果をファイルに出してくれるような機能があるので、Kamiにも実装してみたいと思います。
outfile, err := os.Create("cocoa.txt")
if err != nil {
panic(err)
}
defer outfile.Close()
writer := bufio.NewWriter(outfile)
defer writer.Flush()
go io.Copy(writer, f)
そんなわけで作成中です
erukiti/kamiで、God.rbに変わるツールを作り上げたい、またその過程で子プロセス管理用のライブラリを作ってみたいと思っています。
それではごきげんよう。