-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
net/http: *http.Transport is never collected #16005
Comments
I'm not sure what you expect to happen here. All the code is working exactly as documented. In particular, you must always Close the response body when using http.Client. You can turn off keepalives entirely in your custom http.Transport if you like. There are also configuration knobs on Client that you may wish to turn. |
I expect to have this code working correctly without needs to manually call I don't see any issues with needs to call Body.Close(), but relationship between this issue and |
Unless you disable Keep-Alive connections in the Transport, then you should expect the Transport to maintain a pool of idle connections. If you don't close those connections or re-use the Transport, you will leak Transports.
Hmm, well |
Sure. But also I expect Transport object to clean up after itself when it doesn't used anymore - i.e. when all references to that object is gone.
Is this mean only correct way to create own Transport is to manually make sure it will be garbage-collected when it doesn't needed anymore? For example, in simple case when this Transport isn't shared between multiple clients use something like this (untested)? tr := &http.Transport{…}
client := &http.Client{Transport: tr}
runtime.SetFinalizer(client, func(c *http.Client){ c.Transport.CloseIdleConnections() }) If this is the case, then it probably should be noted in documentation with warning about leaking sockets and goroutines otherwise.
Without defining |
But that's not how things usually work in Go. I would expect to call some kind of Close method. I would not recommend using The interaction between connection pooling (I assume) and |
Can you please explain why? Or give a link if this was already explained somewhere? How it's differ from
I agree, but I believe this is somewhat corner/subtle case and it's worth to be mentioned in http package documentation. |
There are no guarantees about if/when a finalizer will be executed. A finalizer may be silently replaced by a subsequent call to SetFinalizer, with no warning from the compiler or runtime. In short, they are not reliable enough to be broadly useful. On the other hand, defer is well-defined and its behavior is deterministic. You know that all deferred functions were executed if a function call returns. |
I actually don't see Here's my version: package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"runtime"
"time"
)
func main() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello")
}))
var ms runtime.MemStats
for {
runtime.ReadMemStats(&ms)
log.Printf("Before request: %d goroutines, %d heapalloc, %d GC", runtime.NumGoroutine(), ms.HeapAlloc, ms.NumGC)
doRequest(ts.URL)
time.Sleep(500 * time.Millisecond)
runtime.GC()
runtime.GC()
runtime.ReadMemStats(&ms)
log.Printf(" After request: %d goroutines, %d heapalloc, %d GC", runtime.NumGoroutine(), ms.HeapAlloc, ms.NumGC)
}
}
func doRequest(url string) {
tr := &http.Transport{
DisableKeepAlives: true,
}
c := &http.Client{Transport: tr}
runtime.SetFinalizer(c, func(*http.Client) { fmt.Println("--- http.Client GONE") })
runtime.SetFinalizer(tr, func(*http.Transport) { fmt.Println("--- http.Transport GONE") })
resp, err := c.Get(url)
if err != nil {
log.Fatal(err)
}
fmt.Println("got response")
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
// Pointless with DisableKeepAlives above, but:
time.Sleep(10 * time.Millisecond)
tr.CloseIdleConnections()
} And the output:
So it's not a goroutine leak at least. I really wish there were an API like 'runtime.ShowMePathsFromRootsetTo(mem uintptr)` so I could debug retentions like this. Not sure whether our heapdump/heapview tooling is good enough for this yet. I also suspected that maybe *http.Transport was on the stack which is why I never saw it finalized, but
Go team, any help? |
While that would be nice, it takes as much space as the heap itself (or, strictly speaking, as all of the pointers in the heap) to compute that and I don't think the runtime itself could give you any type information (just a chain of pointers and offsets). The heap dump is the way to do this, but I'm not sure what its status is. |
I suppose that's not strictly true. We could do it with less space, but it would take O(|heap| * |path length|) time.
Is it possible |
I toyed with solving this using GDB's Python API, and actually got quite far, but eventually slammed into the same problem the heap dumper has run into: I have only DWARF type information for global and stack roots, but only Go type information for interface values. Right now I don't think we have a reliable way to map from a Go type to a DWARF type. We'll probably have to fix that for the heap dumper to be robust. |
Found it. The fact that the The only reason the Transport isn't being collected is because of the finalizer. If you remove the finalizer, it will be collected (you can see this clearly by setting In general, you should never set a finalizer on an object you didn't implement because you don't know if it may participate in a cycle, or if the internal implementation already uses a finalizer. In this case, setting the finalizer causes it not to be freed because it participates in a cycle where |
That's more disappointing than I was expecting. Is that documented anywhere? I'm also not even sure I understand why cycles matter here. If that finalizer's func was doing something with the transport I could believe it, but where is the cycle? I understand that Transport internally has cycles, but cycles don't preclude things being collected. So why do internal cycles + finalizers matter? |
The GC guarantees that an object and all its references will be live when the finalizer is called. If you have a cycle, there's no object that can be collected "first" without making it impossible to run the other object's finalizer. |
"If a cyclic structure includes a block with a finalizer, that cycle is not guaranteed to be garbage collected and the finalizer is not guaranteed to run, because there is no ordering that respects the dependencies." -- https://godoc.org/runtime#SetFinalizer In principle we could run the finalizer if exactly one block in the cycle has a finalizer, but I have no idea how to implement that efficiently and then it would just break down in an even subtler way if you happened to have two finalizers in the cycle. Don't use finalizers. |
Okay, I should've re-read the finalizer docs. I thought I'd still remembered them. Apparently not, thanks.
Fair enough. Got that, @powerman? |
Thanks for the explanation! |
http.Transport seems to only get collected if there are no connections in flight and all idle connections are closed. I would have expected the gc to garbage collect http.Transport when there are no references from my code even if the idle connection map still has entries. Is that a bad assumption? |
Please answer these questions before submitting your issue. Thanks!
go version
)?go version go1.6.2 linux/amd64
go env
)?GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/powerman/gocode"
GORACE=""
GOROOT="/usr/lib/go"
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GO15VENDOREXPERIMENT="1"
CC="x86_64-pc-linux-gnu-gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
CXX="x86_64-pc-linux-gnu-g++"
CGO_ENABLED="1"
Provided program works correctly and gives output shown above. But if we'll comment any of lines marked
// A
or// B
or// C
then http.Transport won't be garbage-collected anymore and output became:I know it's recommended to share same http.Client for all application, but I'm developing load-testing application which simulate thousands of independent clients and thus require own simulated client to use own http.Client and own http.Transport objects to behave more like real independent clients.
The text was updated successfully, but these errors were encountered: