为什么即使有 nohup 也会捕获挂断信号?

Why hangup signal is caught even with nohup?

package main

import (
    "os"
    "os/signal"

    log "github.com/sirupsen/logrus"
    "golang.org/x/sys/unix"
)

func main() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, unix.SIGHUP)

    go func() {
        s := <-sigs
        log.Info("OS signal: " + s.String())
    }()

    DoSomething()
}

我编译了上面的 Go 代码并使用以下命令执行:

nohup ./build_linux > /dev/null 2>&1 &

但是当我退出终端时进程仍然捕捉到 HANGUP 信号。

好像signal.Notify优先级高,nohup指令无效

这是怎么回事,为什么 nohup 不能阻止向进程发送挂断信号?

TL;DR

先检查signal.Ignored()

if !signal.Ignored(unix.SIGHUP) {
    signal.Notify(sigs, unix.SIGHUP)
}

的正确答案是:运行:

nohup ./build_linux

从命令行启动 ./build_linux 程序,其中 SIGHUP 设置为 SIG_IGN(所有这些信号名称都是通用的 Linux 名称,而不是 Go 包名称).如果您为 SIGHUP 安装自己的处理程序,这将覆盖 SIGHUP 的当前设置。

一般来说,在Unix/Linux程序中,正确的模式是测试信号是否当前被忽略之前(或者作为一部分)安装 signal-catch 函数。如果信号忽略,将其恢复为被忽略。

为了使这个过程完全可靠,最有效的正确模式是:

  • 延迟信号(可能是所有信号);
  • 安装任何需要的处理程序,return是信号的当前配置;
  • 如果当前配置为 SIG_IGN,return 则配置为 SIG_IGN
  • 解除信号

holding-and-releasing 是通过 Unix/Linux sigprocmaskpthread_sigmask 系统调用完成的。使用哪一个取决于您是否使用线程。 Go 当然使用线程;参见,例如 this patch to the Cgo runtime startup from 2013 (fixes issue #6811).

自从引入 signal.Ignored 的 Go 1.11 以来,您可以直接使用它,因为 Go 运行时已经在启动时完成了所有适当的 hold / set-and-test / restore 序列,并缓存了结果.应该 绝对 将此用于 SIGHUP 以遵守 nohup 约定。人们通常也应该将它用于 SIGINT 和其他键盘信号,并且几乎1 没有理由 将它用于所有信号。1


1Jenkins,或者至少是 Jenkins 的某个版本,显然(错误地)将所有信号设置为在 运行 测试套件启动时忽略。