Ensure executables called in Go Process get killed when Process is killed

后端 未结 5 1588
庸人自扰
庸人自扰 2021-01-02 06:42

I have some code that in Go (golang), has a few different threads running a separate executable. I want to ensure that if a user kills my process in Go, that I have a way of

相关标签:
5条回答
  • 2021-01-02 07:36

    I would suggest you to use unix.Prctl.

    flag := unix.SIGHUP
    if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(flag), 0, 0, 0); err != nil {
        return
    }
    

    A detailed example is below, new.go for child process and main.go as parent process.

    //new.go
    package main
    
    func main() {
        flag := unix.SIGHUP
    
        if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(flag), 0, 0, 0); err != nil {
        return
        }
    
        f, _ := os.Create("./dat2")
        defer f.Close()
        i := 0
        for {
            n3, _ := f.WriteString("writes " + string(i) + "\n")
            fmt.Printf("wrote %d bytes\n", n3)
            f.Sync()
            i += 2
            time.Sleep(2 * time.Second)
        }
    
        for {
            n3, _ := f.WriteString("newwrites\n")
            fmt.Printf("wrote %d bytes\n", n3)
            f.Sync()
            time.Sleep(2 * time.Second)
        }
    }
    
    
    //main.go
    package main
    
    import "os/exec"
    
    func main() {
        commandA := exec.Command("./new")
        commandA.Start()
        commandB := exec.Command("./new")
        commandB.Start()
        for {
        }
    }
    
    0 讨论(0)
  • 2021-01-02 07:40

    If you're on Windows, you might find this snippet helpful:

    package main
    
    import (
        "os"
        "os/exec"
        "unsafe"
    
        "golang.org/x/sys/windows"
    )
    
    // We use this struct to retreive process handle(which is unexported)
    // from os.Process using unsafe operation.
    type process struct {
        Pid    int
        Handle uintptr
    }
    
    type ProcessExitGroup windows.Handle
    
    func NewProcessExitGroup() (ProcessExitGroup, error) {
        handle, err := windows.CreateJobObject(nil, nil)
        if err != nil {
            return 0, err
        }
    
        info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
            BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
                LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
            },
        }
        if _, err := windows.SetInformationJobObject(
            handle,
            windows.JobObjectExtendedLimitInformation,
            uintptr(unsafe.Pointer(&info)),
            uint32(unsafe.Sizeof(info))); err != nil {
            return 0, err
        }
    
        return ProcessExitGroup(handle), nil
    }
    
    func (g ProcessExitGroup) Dispose() error {
        return windows.CloseHandle(windows.Handle(g))
    }
    
    func (g ProcessExitGroup) AddProcess(p *os.Process) error {
        return windows.AssignProcessToJobObject(
            windows.Handle(g),
            windows.Handle((*process)(unsafe.Pointer(p)).Handle))
    }
    
    func main() {
        g, err := NewProcessExitGroup()
        if err != nil {
            panic(err)
        }
        defer g.Dispose()
    
        cmd := exec.Command("notepad.exe", "noname")
        if err := cmd.Start(); err != nil {
            panic(err)
        }
    
        if err := g.AddProcess(cmd.Process); err != nil {
            panic(err)
        }
    
        if err := cmd.Wait(); err != nil {
            panic(err)
        }
    }
    

    Original link: https://gist.github.com/hallazzang/76f3970bfc949831808bbebc8ca15209

    0 讨论(0)
  • 2021-01-02 07:42

    One possible strategy is to keep a list of processes you're running in a global array var childProcesses = make([]*os.Process, 0) and append to it every time you start a process.

    Have your own Exit function. Make sure that you never call os.Exit anywhere in your code, and always call your own Exit function instead. Your Exit function will kill all the childProcesses

    for _, p := range childProcesses {
        p.Kill()
    }
    

    Handle signals so they go through your own exit function, for example by doing this during initialization (somewhere near the top of your main function)

    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT)
    goUnsafe(func() {
        var signal = <-sigs
        log.Println("Got Signal", signal)
        Exit(0)
    })
    
    0 讨论(0)
  • 2021-01-02 07:50

    The only ways to ensure that the child process is killed, is to start it in the same process group, and kill the process group as a whole, or set Pdeadthsig in the syscall.SetProcAddr.

    You can setup a signal handler for common signals like SIG_INT and SIG_TERM, and kill your child processes before exiting, but since you can't catch SIG_KILL that's often not worth the effort.

    See: Panic in other goroutine not stopping child process

    cmd := exec.Command("./long-process")
    
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Pdeathsig: syscall.SIGTERM,
    }
    
    0 讨论(0)
  • 2021-01-02 07:50

    I have some code that in Go (golang), has a few different threads running a separate executable. I want to ensure that if a user kills my process in Go, that I have a way of killing the executables I've called, is there a way to do that?

    It is not clear to me what you mean by "different threads running." I will assume:

    • Those threads belong to "executables I've called"
    • Those threads have not necessarily written in golang
    • You cannot control their execution (i.e., you unable to change their code)

    In this sense, we are talking about to create a process group and ensure that when you kill a process, every other process in that group must stop too. This mechanism is Operating System dependent.

    In the current version, there are two possibilities (see package x/sys):

    • Unix: it is about to setgid(2)

    • Windows: it is about to CreateProcess. Please, refer to the MS Windows documentation CreateProcess. You must pass the CREATE_NEW_PROCESS_GROUP flag (see)

    0 讨论(0)
提交回复
热议问题