Sending shutdown signals
When our application is running, we can terminate it at any time by sending it a specific signal. A common way to do this, which you’ve probably been using, is by pressing Ctrl+C on your keyboard to send an interrupt signal — also known as a SIGINT.
But this isn’t the only type of signal that can stop our application. Some of the other common ones are:
| Signal | Description | Keyboard shortcut | Catchable |
|---|---|---|---|
SIGINT |
Interrupt from keyboard | Ctrl+C |
Yes |
SIGQUIT |
Quit from keyboard | Ctrl+\ |
Yes |
SIGKILL |
Kill process (terminate immediately) | - | No |
SIGTERM |
Terminate process in an orderly manner | - | Yes |
It’s important to explain upfront that some signals are catchable and others are not. Catchable signals can be intercepted by our application and either ignored, or used to trigger a certain action (such as a graceful shutdown). Other signals, like SIGKILL, are not catchable and cannot be intercepted.
Let’s take a quick look at these signals in action. If you’re following along, go ahead and start the API application with the same go run ./cmd/api command as normal:
$ go run ./cmd/api time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development
Doing this should start a process with the name api on your machine. You can use the pgrep command to verify that this process exists, like so:
$ pgrep -l api 4414 api
In my case, I can see that the api process is running and has the process ID 4414 (your process ID will most likely be different).
Once that’s confirmed, try sending a SIGKILL signal to the api process using the pkill command like so:
$ pkill -SIGKILL api
If you go back to the terminal window that is running the API application, you should see that it has been terminated and the final line in the output stream is signal: killed. Similar to this:
$ go run ./cmd/api time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development signal: killed
Feel free to repeat the same process, but sending a SIGTERM signal instead:
$ pkill -SIGTERM api
This time you should see the line signal: terminated at the end of the output, like so:
$ go run ./cmd/api time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development signal: terminated
You can also try sending a SIGQUIT signal — either by pressing Ctrl+\ on your keyboard or running pkill -SIGQUIT api. This will cause the application to exit with a stack dump, similar to this:
$ go run ./cmd/api
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established"
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development
SIGQUIT: quit
PC=0x46ebe1 m=0 sigcode=0
goroutine 0 [idle]:
runtime.futex(0x964870, 0x80, 0x0, 0x0, 0x0, 0x964720, 0x7ffd551034f8, 0x964420, 0x7ffd55103508, 0x40dcbf, ...)
/usr/local/go/src/runtime/sys_linux_amd64.s:579 +0x21
runtime.futexsleep(0x964870, 0x0, 0xffffffffffffffff)
/usr/local/go/src/runtime/os_linux.go:44 +0x46
runtime.notesleep(0x964870)
/usr/local/go/src/runtime/lock_futex.go:159 +0x9f
runtime.mPark()
/usr/local/go/src/runtime/proc.go:1340 +0x39
runtime.stopm()
/usr/local/go/src/runtime/proc.go:2257 +0x92
runtime.findrunnable(0xc00002c000, 0x0)
/usr/local/go/src/runtime/proc.go:2916 +0x72e
runtime.schedule()
/usr/local/go/src/runtime/proc.go:3125 +0x2d7
runtime.park_m(0xc000000180)
/usr/local/go/src/runtime/proc.go:3274 +0x9d
runtime.mcall(0x0)
/usr/local/go/src/runtime/asm_amd64.s:327 +0x5b
...
We can see that these signals are effective in terminating our application — but the problem we have is that they all cause our application to exit immediately.
Fortunately, Go provides tools in the os/signals package that we can use to intercept catchable signals and trigger a graceful shutdown of our application. We’ll look at how to do that in the next chapter.