簡體   English   中英

為什么 Go 不會正確殺死子進程?

[英]Why won't Go kill a child process correctly?

當 cmd 在分配的時間內完成時,以下工作正常。 但是,超時不起作用。 雖然它確實打印了"It's dead Jim" ,但它不僅沒有打印"Done waiting" ,而且該進程實際上並沒有被終止。 它繼續運行,並且永遠不會打印"Done waiting"

func() {
    var output bytes.Buffer
    cmd := exec.Command("Command", args...)
    cmd.Dir = filepath.Dir(srcFile)
    cmd.Stdout, cmd.Stderr = &output, &output
    if err := cmd.Start(); err != nil {
        return err
    }
    defer time.AfterFunc(time.Second*2, func() {
        fmt.Printf("Nobody got time fo that\n")
        if err := cmd.Process.Signal(syscall.SIGKILL); err != nil {
            fmt.Printf("Error:%s\n", err)
        }
        fmt.Printf("It's dead Jim\n")
    }).Stop()
    err := cmd.Wait()
    fmt.Printf("Done waiting\n")
}()

我不認為它應該有什么不同,但是對於值得的命令是go test html 它超時的原因是因為我在運行它之前注入了一個導致無限循環的錯誤。 為了增加混亂,我嘗試使用go test net運行它。 有一個超時,它工作正常。

看起來問題在於 cmd.Process.Kill() 不會殺死子進程。 在子進程上看到這個類似的問題Process.Kill()

我在這個線程中找到了一個解決方案https://groups.google.com/forum/#!topic/golang-nuts/XoQ3RhFBJl8

cmd := exec.Command( some_command )
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Start()

pgid, err := syscall.Getpgid(cmd.Process.Pid)
if err == nil {
    syscall.Kill(-pgid, 15)  // note the minus sign
}

cmd.Wait()

作為一個警告,這幾乎肯定不會跨平台工作 - 我目前在 OSX Yosemite 上,我願意打賭它也適用於大多數 Linux,但我對 BSD 了解得不夠有意見,我懷疑它是否適用於 Windows。

僅供參考,我也會把我的 Windows 解決方案放在這里:

func kill(cmd *exec.Cmd) error {
    kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
    kill.Stderr = os.Stderr
    kill.Stdout = os.Stdout
    return kill.Run()
 }

我不確定它是何時添加的,但是從 Go 1.11 開始,您可以將子Pdeathsig上的Pdeathsig設置為syscall.SIGKILL 當父母退出時,這將殺死孩子。

cmd, _ := exec.Command("long-running command")
cmd.SysProcAttr = &syscall.SysProcAttr{
    Pdeathsig: syscall.SIGKILL,
}
cmd.Start()

os.Exit(1)

cmd應該在退出時被殺死。

您的調用進程可以使用 setid 在 posix 系統上創建一個新會話。 當您執行以下代碼時,您的代碼將成為流程組組長,如果(現在還不是)。 當你殺死進程組組長時,孩子們也會死。 至少,這是我的經驗。

cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
cmd.Start()
time.Sleep(5)
if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {
        log.Println("failed to kill: ", err)
}

Go 的 defer 語句會在執行 defer 的函數返回之前安排一個函數調用(延遲函數)立即運行。

所以推遲之后的事情

defer time.AfterFunc(time.Second*2, func() {
    fmt.Printf("Nobody got time fo that\n")
    cmd.Process.Kill()
    fmt.Printf("It's dead Jim\n")
}).Stop()

除非 func() 結束,否則不會執行。 因此,如果“cmd.Wait()”永遠不會結束,則永遠不會執行“time.AfterFunc()”。

從 defer 中刪除 "time.AfterFunc(...)" 可以解決這個問題,因為 "time.AfterFunc" 可以等待持續時間過去,然后在它自己的 goroutine 中調用 f。

這是一個工作版本。 我在我的 ubuntu 盒子中進行了測試,它可以工作。 將源另存為wait.go

package main

import "os/exec"
import "time"
import "bytes"
import "fmt"


func main() {
    var output bytes.Buffer
        cmd := exec.Command("sleep", "10s")
        cmd.Stdout, cmd.Stderr = &output, &output
        if err := cmd.Start(); err != nil {
                fmt.Printf("command start error\n")
                return
        }
        time.AfterFunc(time.Second*2, func() {
                fmt.Printf("Nobody got time for that\n")
                cmd.Process.Kill()
                fmt.Printf("It's dead Jim\n")
        })
        cmd.Wait()
        fmt.Printf("Done waiting\n")
}

運行命令:

time go run wait.go

輸出:

Nobody got time for that
It's dead Jim
Done waiting

real    0m2.481s
user    0m0.252s
sys 0m0.452s

正如@James Henstridge 所說,上述理解是不正確的。 其實我對 defer 的理解並不全面。 另一半是“延遲執行時評估延遲函數的參數(如果函數是方法,則包括接收器)”。 所以定時器是在執行 defer 時真正創建的,因此定時器會超時。

問題實際上是為什么該進程不能被殺死。 我檢查了 go 的 pkg 代碼,它在 *nix 之類的系統中發送一個 SIGKILL 來終止進程。 SIGKILL 不能被阻止和忽略。 所以它可能是其他可能性,例如進程本身處於TASK_UNINTERRUPTIBLE狀態。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM