43

I want to implement a "process wrapper" in Go. Basically what it will do, is launch a process (lets say a node server) and monitor it (catch signals like SIGKILL, SIGTERM ...)

I think the way to do is to launch the node server in a go routine using syscall.Exec:

func launchCmd(path string, args []string) {
  err := syscall.Exec(path, args, os.Environ())
  if err != nil {
    panic(err)
  }
}

Then I'd like to catch every possible signals generated by the command executed by syscall. I'm pretty new to Go, any help would be appreciated.

rmonjo
  • 2,675
  • 5
  • 30
  • 37

3 Answers3

71

There are three ways of executing a program in Go:

  1. syscall package with syscall.Exec, syscall.ForkExec, syscall.StartProcess
  2. os package with os.StartProcess
  3. os/exec package with exec.Command

syscall.StartProcess is low level. It returns a uintptr as a handle.

os.StartProcess gives you a nice os.Process struct that you can call Signal on. os/exec gives you io.ReaderWriter to use on a pipe. Both use syscall internally.

Reading signals sent from a process other than your own seems a bit tricky. If it was possible, syscall would be able to do it. I don't see anything obvious in the higher level packages.

To receive a signal you can use signal.Notify like this:

sigc := make(chan os.Signal, 1)
signal.Notify(sigc,
    syscall.SIGHUP,
    syscall.SIGINT,
    syscall.SIGTERM,
    syscall.SIGQUIT)
go func() {
    s := <-sigc
    // ... do something ...
}()

You just need to change the signals you're interested in listening to. If you don't specify a signal, it'll catch all the signals that can be captured.

You would use syscall.Kill or Process.Signal to map the signal. You can get the pid from Process.Pid or as a result from syscall.StartProcess.

LeGEC
  • 46,477
  • 5
  • 57
  • 104
Luke
  • 13,678
  • 7
  • 45
  • 79
  • Thx I will try it. The idea is to have this wrapper monitored by upstart, it will just be used to keep track of what happened – rmonjo Aug 07 '13 at 15:07
  • Ok I can catch signals that are inputed to the program (if I do ^C for example) but can't get signals generated by my program executed by syscall. Any thought ? – rmonjo Aug 07 '13 at 15:46
  • @rmonjo You're trying to catch a signal sent *from* your program or *to*? – Luke Aug 07 '13 at 16:59
  • I want to catch signals sent from the program I execute in the `syscall`. I tried your solution, when I use `syscall.Exec` it seems that the program is forked and go just exits. Using `os/exec` however, I can catch signals such as child exit. – rmonjo Aug 07 '13 at 19:41
  • @rmonjo `signal.Notify` catches signals sent to your program. Catching from may be a bit tricky. Refined answer a bit. – Luke Aug 07 '13 at 21:56
39

You can use signal.Notify :

import (
"os"
"os/signal"
"syscall"
)

func main() {
    signalChannel := make(chan os.Signal, 2)
    signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
    go func() {
        sig := <-signalChannel
        switch sig {
        case os.Interrupt:
            //handle SIGINT
        case syscall.SIGTERM:
            //handle SIGTERM
        }
    }()
    // ...
}
Baba
  • 94,024
  • 28
  • 166
  • 217
1
func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSEGV)
    for {
        s := <-c
        switch s {
        case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
            return
        case syscall.SIGHUP:
        case syscall.SIGSEGV:
        default:
            return
        }
    }
}

Mason
  • 11
  • 3
  • while this may answer the question, I think it would be much more useful with a little bit of explanation (also for future readers!). Why do you catch which signals? Why do you do what in which case? – FObersteiner Jul 11 '22 at 08:04