sigchanyzer: static analyzer to report use of unbuffered signal channels to signal.Notify which could lose signals
TL;DR: We’ve developed a static analyzer “sigchanyzer” that’ll report instances of unbuffered channels being passed into signal.Notify: a scenario that is well warned as a bug that could cause the signal to not be delivered. We are donating this pass to the Go project and evenetually every Go developer running go vet
or go test
will have it automatically enabled
Motivation
To make your programs responsive to user inputs and external stimuli from the system, you most likely would like your programs to be able to handle UNIX signals, for example:
- Graceful shutdown of HTTP server when receiving SIGTERM
- A command line executable stops processing further input when receiving SIGINT
- Closing all database connections before cleaning up and shutting down
Go provides signal.Notify to accomplish this relaying of signals, for example in:
Problem from non-enforcement:
Though it is fairly easy to use, it’s also easy for you to shoot yourself in the foot. The docs for signal.Notify explicitly say:
As you can see in godoc above and from the prior example, the signal channel must be buffered for the program to work correctly. All well and good, but unfortunately Go’s standard library tooling doesn’t have any enforcement for to prevent users from getting this bug in :-( There’s even an issue for adding this check into go vet.
Even the Go standard library code has this bug :=( in 2 important places, as per CL 274332
Investigating further
We already had proof of the bug being common, but given that we’ve been in touch with our friends from SourceGraph about an unrelated code search and some ideas, we got some guidance on how to interact and search using SourceGraph, and we ran this query in their console, searching for the top 100 Go repositories on Github searching for make(chan os.Signal)
repogroup:go-gh-100
not file:test
not file:Godeps
not file:vendormake(chan os.Signal)
sigchanyzer to the rescue…
We built this static analyzer “sigchanyzer” internally at Orijtech, Inc. and when we ran it in the outside world, we were alarmed at how common this bug is, so that gave us further impetus to open source it and donate it to the Go project.
Usage:
Consider this buggy program:
Run sigchanyzer on it:
So far so good, sigchanyzer reports a bug at line 11, which is the call to signal.Notify.
sigchanyzer can also fix the problem for you:
And your program is now safe and working alright.
With help from sigchanyzer, we were able to deliver a bunch of fixes for some repositories:
- https://go-review.googlesource.com/c/go/+/274332
- https://boringssl-review.googlesource.com/c/boringssl/+/44264
- https://github.com/nat/keyrace/pull/7
- https://github.com/github/hub/pull/2656
- https://github.com/caddyserver/caddy/pull/3895
Further more, we’re going to donate this to Go project, and made a CL to add this pass to tools/go/analysis/passes, and eventually every go user running go vet
or go test
will be able to use it and fix their code.
Conclusion
The Go programming language is designed to be easy to write, easy to read, easy to maintain, and minimizing programmer effort as much as possible. At Orijtech, Inc. we use Go in our daily work, and we focus on improving the quality of Go code written by Go developers, and help us deliver safer, more maintainable Go code.
You can get this pass today by running:
get github.com/orijtech/sigchanyzer/cmd/sigchanyzer
Please share it with your friends, provide feedback, report bugs and enjoy!
Thank you for reading this far.
Kind regards,
Cuong Manh Le, Emmanuel T Odeke
Engineering Department @ Orijtech, Inc.