説教臭いブログ

中途半端なので、タイトル変えてみた。多分説教臭いと思う。

golang で子プロセス実行するときに、Backgroundにする(やり方が分からず・・・

Goで書いてる運用系のツールで、以下のようなことをやりたかった。

  • メインのツールと、サブツールみたいなのがある。
  • サブツールはバックグラウンドで起動しておいて、メインのツールから利用する軽いWebAPI
  • メインのツールはある程度起動/停止を繰り返す感じになる
  • メインのツール起動時にサブツール(API)が起動してなかったら、バックグラウンドで起動したい

つまり、メインのツール(M)起動時に、サブのツール(S)が起動してなかったらバックグラウンド起動しておく、ということをしたい。

プロセスの存在チェック

プロセス調べるのは、Goの標準ライブラリで無さそうなので pgrep "S" コマンドを実行して、ExitCodeで判定

package main

import (
  "bytes"
  "fmt"
  "os/exec"
  "strings"
  "syscall"
)

func main() {
  process_name := "target_tool_name"
  cmd := exec.Command("pgrep", process_name)

  cmd.Stdin = strings.NewReader("")
  var out bytes.Buffer
  cmd.Stdout = &out
  err := cmd.Run()
  if err != nil {
    e := err.(*exec.ExitError)
    s := e.Sys().(syscall.WaitStatus)
    status := s.ExitStatus()
    if status == 1 {  // exit(1) は見つからない時
      fmt.Printf("process '%s' is found")
    } else { 
      fmt.Println(err)  // なんかその他のエラー
    }
  } else {  // プロセス1つでも見つかるとエラーにならない
    fmt.Printf("process '%s' is found", process_name)
  }
}

こんな感じ。無駄もあるかもしれないけど Linux ならこで良さそう?

ExitCode 取るのが思ったより面倒だった。

プロセス実行しつつ、バックグラウンドにする

os/execCommand では、 Start() / Run() 辺りでプロセスを実行する。

Run() だと終了を待つけど、 Start() は goroutine で実行して待たない。 Wait() 使うと、プロセスの終了を待つ感じになると思う。

で、 Start() を使うとバックグラウンド実行かな、と思いきや、 自身のプロセスが終了するタイミングで Start() で実行したプロセスも終わってしまう。 (Ctrl-C で終わる場合)

grokbase.com

このサイト見ると、プロセスグループが同じだから(?)みたいなことが書いてあって、 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} で分離できそうなことが書いてある。 (と読んで思った)

けど、できなかった。

結局、 /path/to/sub/tool & でバックグラウンド実行するように shellscript を作っておいて、 その shellscript を golang から実行するようにした。

Goのメインツールのプロセスとは分離できたけど、イマイチな感じになった。

感想

もうちょっとスマートな方法ありそう。 勉強が必要そう。。。。

(追記)そう言えば supervisord 経由で起動するがどうの、という感じのことが書いてあった。