8

I've noticed that processes started with exec.Command get interrupted even when the interrupt call has been intercepted via signal.Notify. I've done the following example to show the issue:

package main

import (
    "log"
    "os"
    "os/exec"
    "os/signal"
    "syscall"
)

func sleep() {
    log.Println("Sleep start")
    cmd := exec.Command("sleep", "60")
    cmd.Run()
    log.Println("Sleep stop")
}

func main() {
    var doneChannel = make(chan bool)

    go sleep()

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    signal.Notify(c, syscall.SIGTERM)
    go func() {
        <-c
        log.Println("Receved Ctrl + C")
    }()

    <-doneChannel
}

If Ctrl+C is pressed while this program is running, it's going to print:

2015/10/16 10:05:50 Sleep start
^C2015/10/16 10:05:52 Receved Ctrl + C
2015/10/16 10:05:52 Sleep stop

showing that the sleep commands gets interrupted. Ctrl+C is successfully caught though and the main program doesn't quit, it's just the sleep commands that gets affected.

Any idea how to prevent this from happening?

laurent
  • 88,262
  • 77
  • 290
  • 428

2 Answers2

15

The shell will signal the entire process group when you press ctrl+c. If you signal the parent process directly, the child process won't receive the signal.

To prevent the shell from signaling the children, you need to start the command in its own process group with with the Setpgid and Pgid fields in syscall.SysProcAttr before starting the processes

cmd := exec.Command("sleep", "60")
cmd.SysProcAttr = &syscall.SysProcAttr{
    Setpgid: true,
}
JimB
  • 104,193
  • 13
  • 262
  • 255
  • 2
    This doesn't work for windows as there is no `Setpgid` in `SysProcAttr` under windows. Any alternatives? – tkausl Jun 13 '16 at 19:07
  • 1
    @tkausl Yes. On Windows you need to set `CreationFlags` in the `syscall.SysProcAttr` to `syscall.CREATE_NEW_PROCESS_GROUP`. – al45tair Jan 24 '20 at 08:23
-2

You can ignore the syscall.SIGINT signal, then it won't be passed to the exec.Command.

func main() {
    var doneChannel = make(chan bool)

    signal.Ignore(syscall.SIGINT)

    go func() {
        log.Println("Sleep start")
        cmd := exec.Command("sleep", "10")
        cmd.Run()
        log.Println("Sleep stop")
        doneChannel <- true
    }()

    <-doneChannel
}
J Aamish
  • 522
  • 7
  • 12
  • 1
    import("os/signal" "syscall") – ideawu Feb 26 '20 at 13:40
  • 3
    This only works because the child (in this case `sleep`) inherits parent's signal dispositions. The signal is still delivered and this might fail if the target application restores default signal behavior with e.g. `signal(SIGINT, SIG_DFL)` or sets it's own signal handler. – Nikolay Edigaryev Feb 10 '21 at 21:00