Убедитесь, что исполняемые файлы, вызванные в процессе Go, будут убиты, когда процесс будет убит

У меня есть код, который в Go (golang) имеет несколько разных потоков, выполняющих отдельный исполняемый файл. Я хочу убедиться, что если пользователь убивает мой процесс в Go, у меня есть способ убить исполняемые файлы, которые я вызвал, есть ли способ сделать это?

Ответ 1

Единственный способ убедиться, что дочерний процесс завершен, - это запустить его в той же группе процессов и полностью уничтожить группу процессов или установить Pdeadthsig в syscall.SetProcAddr.

Вы можете настроить обработчик сигналов для общих сигналов, таких как SIG_INT и SIG_TERM, и убить ваши дочерние процессы перед выходом, но так как вы не можете поймать SIG_KILL, который часто не стоит усилий.

Смотрите: Паника в другой программе не останавливает дочерний процесс

cmd := exec.Command("./long-process")

cmd.SysProcAttr = &syscall.SysProcAttr{
    Pdeathsig: syscall.SIGTERM,
}

Ответ 2

Одной из возможных стратегий является сохранение списка процессов, которые вы выполняете, в глобальном массиве var childProcesses = make([]*os.Process, 0) и добавление к нему при каждом запуске процесса.

Имейте свою собственную функцию Exit. Убедитесь, что вы никогда не вызываете os.Exit где-либо в вашем коде, и вместо этого всегда вызывайте свою собственную функцию Exit. Ваша функция выхода уничтожит все childProcesses

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

Обрабатывайте сигналы так, чтобы они проходили через вашу собственную функцию выхода, например, делая это во время инициализации (где-то в верхней части вашей основной функции)

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)
})

Ответ 3

Я предлагаю вам использовать unix.Prctl.

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

Ниже приведен подробный пример: new.go для дочернего процесса и main.go как родительский процесс.

//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 {
    }
}

Ответ 4

Если вы работаете в Windows, этот фрагмент может оказаться полезным:

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)
    }
}

Исходная ссылка: https://gist.github.com/hallazzang/76f3970bfc949831808bbebc8ca15209

Ответ 5

  У меня есть некоторый код, который в Go (golang), имеет несколько разных потоков, выполняющих отдельный исполняемый файл. Я хочу убедиться, что если пользователь убивает мой процесс в Go, у меня есть способ убить вызванные мной исполняемые файлы, есть ли способ сделать это?

Мне не понятно, что вы имеете в виду под "разными потоками". Я приму:

  • Эти потоки принадлежат "исполняемым файлам, которые я назвал"
  • Эти темы не обязательно написаны на Голанге
  • Вы не можете контролировать их выполнение (т.е. вы не можете изменить их код)

В этом смысле мы говорим о том, чтобы создать группу процессов и обеспечить, чтобы при завершении процесса все остальные процессы в этой группе также останавливались. Этот механизм зависит от операционной системы.

В текущей версии есть две возможности (см. пакет x/sys):