Skip to content

Cleanly exiting a program

Matt Aimonetti edited this page Jun 24, 2016 · 1 revision

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.

Clone this wiki locally