Summary
This lighting talk was presented at the Utah Go Meetup and covered some of the major pitfalls and problems that come with using package variables in Go.
Key Takeaways
Package variables often look like this:
package main
// x is a package variable
var x = 1
func main() {
println(x)
}
A simple example like this might seem innocuous. But it is easy to introduce subtle bugs. Because any function in the package can manipulate them. You end up with shared state. And shared state is dangerous!
A more complete example that illustrates how bugs can be introduced can be seen in the following code:
// START MAIN OMIT
var (
url = ""
)
func main() {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "You called "+r.URL.Path)
}))
defer s.Close()
url = s.URL
go startHealthProbes()
doHello()
time.Sleep(time.Second)
doHello()
}
The code above does the following:
- It starts a web server which prints the requested url path for all requests
- It sets the package variable
url
to the generated url for the test server - It starts a background health probe process to request
/health
every half second - It requests
/
- It waits one second
- It requests
/
again
The code for doHello
is trivial:
func doHello() {
r, _ := http.Get(url)
io.Copy(os.Stdout, r.Body)
fmt.Println()
}
The code for startHealthProbes
seems simple enough as well.
func startHealthProbes() {
for range time.Tick(time.Second / 2) {
doHeathProbe()
}
}
func doHeathProbe() {
// the health probe needs a timeout (default is 0 which means no timeout)
http.DefaultClient.Timeout = time.Second * 30
// the health probe needs to use a different url
url = url + "/health"
r, err := http.Get(url)
if err != nil {
panic(err)
}
defer r.Body.Close()
// read until EOF
io.Copy(io.Discard, r.Body)
}
However, If you run the code above then you will see that the first call to doHello
prints correctly, but the second call prints an unexpected result for the second call to doHello
:
$ go run main.go
You called /
You called /health
The reason is that url
is a package variable! So when doHeathProbe
changes it to add the /health
to the end, it breaks it for the second call to doHello
.
This example is simple enough and could be avoided with a slight change to doHealthProbe
but hopefully it illustrates one way of how package variables can introduce subtle bugs.
The full example code for the above problem is here
Details
The full source code for the talk can be found at: