确保在终止进程时,Go进程中调用的可执行文件也被终止。

huangapple go评论89阅读模式
英文:

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

问题

我有一些 Go 代码,其中有几个不同的线程在运行一个独立的可执行文件。我想确保如果用户在 Go 中终止了我的进程,我有一种方法来终止我调用的可执行文件,有办法做到吗?

英文:

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?

答案1

得分: 17

确保子进程被终止的唯一方法是在同一进程组中启动它,并将整个进程组一起终止,或者在syscall.SetProcAttr中设置Pdeathsig

您可以为常见信号(如SIG_INTSIG_TERM)设置信号处理程序,在退出之前终止子进程,但由于无法捕获SIG_KILL,这通常不值得努力。

参考:https://stackoverflow.com/questions/34095254/panic-in-other-goroutine-not-stopping-child-process/34095869#34095869

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

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

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.SetProcAttr.

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: https://stackoverflow.com/questions/34095254/panic-in-other-goroutine-not-stopping-child-process/34095869#34095869

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

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

答案2

得分: 1

如果你使用的是Windows系统,你可能会发现以下代码片段很有帮助:

package main

import (
	"os"
	"os/exec"
	"unsafe"

	"golang.org/x/sys/windows"
)

// 我们使用这个结构体通过不安全的操作从os.Process中获取进程句柄(未导出的)
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

英文:

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

答案3

得分: 1

一种可能的策略是在一个全局数组var childProcesses = make([]*os.Process, 0)中保持一个正在运行的进程列表,并在每次启动进程时将其添加到列表中。

拥有自己的Exit函数。确保在代码中从不调用os.Exit,而是始终调用自己的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)
})
英文:

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 = &lt;-sigs
log.Println(&quot;Got Signal&quot;, signal)
Exit(0)
})

答案4

得分: 0

我建议你使用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 {
    }
}
英文:

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(&quot;./dat2&quot;)
defer f.Close()
i := 0
for {
n3, _ := f.WriteString(&quot;writes &quot; + string(i) + &quot;\n&quot;)
fmt.Printf(&quot;wrote %d bytes\n&quot;, n3)
f.Sync()
i += 2
time.Sleep(2 * time.Second)
}
for {
n3, _ := f.WriteString(&quot;newwrites\n&quot;)
fmt.Printf(&quot;wrote %d bytes\n&quot;, n3)
f.Sync()
time.Sleep(2 * time.Second)
}
}
//main.go
package main
import &quot;os/exec&quot;
func main() {
commandA := exec.Command(&quot;./new&quot;)
commandA.Start()
commandB := exec.Command(&quot;./new&quot;)
commandB.Start()
for {
}
}

答案5

得分: 0

我有一些Go(golang)代码,其中有几个不同的线程在运行一个单独的可执行文件。我想确保如果用户在Go中终止我的进程,我有一种方法来终止我调用的可执行文件,有办法做到这一点吗?

我不太清楚你所说的"不同的线程运行"是什么意思。我会假设:

  • 这些线程属于"我调用的可执行文件"
  • 这些线程不一定是用golang编写的
  • 你无法控制它们的执行(即,无法更改它们的代码)

在这种情况下,我们要创建一个进程组,并确保当你终止一个进程时,该组中的每个其他进程也必须停止。这个机制取决于操作系统。

在当前版本中,有两种可能性(参见x/sys包):

英文:

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

huangapple
  • 本文由 发表于 2016年1月12日 04:58:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/34730941.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定