-
Notifications
You must be signed in to change notification settings - Fork 33
Cleanly exiting a program
Very often a program using a device library will have code inside an infinite loop. If you were to exit the program by sending a signal, the defer code wouldn't execute.
The proper way to deal with a such a situation is to add a signal handler. Note that a signal can be sent via different means (sending a signal to the process (HUP, KILL..) or ctrl+c when the application is running.
Here is a code example monitoring incoming signals and properly exiting the loop.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/goiot/devices/grove/accel3xdigital"
"golang.org/x/exp/io/i2c"
)
func main() {
accel, err := accel3xdigital.Open(&i2c.Devfs{
Dev: "/dev/i2c-1",
Addr: accel3xdigital.Address,
})
if err != nil {
panic(err)
}
// channel to push to if we want to exit in a clean way
quitCh := make(chan bool)
// catch signals
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
// monitor for signals in the background
go func() {
s := <-sigCh
fmt.Println("\nreceived signal:", s)
quitCh <- true
}()
for {
select {
case <-quitCh:
accel.Close()
fmt.Println("Ciao! :)")
os.Exit(0)
case <-time.After(500 * time.Millisecond):
accel.Update()
fmt.Println(accel.State)
}
}
}
We start by creating a channel of bolleans called quitCh
. We are going to later on listen on this channel and when data is available, it will mean that we need to cleanly quit our application.
We create another channel is created, we called it sigCh
and it's a channel of os.Signal
. Now channels don't do anything on their own so we need a way to monitor incoming signals and push the information to our newly created channel. We do that via signal.Notify
:
signal.Notify(sigCh,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
The above code indicates that if one of the mentioned signals are received, they should be pushed to our sigCh
channel. We now need to monitor our channel. We can't do that straight in our main function because it would block. We therefore start a goroutine, wait on the channel and when something comes in, we write to quitCh
.
go func() {
s := <-sigCh
quitCh <- true
}()
Finally, we can start our for
loop. We use select
inside the loop to check on the quitCh
channel and to trigger a reading of our device every 500 milliseconds.
If a signal is received, then the case statement related to the quitCh
channel will be exited allowing us to properly clean the various states and cleanly exit the program.