Golang的context.WithTimeout在使用exec.CommandContext的”su -c”命令时无效。

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

Golang context.WithTimeout doesn't work with exec.CommandContext "su -c" command

问题

这段代码可以正常工作,它会打印出"Timeout":

ctx, cancel := context.WithTimeout(context.Background(), 1000 * time.Millisecond)
process := exec.CommandContext(ctx, "sleep", "5")

processOutBytes, err := process.Output()

if ctx.Err() == context.DeadlineExceeded {
    fmt.Println("Timeout")
}

但是,只是换了另一个命令的这段代码却不能正常工作。进程可能会无限运行!

ctx, cancel := context.WithTimeout(context.Background(), 1000 * time.Millisecond)
process := exec.CommandContext(ctx, "su", "-", "myuser", "-c", "python3 main.py")

processOutBytes, err := process.Output()

if ctx.Err() == context.DeadlineExceeded {
    fmt.Println("Timeout")
}

在基于ubuntu:20.04的Docker容器中运行Ubuntu 20.04。为什么会这样,如何使第二段代码正常工作?

英文:

This code works properly, it prints Timeout:

ctx, cancel := context.WithTimeout(context.Background(), 1000 * time.Millisecond)
process := exec.CommandContext(ctx, "sleep", "5")

processOutBytes, err := process.Output()

if ctx.Err() == context.DeadlineExceeded {
    fmt.Println("Timeout")
}

But this, with just another command, is not. Process can work infinitely!

ctx, cancel := context.WithTimeout(context.Background(), 1000 * time.Millisecond)
process := exec.CommandContext(ctx, "su", "-", "myuser", "-c", "python3 main.py")

processOutBytes, err := process.Output()

if ctx.Err() == context.DeadlineExceeded {
    fmt.Println("Timeout")
}

Running on Ubuntu 20.04 inside docker container based on ubuntu:20.04. Why is this so and how to make the second code work?

答案1

得分: 6

超时只适用于由exec启动的进程,它不会终止任何子进程。在你的情况下,它会终止su进程,但不会终止接下来的python3进程。

要终止由给定进程启动的所有子进程,你可以在一个新的进程组中启动它,并通过向-pid(负pid)发送SIGKILL信号来终止整个进程组,代码如下:

ctx, cancel := context.WithTimeout(context.Background(), 1000*time.Millisecond)
process := exec.CommandContext(ctx, "su", "-", "myuser", "-c", "python3 main.py")

process.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
go func() {
	<-ctx.Done()
	if ctx.Err() == context.DeadlineExceeded {
		syscall.Kill(-process.Process.Pid, syscall.SIGKILL)
	}
}()

processOutBytes, err := process.Output()
cancel()

if ctx.Err() == context.DeadlineExceeded {
	fmt.Println("Timeout")
}

请注意,依赖于syscall的代码不可移植;例如,在Windows上甚至无法编译。

英文:

The timeout only applies to the process started by exec, it won't kill any child processes. In your case it will kill the su but not the next python3 process.

To kill all children started by a given process you can start it in a new process group and kill the entire group by sending SIGKILL to -pid (negative pid), like so:

ctx, cancel := context.WithTimeout(context.Background(), 1000 * time.Millisecond)
process := exec.CommandContext(ctx, &quot;su&quot;, &quot;-&quot;, &quot;myuser&quot;, &quot;-c&quot;, &quot;python3 main.py&quot;)

process.SysProcAttr = &amp;syscall.SysProcAttr{Setpgid: true}
go func() {
	&lt;-ctx.Done()
	if ctx.Err() == context.DeadlineExceeded {
		syscall.Kill(-process.Process.Pid, syscall.SIGKILL)
	}
}()

processOutBytes, err := process.Output()
cancel()

if ctx.Err() == context.DeadlineExceeded {
	fmt.Println(&quot;Timeout&quot;)
}

Note also that the code relying on syscall isn't portable; it won't even compile on Windows, for example.

huangapple
  • 本文由 发表于 2021年5月29日 18:56:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/67750520.html
匿名

发表评论

匿名网友

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

确定