在Go代码中,如何在超时时终止一个进程及其子进程?

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

How do you kill a process and its children on a timeout in Go code?

问题

我有一个情况,需要在一段时间后终止一个进程。我启动了这个进程,然后:

case <-time.After(timeout):
    if err := cmd.Process.Kill(); err != nil {
        return 0, fmt.Errorf("无法终止进程:%v", err)
    }

终止了该进程。但是它只终止了父进程,而没有终止主进程启动的5-10个子进程。我还尝试创建一个进程组,然后执行:

syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)

来终止主进程和孙子进程,但是没有起作用。是否有其他方法可以终止这些进程?

英文:

I have a situation where I need to kill a process after some time. I start the process and then:

case &lt;-time.After(timeout):
		if err := cmd.Process.Kill(); err != nil {
			return 0, fmt.Errorf(&quot;Failed to kill process: %v&quot;, err)
		}

kills the process. But it only kills the parent process not the 5-10 child processes that main process starts. I also tried creating a process group and then doing:

syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)

to kill main and grandchild processes, but not working. Is there any other way I can kill the processes.

答案1

得分: 4

我认为这是你需要的代码:

cmd := exec.Command(command, arguments...)

// 这里设置了一个进程组,我们稍后会杀掉它
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

if err := cmd.Start(); err != nil {
    return err
}

// 使用带缓冲的通道很重要,这样goroutine在函数返回后不会被阻塞和停留
done := make(chan error, 1)

go func() {
    done <- cmd.Wait()
}()

select {
case err := <-done:
    // 如果没有错误,err将为nil
    return err
case <-time.After(time.Second):
    // 我们在上面创建了一个进程组,在这里杀掉它
    pgid, err := syscall.Getpgid(cmd.Process.Pid)
    if err != nil {
        return err
    }
    // 注意负号
    if err := syscall.Kill(-pgid, 15); err != nil {
        return err
    }
    return fmt.Errorf("超时")
}

希望对你有帮助!

英文:

I think this is what you need:

cmd := exec.Command(command, arguments...)

// This sets up a process group which we kill later.
cmd.SysProcAttr = &amp;syscall.SysProcAttr{Setpgid: true}

if err := cmd.Start(); err != nil {
	return err
}

// buffered chan is important so the goroutine does&#39;t
// get blocked and stick around if the function returns
// after the timeout
done := make(chan error, 1)

go func() {
	done &lt;- cmd.Wait()
}()

select {
case err := &lt;-done:
	// this will be nil if no error
	return err
case &lt;-time.After(time.Second):
	// We created a process group above which we kill here.
	pgid, err := syscall.Getpgid(cmd.Process.Pid)
	if err != nil {
		return err
	}
	// note the minus sign
	if err := syscall.Kill(-pgid, 15); err != nil {
		return err
	}
	return fmt.Errorf(&quot;Timeout&quot;)
}

答案2

得分: 0

不清楚你是否控制这些子进程。如果是的话,你可以考虑使用以下Linux特性(你也没有说明它是否特定于某个操作系统)。

这行代码请求内核在父进程死亡时向子进程发送SIGHUP信号。这样,你的Go进程只需杀死父进程,它就会自动杀死所有子进程。而且,它从不失败!内核在这方面做得非常好。

prctl(PR_SET_PDEATHSIG, SIGHUP);

当然,如果只是这样做,会存在竞争条件。也就是说,当子进程调用这个prctl()函数时,父进程可能已经死亡,此时子进程需要立即退出。

if(getppid() != parent_pid)
{
    exit(1);
}

为了避免竞争条件,完整的代码如下:

// 必须在fork()调用之前发生
const pid_t parent_pid = getpid();

const pid_t child_pid = fork();

if(child_pid != 0)
{
    // fork()失败(child_pid == -1)或成功(一个实际的PID)
    ...
    return;
}

prctl(PR_SET_PDEATHSIG, SIGHUP);

if(getppid() != parent_pid)
{
    exit(1);
}

注意:在这种情况下,使用SIGHUP是惯例。你可能还想考虑其他信号,特别是如果子进程处理管道/套接字(在这种情况下,你可能会忽略SIGHUP!)或者出于其他原因需要处理SIGHUP

现在,如果你无法控制子进程的代码...你可以尝试从你的Go应用程序中逐个搜索并杀死每个子进程,然后杀死父进程。然而,你总是会遇到一个无法避免的竞争条件,除非你可以阻止整个子进程树创建新进程。如果你能做到这一点,那么只需要注册所有这些子进程的PID,并逐个杀死它们。

当然,如果你能创建一个进程组,那就更好了。像上面的SIGHUP一样,杀死一个进程组的所有成员是由内核完成的,它不会漏掉任何进程。

英文:

It is not clear whether you have control of those child processes. If so, you could consider using the following Linux feature (you also don't say whether it's specific to an OS).

This line of code asks the kernel to send a SIGHUP to the children when the parent's die. That way your Go process can just kill the parent and it will automatically kill all the children. Not only that, it never fails! The kernel is really good on that one.

prctl(PR_SET_PDEATHSIG, SIGHUP);

Of course, there is a race condition if you do just that. That is, by the time the child calls this prctl() function, the parent may have died already in which case the child needs to exit immediately.

if(getppid() != parent_pid)
{
    exit(1);
}

So the complete code to avoid the race condition is:

// must happen before the fork() call
const pid_t parent_pid = getpid();

const pid_t child_pid = fork();

if(child_pid != 0)
{
    // fork() failed (child_pid == -1) or worked (an actual PID)
    ...
    return;
}

prctl(PR_SET_PDEATHSIG, SIGHUP);

if(getppid() != parent_pid)
{
    exit(1);
}

Note: it is customary to use SIGHUP for this situation. You may want to consider other signals too, especially if the children deal with pipes/sockets (in which case you are likely to ignore SIGHUP!) or need to handle SIGHUP for other reasons.

Now if you do not have any control over the code of the children processes... you could try to kill each one from your Go application by searching all the children, killing them one by one, and then kill the parent process. However, you always have a race condition that you can't avoid unless you can prevent that whole tree of children from creating new processes. If you can do that, then it's just a matter of registering the PID of all those children and killing them one by one.

Of course, if you can create a group, much better. Like the SIGHUP above, killing all the members of a group is done by the kernel and it won't miss any processes.

huangapple
  • 本文由 发表于 2015年10月3日 19:03:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/32921792.html
匿名

发表评论

匿名网友

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

确定