【问题标题】:Panic in other goroutine not stopping child process其他 goroutine 中的恐慌没有停止子进程
【发布时间】:2015-12-04 18:39:54
【问题描述】:

如果我(出于任何原因)退出父应用程序,我需要运行一个长时间运行的子进程并终止它。

代码如下:

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

defer cmd.Process.Kill()

if err != nil {
    log.Fatal(err)
}

var fail io.ReadCloser
fail.Close()

这里的fail 很明显

panic: runtime error: invalid memory address or nil pointer dereference

它按预期工作 - 子进程被杀死。

但这发生在 goroutine 中:

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

defer cmd.Process.Kill()

if err != nil {
    log.Fatal(err)
}

go func() {
    var fail io.ReadCloser
    fail.Close()
}()

恐慌仍然发生,但似乎 defer 没有被调用并且子进程没有被杀死。

有什么办法可以解决这个问题吗?

更新我需要一个跨平台的解决方案(至少对于 Linux 和 FreeBSD)

小例子:

infinite-loop.sh

#!/bin/bash

while true; do
  sleep 1
done

别忘了

chmod +x infinite-loop.sh

test1.go(为简洁起见省略了错误检查):

package main

import (
    "time"
    "io"
    "os/exec"
    "runtime"
)

func main() {

    cmd := exec.Command("./infinite-loop.sh")

    cmd.Start()

    defer cmd.Process.Kill()

    go func() {
        time.Sleep(100 * time.Millisecond)
        var fail io.ReadCloser
        fail.Close()
    }()

    for {
        runtime.Gosched()
    }
}

我们跑吧

ps aux | grep infinite-loop.sh | grep -v grep | wc -l; \
go run test1.go; \
ps aux | grep infinite-loop.sh | grep -v grep | wc -l


     0 <--- !!


panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x20 pc=0x2130]

goroutine 5 [running]:
main.main.func1()
.../multiline/test1.go:19 +0x30
created by main.main
.../multiline/test1.go:20 +0x9a

goroutine 1 [runnable]:
runtime.Gosched()
/usr/local/Cellar/go/1.5.1/libexec/src/runtime/proc.go:166 +0x14
main.main()
.../multiline/test1.go:23 +0x9f
exit status 2

     1 <--- !!

退出前有 0 个进程,退出后有 1 个进程。

如果你注释掉 goroutine 代码 - 它工作正常。

现在我们可以杀死它了:

kill $(ps aux | grep infinite-loop.sh | grep -v grep | awk {'print $2'})

【问题讨论】:

  • 你不想那样推迟,因为如果函数在 cmd 启动之前返回,Process 将为 nil,你会得到另一个恐慌。
  • @JimB 感谢您的通知!
  • @MarkusWMahlberg 已添加,谢谢!

标签: go


【解决方案1】:

没有自动终止子进程的跨平台解决方案。

在 Linux 上,您可以使用 pdeathsig 功能:

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

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

在其他平台上,孩子需要自行决定何时退出。一种方法是监视从父级提供给它的管道或套接字 FD。您还可以让某种流程管理器监控流程并在出现问题时进行清理。

不过,一般来说,恐慌应该很少见并得到修复。如果您确实有容易出现恐慌的代码区域,您可以在本地恢复并在退出之前调用子进程的清理。

【讨论】:

  • 是的,抱歉,忘了提 - 我需要一个跨平台的解决方案。但是,谢谢!
  • 没有跨平台解决方案。一个古老的跨平台技巧是给子进程一个生命线套接字 fd,并在它关闭时让子进程退出,但这当然需要与子进程合作。
  • @JimB 这段代码是必要的,但不足以保证孩子留在身边。请参阅github.com/golang/go/issues/27505 的讨论,包括保证孩子活着的完整示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-27
  • 2018-05-28
  • 2015-08-30
  • 2016-11-18
  • 1970-01-01
  • 2018-10-12
相关资源
最近更新 更多