A more practical and common example of a clean shutdown is resource cleanup. When using exit statements, deferred functions such as Flush for a bufio.Writer struct are not executed. This can lead to information loss, as shown in the following example:
package main
import (
"bufio"
"fmt"
"log"
"os"
"time"
)
func main() {
f, err := os.OpenFile("file.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
w := bufio.NewWriter(f)
defer w.Flush()
for i := 0; i < 3; i++ {
fmt.Fprintln(w, "hello")
log.Println(i)
time.Sleep(time.Second)
}
}
If a TERM signal is sent to this application before it finishes, the file will be created and truncated, but the flush will never be executed—resulting in an empty file.
This could, perhaps, be the intended behavior, but this is rarely the case. It is better to do any cleanup in the signal handling part, as shown in the following example:
func main() {
c := make(chan os.Signal, syscall.SIGTERM)
signal.Notify(c)
f, err := os.OpenFile("file.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
w := bufio.NewWriter(f)
go func() {
<-c
w.Flush()
os.Exit(0)
}()
for i := 0; i < 3; i++ {
fmt.Fprintln(w, "hello")
log.Println(i)
time.Sleep(time.Second)
}
}
In this case, we are using a goroutine in combination with the signal channel to flush the writer before exiting. This will ensure that whatever is written to the buffer gets persisted on the file.