Open4

Goで起こすプロセスの標準出力をリアルタイムに読みたい時

NoboNoboNoboNobo
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{}
NoboNoboNoboNobo

例えばプロセスがなんかしら標準出力になにか出しているうちは見守りで、
何も出さなくなったらキルしたいときー。

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)
}
NoboNoboNoboNobo

いまのところこれがプロセスの標準出力をリアルタイムに読みつつ中断もちゃんとやるベストな書き方かなーと思っているんだけど、もっといい方法があったら教えて!

NoboNoboNoboNobo

綺麗に原因掴めていないんだけど、対象のプロセスの標準出力がBufferedだったりUnbufferedだったりするのがややこしかった。あとBufferedでいつFlushするのかも実装依存だったりする。
また、io.Pipeやcommand.StdoutPipeなどは読みと書きが別のgoroutineになるようにする必要がある?

なんにせよ上のおすすめなら直感的に期待通りに動いてくれたのでおすすめ。