What could happen if I don't close response.Body?

Go

Go Problem Overview


In Go, I have some http responses and I sometimes forget to call:

resp.Body.Close()

What happens in this case? will there be a memory leak? Also is it safe to put in defer resp.Body.Close() immediately after getting the response object?

client := http.DefaultClient
resp, err := client.Do(req)
defer resp.Body.Close()
if err != nil {
	return nil, err
}

What if there is an error, could resp or resp.Body be nil?

Go Solutions


Solution 1 - Go

> What happens in this case? will there be a memory leak?

It's a resource leak. The connection won't be re-used, and can remain open in which case the file descriptor won't be freed.

> Also is it safe to put in defer resp.Body.Close() immediately after getting the response object?

No, follow the example provided in the documentation and close it immediately after checking the error.

client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
    return nil, err
}
defer resp.Body.Close()

From the http.Client documentation:

> If the returned error is nil, the Response will contain a non-nil Body which the user is expected to close. If the Body is not both read to EOF and 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.

Solution 2 - Go

If Response.Body won't be closed with Close() method than a resources associated with a fd won't be freed. This is a resource leak.

Closing Response.Body

From response source:

> It is the caller's responsibility to close Body.

So there is no finalizers bound to the object and it must be closed explicitly.

Error handling and deferred cleanups

> On error, any Response can be ignored. A non-nil Response with a non-nil error only occurs when CheckRedirect fails, and even then the returned Response.Body is already closed.

resp, err := http.Get("http://example.com/")
if err != nil {
    // Handle error if error is non-nil
}
defer resp.Body.Close() // Close body only if response non-nil

Solution 3 - Go

At first the descriptor never closes, as things mentioned above.

And what's more, golang will cache the connection (using persistConn struct to wrap) for reusing it, if DisableKeepAlives is false.

In golang after use client.Do method, go will run goroutine readLoop method as one of the step.

So in golang http transport.go, a pconn(persistConn struct) won't be put into idleConn channel until the req canceled in the readLoop method, and also this goroutine(readLoop method) will be blocked until the req canceled.

Here is the code showing it.

If you want to know more, you need to see the readLoop method.

Solution 4 - Go

See https://golang.org/src/net/http/client.go
"When err is nil, resp always contains a non-nil resp.Body."

but they do not say when err != nil, resp always is nil. They go on to say:
"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."

So I have typically solved the issue like this:

client := http.DefaultClient
resp, err := client.Do(req)
if resp != nil {
   defer resp.Body.Close()
}
if err != nil {
    return nil, err 
}

Solution 5 - Go

One option is to put subsequest requests into a new context, that way you can use same variable name if you want, without worrying about clobbering any existing variable, and still closing everything:

package main

import (
   "bytes"
   "net/http"
)

func main() {
   b := new(bytes.Buffer)
   // request one
   r, e := http.Get("http://speedtest.atl.hivelocity.net")
   if e != nil {
      panic(e)
   }
   defer r.Body.Close()
   b.ReadFrom(r.Body)
   // request two
   {
      r, e := http.Get("http://speedtest.lax.hivelocity.net")
      if e != nil {
         panic(e)
      }
      defer r.Body.Close()
      b.ReadFrom(r.Body)
   }
   // print
   print(b.String())
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaniel RobinsonView Question on Stackoverflow
Solution 1 - GoJimBView Answer on Stackoverflow
Solution 2 - GoI159View Answer on Stackoverflow
Solution 3 - GozatrixView Answer on Stackoverflow
Solution 4 - GocanditaView Answer on Stackoverflow
Solution 5 - GoZomboView Answer on Stackoverflow