155

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?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Daniel Robinson
  • 13,806
  • 18
  • 64
  • 112
  • Its alright to put defer resp.Body.Close() after the err != nil when return is present because, when the err is not nil, it already comes closed. On the other hand the body needs to closed explicitly when the request succeeds. – Vasantha Ganesh Aug 19 '20 at 07:48

5 Answers5

186

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.

JimB
  • 104,193
  • 13
  • 262
  • 255
  • 6
    According to this [link](http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#close_http_resp_body) it is still possible to leak the connection with your code. There are some instances where the response is non-nil and the error is non-nil. – mmcdole Aug 30 '16 at 00:35
  • 32
    @mmcdole: That post is just wrong, and there's no guarantee that it won't panic, since whatever response is returned on an error doesn't have a defined state. If a Body isn't closed on an error, then it's a bug and needs to be reported. You should go by the [official client documentation](https://golang.org/pkg/net/http/#Client.Do), which states "On error, any Response can be ignored", rather than a random blog post. – JimB Aug 30 '16 at 01:35
  • Thanks for the clarification. I had a bug report logged against my go library that referenced that source and I hadn't vetted it. Interestingly, it looks like there was a [discussion about this very source](https://groups.google.com/forum/#!topic/golang-nuts/AidX-vUBLI0) on the golang mailing list. It looks that the line you referenced in the docs was recently added in 1.6.2 -- so I assume that is where the confusion came in for the original blogger who wrote the post. – mmcdole Aug 30 '16 at 02:09
  • @mmcdole, that particular line was added recently to try and make the issue as unambiguous as possible. It has always been documented as shown in the official examples. – JimB Aug 30 '16 at 02:22
  • @JimB What about draining body? Like io.Copy(ioutil.Discard, req.Body), in case when body is not read entirely (in case of faulty logic in http handler)? Would that be useful or just waste of CPU cycles? I was thinking of adding this to some middleware, since I do not see downside of doing it, but I am not sure if there is a benefit. – del-boy Jan 09 '17 at 00:39
  • 2
    @del-boy: If you expect that client to make more requests, then you should try to read the body so the connection can be reused. If you don't need the connection, then don't bother reading the body. If you read the body, wrap it with `io.LimitReader`. I usually use a fairly small limit, since it's faster to make a new connection if the request is too big. – JimB Jan 09 '17 at 14:26
  • @JimB do we need to close the response as well (aside from fully reading it) to make the connection reusable ? – Yerken Jun 25 '17 at 17:13
  • So, should I close the body if not nil, even if I got an error or not?! – lucaswxp Sep 16 '17 at 01:30
  • @lucaswxp: no. from the docs: **`On error, any Response can be ignored`** – JimB Sep 16 '17 at 01:34
  • 4
    It's worth pointing out that doing `_, err := client.Do(req)` also results in the file descriptor staying open. So even if one does not care what the response is, it is still necessary to assign it to a variable and close the body. – j boschiero Nov 07 '17 at 23:59
  • 2
    For anyone interested, the full documentation is (emphasis added): "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." – nishanthshanmugham Jan 01 '18 at 01:47
  • @nishanths: you've quoted verbatim the exact statement that's in the answer already. If the documentation changes (it hasn't yet), then feel free to edit the answer. – JimB Jan 03 '18 at 23:01
  • @JimB yeah, that was dumb; I didn't notice that part of the answer. – nishanthshanmugham Jan 04 '18 at 07:58
  • Is there any static checker to detect this kind of *resource* leaks ? – Siva Jan 08 '19 at 05:57
  • I still do not understand. `file descriptor won't be freed` - what is that mean? This is about RAM? TCP connection reuse? – Vitaly Zdanevich Apr 10 '19 at 15:59
  • @VitalyZdanevich: file descriptors are a finite resource, and you will run out if you don't close them (or run out of ports first if that is your lower limit). Yes, this also prevents the connection from being re-used (which I'll note more explicitly in the answer) – JimB Apr 10 '19 at 16:18
  • Will the response.Body auto close if error is timeout of read response body not dial timeout ? – L.T Mar 12 '20 at 03:13
  • ```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.``` From golang http client document. [link](https://golang.org/pkg/net/http/#Client.Do) – Michael Meepo Sep 11 '20 at 10:30
  • What always bothers me is lack of handling error from Close(). Even linter complains, yet there is no nice solution to check the error from Close(). I was always wondering when could call to Close fail here? – Kamil Dziedzic Oct 24 '20 at 16:28
  • I've learned that the hard way. I struggled for two days in search of this error. – Zhasulan Berdibekov Aug 31 '21 at 16:24
  • so even if we only check for resp.StatusCode, we should also close the body? hmm – Altiano Gerung Sep 15 '21 at 03:26
  • Why do you need to add a `defer` if it is already happening after the error is checked? – Alaska Nov 05 '21 at 19:35
  • according to official docs: in case of 'success' (no error) you need to read the body and then 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.`. in case of failure (error occur) no need to close 'On error, any Response can be ignored.', but I dont see problem in closing - any other views? – bfday Dec 05 '22 at 09:49
  • @bfday: there is no other "view" to consider, if there is an error `Body` can be nil and you will get a panic calling `Close`. Simply check the error and handle it as documented. – JimB Dec 05 '22 at 14:10
18

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
I159
  • 29,741
  • 31
  • 97
  • 132
  • 5
    You should note that they should return within your error handling condition. This is going to cause a panic if the user does not return in their error handling. – applewood Jul 19 '18 at 19:25
6

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.

Alex M
  • 2,756
  • 7
  • 29
  • 35
zatrix
  • 69
  • 1
  • 4
0

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 
}
candita
  • 37
  • 1
  • 5
  • 4
    This is incorrect, and there's no guarantee that resp.Body is nit nil when there's an error. – JimB Sep 02 '16 at 20:34
  • 1
    Thanks @JimB. The wording in the docs is "On error, any Response can be ignored." It would be more accurate to say "On error, the response Body is always closed." – candita Jul 07 '17 at 14:00
  • 3
    No, because there usually isn't a response body to be closed. If you continue reading that paragraph in the docs - "A non-nil Response with a non-nil error only occurs when CheckRedirect fails, and even then the returned Response.Body is already closed." – JimB Jul 07 '17 at 14:19
0

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())
}
Zombo
  • 1
  • 62
  • 391
  • 407