概要
よく忘れるので、ここにメモメモ。。。
たまに、Goのプログラムから他のプロセスに対してシグナルを送りたいときがあります。
以下、サンプルです。
サンプル
2つのプログラムで試しています。シグナルを送る方を sender、シグナルを受ける方を receiver としています。
Taskfile.yml
version: "3" tasks: default: cmds: - task: run-receiver - ps - task: run-sender - ps run-receiver: dir: receiver cmds: - go clean - go build -o receiver main.go - ./receiver & run-sender: dir: sender cmds: - go clean - go build -o sender main.go - ./sender
sender
os.FindProcess
を利用すると、該当のプロセスが取得できますが、この関数には pid を渡さないといけません。
sender からだと、receiverプロセスの pid は分からないので、プロセス名の一覧を取得して合致するプロセスの pid を取得する必要があります。
現在稼働中のプロセス名一覧を取得するのに go-ps などの有名なライブラリがありますが
今回のサンプルレベルだと、自分で /proc
の下を探しに行けばいいだけなので自力でやっています。
/proc
ファイルシステムを利用しているので、当然ながら windows では動きません。
//go:build !windows // シグナルを送信する側です // // # 処理手順 // // - receiver プロセスを探して pid を取得 // - 対象 pid に対して SIGTERM を送る // // REFERENCES: // - https://stackoverflow.com/questions/9030680/list-of-currently-running-process-in-go package main import ( "errors" "log" "os" "path/filepath" "strconv" "strings" "syscall" ) const ( procName = "receiver" ) var ( appLog = log.New(os.Stderr, "[sender ] >>> ", 0) ) func main() { pid, err := find(procName) if err != nil { panic(err) } if pid == -1 { panic("receiver pid not found") } proc, err := os.FindProcess(pid) if err != nil { panic(err) } appLog.Printf("send SIGTERM to receiver(%d)", pid) err = proc.Signal(syscall.SIGTERM) if err != nil { panic(err) } } func find(name string) (int, error) { var ( matches []string err error pid = -1 ) matches, err = filepath.Glob("/proc/*/exe") if err != nil { return pid, err } for _, f := range matches { real, err := os.Readlink(f) if err != nil && !errors.Is(err, os.ErrPermission) { return pid, err } if len(real) > 0 && strings.Contains(real, "receiver") { dir := filepath.Base(filepath.Dir(f)) pid, err = strconv.Atoi(dir) if err != nil { return pid, err } appLog.Printf("receiver pid is %v\n", pid) break } } return pid, nil }
receiver
//go:build !windows // シグナルを受信する側です // // # 処理手順 // // - signal.NotifyContext を利用して SIGTERM をフック // - シグナルが送信されるのを待機 // // REFERENCES: // - https://stackoverflow.com/questions/9030680/list-of-currently-running-process-in-go package main import ( "context" "log" "os" "os/signal" "syscall" "time" ) var ( appLog = log.New(os.Stderr, "[receiver] >>> ", 0) ) func main() { var ( mainCtx = context.Background() procCtx, procCxl = context.WithTimeout(mainCtx, 10*time.Second) signalCtx, signalCxl = signal.NotifyContext(procCtx, syscall.SIGTERM) ) defer procCxl() defer signalCxl() appLog.Println("wait for SIGTERM") select { case <-procCtx.Done(): appLog.Println("timeout") case <-signalCtx.Done(): appLog.Println("receive SIGTERM from sender") } }
実行結果
gitpod /workspace/try-golang (master) $ task -d examples/signal/send/ task: [run-receiver] go clean task: [run-receiver] go build -o receiver main.go task: [run-receiver] ./receiver & task: [default] ps [receiver] >>> wait for SIGTERM PID TTY TIME CMD 70 pts/3 00:00:00 bash 3856 pts/3 00:00:00 task 4016 pts/3 00:00:00 receiver 4018 pts/3 00:00:00 ps task: [run-sender] go clean task: [run-sender] go build -o sender main.go task: [run-sender] ./sender [sender ] >>> receiver pid is 4016 [sender ] >>> send SIGTERM to receiver(4016) [receiver] >>> receive SIGTERM from sender task: [default] ps PID TTY TIME CMD 70 pts/3 00:00:00 bash 3856 pts/3 00:00:00 task 4191 pts/3 00:00:00 ps
ちゃんと receiver に対して SIGTERM を送ることが出来ていますね。
参考情報
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。