sigchanyzer: static analyzer to report use of unbuffered signal channels to signal.Notify which could lose signals

Cuong Manh Le
Orijtech Developers
3 min readDec 1, 2020

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

lost signal :(

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:

signal.Notify example

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:

signal.Notify

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:vendor
make(chan os.Signal)
searching for the top 100 Go repositories on Github

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:

buggy signal notify program

Run sigchanyzer on it:

sigchanyzer run

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:

sigchanyzer fixed it!

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:

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.

--

--