在其他goroutine中的恐慌不会停止子进程。

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

Panic in other goroutine not stopping child process

问题

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

以下是代码:

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()
}()

仍然会发生panic,但是似乎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}')
英文:

I need to run a long-running child process and kill it if I quit (for any reason) out of parent application.

Here is the code:

cmd := exec.Command(&quot;./long-process&quot;)

defer cmd.Process.Kill()

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

var fail io.ReadCloser
fail.Close()

The fail here produces obvious

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

It works as expected - the child process is killed.

But this happens in a goroutine:

cmd := exec.Command(&quot;./long-process&quot;)

defer cmd.Process.Kill()

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

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

The panic still happens, but then it seems defer is not called and the child process is not killed.

Any way to go around this?

UPDATE I need a cross-platform solution (at least for Linux and FreeBSD)

Minimal example:

infinite-loop.sh

#!/bin/bash

while true; do
  sleep 1
done

Don't forget to

chmod +x infinite-loop.sh

test1.go (error checking left out for brevity):

package main

import (
	&quot;time&quot;
	&quot;io&quot;
	&quot;os/exec&quot;
	&quot;runtime&quot;
)

func main() {

	cmd := exec.Command(&quot;./infinite-loop.sh&quot;)

	cmd.Start()

	defer cmd.Process.Kill()

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

	for {
		runtime.Gosched()
	}
}

Let's run

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 &lt;--- !!


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 &lt;--- !!

0 processes before and 1 after exit.

If you comment out goroutine code - it works fine.

Now we can kill it:

kill $(ps aux | grep infinite-loop.sh | grep -v grep | awk {&#39;print $2&#39;})

答案1

得分: 5

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

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

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

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

在其他平台上,子进程需要自行确定何时退出。一种方法是监视父进程传递给子进程的管道或套接字文件描述符。你也可以使用某种进程管理器监视进程,并在出现问题时进行清理。

总的来说,panic应该是罕见的,并且应该得到修复。如果你的代码中有可能发生panic的区域,你可以在本地进行恢复,并在退出之前调用清理子进程的操作。

英文:

There's no cross-platform solution to automatically kill a child process.

On Linux, you can use the pdeathsig functionality:

cmd := exec.Command(&quot;./long-process&quot;)

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

On other platforms, the child needs to determine when to exit on its own. One way is to monitor a pipe or socket FD given to it from the parent. You could also have a process manager of some sort monitor the processes and cleanup if something goes wrong.

In general though, panics should be rare and get fixed. If you do have areas of code that are prone to panic'ing, you can recover locally and call for the cleanup of child processes before exiting.

huangapple
  • 本文由 发表于 2015年12月5日 02:39:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/34095254.html
匿名

发表评论

匿名网友

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

确定