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 select
ed, 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)
}