func httpHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("This is an example server.\n"))
}
func main() {
http.HandleFunc("/", httpHandler)
err := http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil)
if err != nil {
log.Fatal(err)
}
}
func main() {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
count := 0
for {
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := http.Client{Transport: transport}
res, err := client.Get("https://localhost:8080/")
if err != nil {
panic(err)
}
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Printf("(%d) Received: %s", count, bytes)
count++
res.Body.Close()
transport.CloseIdleConnections()
}
}# 0x39cb3 crypto/tls.(*clientHandshakeState).doFullHandshake+0x273 /usr/local/Cellar/go/1.3.3/libexec/src/pkg/crypto/tls/handshake_client.go:227
# 0x39530 crypto/tls.(*Conn).clientHandshake+0x1310 /usr/local/Cellar/go/1.3.3/libexec/src/pkg/crypto/tls/handshake_client.go:184
# 0x37c50 crypto/tls.(*Conn).Handshake+0xf0 /usr/local/Cellar/go/1.3.3/libexec/src/pkg/crypto/tls/conn.go:974
# 0x9192b net/http.func·021+0x3b /usr/local/Cellar/go/1.3.3/libexec/src/pkg/net/http/transport.go:577
How are you measuring this?
To make it easier to reproduce, I've created this gist with everything required and a full dump of the heap after 5 seconds (this will steadily grow).
--
./client.go:17: func literal escapes to heap
./client.go:22: &tls.Config literal escapes to heap
./client.go:28: moved to heap: client
./client.go:30: client escapes to heap
./client.go:27: &http.Transport literal escapes to heap
./client.go:22: &tls.Config literal escapes to heap
./client.go:27: &http.Transport literal escapes to heap
./client.go:22: &tls.Config literal escapes to heap
./client.go:40: main ... argument does not escape
Your code most certainly is allocating memory. It creates a new http.Client and tls.Config value on every iteration, which since it's escaping must be on the heap (it doesn't re-use the memory). ioutil.ReadAll also has to allocate on the heap.
Sure the GC will clean up for you, according to your trace it's already run 79 times, the lines above the memory statistics (not prefixed with a #) are records of all places where the profiler saw an allocation *when it sampled*. A heap profile record is created for every 512K (by default) of memory allocated. Only the trace of the object allocated at the time the sample is taken is recorded. This is for speed (and space), and on average, the larger allocations are more likely to be profiled and be of relevance. When you look at the profile, you are only seeing the locations where the 524288th byte since a previous profile was allocated, not all allocations. Since there is likely not exactly the perfect # of bytes in your main loop so that the profiling always happens in the same places, over time you will get more profiling locations showing up.
The uncommented lines show the following information for a single stack trace:
0: 0 [2: 2048] @ 0x24eb5 0x24998 0x260e1 0x2616a 0x294bd 0x293f3 0x3378b 0x336da 0x33bac 0x346b0 0x36496 0x3a388 0x39530 0x37c50 0x9192b 0x14e801 2 3 4 5
heap profile: 2: 90400 [82: 171840] @ heap/1048576
Oh, right. I meant to put `DisableKeepAlives: true,` in that transport.I also removed the call to the OS's resolver by using 127.0.0.1 instead of localhost to get rid of another variable.It now does slowly increase, which makes sense since we're making a lot more garbage. I'll let it sit and burn CPU for a while and see what happens.
From my experience, it is due to the duplicate instances of http.Transport. By default keeps around idle connections for resuse, so it is not tagged for garbage collection once its immediately out of scope. The Work around is to set DisableKeepAlive to true.
Or just resuse a single instance of http.Transport and fiddle with max idle connections if need (default is 2).
You may also this "leak" expressed in too many open files, since each open connection holds its file descriptor while the Transport instance sticks around.
From my experience, it is due to the duplicate instances of http.Transport. By default keeps around idle connections for resuse, so it is not tagged for garbage collection once its immediately out of scope. The Work around is to set DisableKeepAlive to true.
Replied to all. You and I are on the same page.
--