Go os.exec - arosh/arosh.github.com GitHub Wiki

外部プロセスを起動する際の注意点

  1. タイムアウト時には「まず SIGKILL 以外のプロセスを送って、それでも止まらなかったら SIGKILL」を実装すべき
  2. 子プロセスが起動した孫プロセスも一緒に止めるようにできているか?

1 については Go 1.20 の WaitDelay の導入により解決している。2 については今でも自明な解決には至っていない。

GNU timeout では自分自身(timeout コマンド)のプロセスグループに対して SIGTERM や SIGKILL を送って自分自身のプロセスも一緒に爆散するという方式が使われている。一方、Songmu/timeout では Setpgid=true を設定することで子孫プロセスを新たなプロセスグループ配下で起動してプロセスグループに対してシグナルを送るということをやっている。

ただし Songmu/timeout には以下のような注意点もある。

  • 子プロセスが別の PGID を持ってしまうので、親プロセスを Ctrl+C してもそのままでは子プロセスが止まらない(プロセスグループが異なるので)。親プロセス側に適切なシグナルハンドリングを実装して子プロセスを止める必要がある

  • 呼び出した子プロセスが孫プロセスを呼び出すときについても👆と同じ問題がある。つまり子プロセスが孫プロセスを起動する処理を備えているなら、Setpgid=true を 使っていない か、Setpgid=true を 使っているけれども適切にシグナルハンドリングしている かのどちらかを期待できなければいけない(これは GNU timeout にもある問題)

  • そもそも Setpgid=true すると問題が起きるようなソフトウェアもある(らしい)

  • https://junkyard.song.mu/slides/gocon2019-spring/

  • https://makiuchi-d.github.io/2020/05/10/go-kill-child-process.ja.html

  • https://techblog.szksh.cloud/go-timeout/

  • https://diary.sorah.jp/2025/01/09/executor-and-sidecar

避けられるなら避けたい関数

Start+Wait → Run

Start() 後に Wait() を忘れるとリソースがリークする。Run() で済むならこちらにしよう。

.Stdout, .Stderr

もし stdout をキャプチャすることが主目的なら .Output() に任せるのが簡単。stdout, stderr を bytes.Buffer に書き出した結果を (out, err) で返してくれる。

CombinedOutput

stdout と stderr を混ぜてキャプチャする。これが便利な状況はあんまりない。

StdinPipe, StdoutPipe, StderrPipe

それぞれ Stdin, Stdout, Stderr が使えるならそっちのほうがハマりどころが少ない。Hoge と HogePipe の違いは、Stdin には io.Reader を渡さないといけないところ、StdinPipe() では戻り値の io.Writer に書き込む、のように、使うインターフェースが逆になっているというものである。実装としては os.Pipe を使ってラップしているだけである。

ハマりどころとしては

  • StdinPipe() を使うときは書き終えた時に .Close() してやらないといけない
  • StdoutPipe(), StderrPipe() を使うときは .Run().Wait() する前に出力を読み切る必要がある

https://hackmysql.com/post/reading-os-exec-cmd-output-without-race-conditions/