Home > database >  Handle a signal in the foreground in Go
Handle a signal in the foreground in Go

Time:05-03

I would like to listen for a OS signal in the background but handle it in the foreground.

Here's example code. The mainloop prints "boop" every second indefinitely. But when an interrupt signal is received, the handler prints "Terminating slowly..." and waits five seconds before terminating the program.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)
    go func() {
        <-c
        fmt.Println("Terminating slowly...")
        time.Sleep(time.Duration(5)*time.Second)
        os.Exit(0)
    }()

    for {
        fmt.Println("boop")
        time.Sleep(time.Duration(1)*time.Second)
    }
}

When the signal is being handled, I want everything else to block. But currently during the five seconds of slow termination, it keeps printing "boop".

I get this:

boop
boop
^CTerminating slowly...
boop
boop
boop
boop
boop

I'd like this:

boop
boop
^CTerminating slowly...

The real program is a stack-based-language interpreter where the user can have something run on termination, but currently the mainloop can change the stack at the same time.

CodePudding user response:

As mh-cbon points out, a select statement is what you need, here. Once you wrap the body of your loop inside the default case of a select, you no longer need to start another goroutine; use a select case to do that work.

Also, you're using an unbuffered channel, but according to Notify's documentation, that function expects an appropriately buffered channel:

Package signal will not block sending to c: the caller must ensure that c has sufficient buffer space to keep up with the expected signal rate. For a channel used for notification of just one signal value, a buffer of size 1 is sufficient.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {
    c := make(chan os.Signal, 1) // buffered
    signal.Notify(c, os.Interrupt)
    for {
        select {
        case <-c:
            fmt.Println("Terminating slowly...")
            time.Sleep(time.Duration(5) * time.Second)
            return
        default:
            fmt.Println("boop")
            time.Sleep(time.Duration(1) * time.Second)
        }
    }
}

As soon as a signal is sent to channel c, the non-default case becomes non-blocking and gets selected, and the function ends.

Here is one possible output of the program (during that specific execution, I hit ^C after about 4 seconds):

boop
boop
boop
boop
^CTerminating slowly...

CodePudding user response:

While the select answer is definitely the best, I just want to add that if you would have flipped the logic, the result would have been probably OK:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)
    go func() {
        for {
            fmt.Println("boop")
            time.Sleep(time.Duration(1) * time.Second)
        }
    }()

    <-c
    fmt.Println("Terminating slowly...")
    time.Sleep(time.Duration(5) * time.Second)
    os.Exit(0)
}
  • Related