At this point, you are probably wondering: how does the application terminate? The answer is by receiving a signal from the operating system. The signal package in the Go standard library comes with a Notify function that allows an application to register for, and receive, notifications when the application receives a particular signal type. Common signal types include the following:
- SIGINT, which is normally sent to a foreground application when the user presses Ctrl + C.
- SIGHUP, which many applications (for example, HTTP servers) hook and use as a trigger to reload their configuration.
- SIGKILL, which is sent to an application before the operating system kills it. This particular signal cannot be caught.
- SIGQUIT, which is sent to a foreground application when the user presses Ctrl+ _. The Go runtime hooks this signal so that it can print the stacks for every running goroutine before terminating the application.
Since our application will be running as a Docker container, we are only interested in handling SIGINT (sent by Kubernetes when the pod is about to shut down) and SIGHUP (for debug purposes). Since the preceding code blocks on the group's Run method, we need to use a goroutine to watch for incoming signals:
go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGHUP) select { case s := <-sigCh: cancelFn() case <-ctx.Done(): } }()
Upon receiving one of the specified signals, we immediately invoke the cancellation function for the context and return. This action will cause all the services in the group to cleanly shut down and for the svcGroup.Run call to return, thus allowing runMain to also return and for the application to terminate.