Introduction
In this tutorial, we will answer the question "Is it possible to capture a Ctrl+C
signal (SIGINT) and run a cleanup function, in a "defer" fashion" in Golang.
The answer is yes, with the help of the built-in package os/signal, we can handle incoming signals. A way for processes to communicate is through signals. When a process receives a signal, it interrupts itself in order to run a signal handler. The type of signal received typically determines how the software operates.
SIGINT is the signal sent when we press Ctrl+C
. The default action is to terminate the process. However, some programs override this action and handle it differently. One common example is the bash interpreter. When we press Ctrl+C
it doesn’t quit, instead, it prints a new and empty prompt line.
On Windows Control-C or (Control-Break) normally cause the program to exit. If Notify is called for os.Interrupt, Ctrl+C or BREAK will cause os.Interrupt
to be sent on the channel, and the program will not exit. If Reset is called, or Stop is called on all channels passed to Notify, then the default behavior will be restored.
Example 1: Capture a Ctrl+C trap signal using os/signal package
In this example, we will use the Notify()
function to capture the os.Interrupt
signal (Ctrl+C).
func Notify(c chan<- os.Signal, sig ...os.Signal)
: Notify causes package signal to relay incoming signals to c. If no signals are provided, all incoming signals will be relayed to c. Otherwise, just the provided signals will. 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)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
fmt.Println(sig)
}
}()
temp := 0
for temp < 10 {
fmt.Println("sleeping...")
time.Sleep(2 * time.Second)
temp += 1
}
}
Output:
Firstly, I created a goroutine to handle the signals from keyboard. Whenever the os.I<code class="language-go">nterrupt
is captured, this goroutine will print out a message to the console. While in the main goroutine, we have a for loop to keep it running.
Example 2: Another example to capture a Ctrl+C signal
We can apply another technic with channel to catch the interrupt signal and do some operations before exiting:
package main
import (
"fmt"
"os"
"os/signal"
"time"
)
func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
fmt.Println("Program killed!")
cleanup()
os.Exit(1)
}()
for {
fmt.Println("sleeping...")
time.Sleep(2 * time.Second)
}
}
func cleanup() {
fmt.Println("This is the clean up function")
}
Output:
sleeping...
Program killed!
This is the clean up function
exit status 1
To slightly expand on the previous answers, syscall can be used to intercept SIGTERM, the standard signal sent by the kill command. Instead of os.Interrupt, use SIGTERM. Be aware that the syscall interface depends on the OS and may not always function (e.g. on windows). But catching both works well:
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
Example 3: Block the main goroutine until SIGINT signal
In this example, we will create 2 channel, 1 for handle the signals and 1 for block the main goroutine. When we run this program it will block waiting for a signal. By typing ctrl-C (which the terminal shows as ^C) we can send a SIGINT signal, causing the program to print interrupt and then exit.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
c := make(chan os.Signal, 1)
check := make(chan bool, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-c
fmt.Println()
fmt.Println(sig)
check <- true
fmt.Println("This is the clean up message")
}()
fmt.Println("waiting for Ctrl+C signal")
<-check
fmt.Println("Done!!")
}
Output:
Summary
In some cases, we need to do extra work if users press Ctrl+C to terminate the program. This post shows how to capture Ctrl+C event and run the handler in Go. We can easily use the built-in package os/signal to catch and handle this signal.
References