Open4
Goで起こすプロセスの標準出力をリアルタイムに読みたい時
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
r, err := cmd.StdoutPipe()
...
ってやりがちだけど、これ意外とナイーブでいろんな条件でプロセスの標準出力をブロックしてしまう。
おすすめは
type output struct {}
func (o *output) Write(b []byte) (int, error) {
...
}
ていう定義をおこしておいて、以下のように差し替えておくのが一番素直に動く。
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Stdout = &output{}
例えばプロセスがなんかしら標準出力になにか出しているうちは見守りで、
何も出さなくなったらキルしたいときー。
type output struct {
timer *time.Timer
}
func (o *output) Write(b []byte) (int, error) {
msg := strings.TrimSpace(string(b))
if len(msg) > 0 {
log.Print(msg)
}
o.timer.Reset(30 * time.Second)
return len(b), nil
}
というようにタイマーリセットを入れておき、以下のようにタイマーが発火したらプロセスを中断するようにしておく。
ctx, cancel := context.WithCancel(ctx)
defer cancel()
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Stdout = &output{timer: time.AfterFunc(30*time.Second, cancel)}
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
いまのところこれがプロセスの標準出力をリアルタイムに読みつつ中断もちゃんとやるベストな書き方かなーと思っているんだけど、もっといい方法があったら教えて!
綺麗に原因掴めていないんだけど、対象のプロセスの標準出力がBufferedだったりUnbufferedだったりするのがややこしかった。あとBufferedでいつFlushするのかも実装依存だったりする。
また、io.Pipeやcommand.StdoutPipeなどは読みと書きが別のgoroutineになるようにする必要がある?
なんにせよ上のおすすめなら直感的に期待通りに動いてくれたのでおすすめ。