< index

Golang memory leak from missing resp.Body.Close()

· 2 minute read

24 hrs after deploying my first Go application to direct user traffic… Boom.

memory usage graph

Thanks to DataDog at least I knew what was happening: my Go app was leaking about ~1Gb of RAM every 6hrs and had consumed all free memory on its host before killed by the kernel. As you can see, what followed was a period of regular restarts while I diagnosed the issue before pushing a fix.

The offending code was a goroutine that HTTP POSTed bulk log data to Loggly:

go func(data string) {
    req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
    if err != nil {
        log.Println("error:> ", err)
        return
    }
    req.Header.Set("Content-Type", "text/plain")

    resp, err := client.Do(req)
    if err != nil {
        log.Println("error:> ", err)
        return
    }

    if resp.StatusCode != http.StatusOK {
        log.Println("error:> status code ", resp.StatusCode)
        return
    }

}(buffer)

To prevent a memory leak the response body must be closed even when it is not used. The http package docs do clearly state the need to close Body, but this wasn’t obvious to me especially as I was only accessing the status code to check success.

Callers should close resp.Body when done reading from it. If resp.Body is not closed, the Client’s underlying RoundTripper (typically Transport) may not be able to re-use a persistent TCP connection to the server for a subsequent “keep-alive” request.

defer resp.Body.Close()

Fixed.

Related...