Search code examples
linuxgosignals

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

I compiled the Go code above and executed with following command:

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

But the process still catches HANGUP signal when I exit the terminal.

Seems like signal.Notify has higher priority, and nohup command has no effect.

What's going on and why nohup does not prevent sending hangup signal to process?


Solution

  • TL;DR

    Check signal.Ignored() first:

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

    Long

    tkausl's comment has the correct answer: running:

    nohup ./build_linux
    

    from the command line launches the ./build_linux program with SIGHUP set to SIG_IGN (all of these signal names being generic Linux names, rather than Go package names). If you install your own handler for SIGHUP, this overrides the current setting of SIGHUP.

    In general, in Unix/Linux programs, the correct pattern is to test whether the signal is currently ignored before (or as part of) installing a signal-catch function. If the signal is ignored, restore it to being ignored.

    To make this process completely reliable, the most efficient correct pattern to use is:

    • hold off the signal (perhaps all signals);
    • install any desired handler, which returns the current disposition of the signal;
    • if the current disposition was SIG_IGN, return the disposition to SIG_IGN;
    • release the signal.

    The holding-and-releasing is done with the Unix/Linux sigprocmask or pthread_sigmask system call. The one to use depends on whether you're using threads. Go of course does use threads; see, e.g., this patch to the Cgo runtime startup from 2013 (fixes issue #6811).

    Since Go 1.11, which introduced signal.Ignored, you can just use that, as the Go runtime has already done all the appropriate hold / set-and-test / restore sequence at startup, and cached the result. One should definitely use this for SIGHUP so as to obey the nohup convention. One should generally use this for SIGINT and other keyboard signals as well, and there's almost1 no reason not to use it for all signals.1


    1Jenkins, or some version or versions of Jenkins at least, apparently (incorrectly) sets all signals to be ignored at startup when running test suites.