いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-237 (別プロセスにシグナルを送る)(os.FindProcess, /proc)

概要

よく忘れるので、ここにメモメモ。。。

たまに、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 を送ることが出来ていますね。

参考情報

stackoverflow.com

github.com

pkg.go.dev

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。