Golang web server leaking memory at crypto/tls.(*block).reserve
Asked Answered
Q

1

10

I've got a web server written in Go.

tlsConfig := &tls.Config{
    PreferServerCipherSuites: true,
    MinVersion:               tls.VersionTLS12,
    CurvePreferences: []tls.CurveID{
        tls.CurveP256,
        tls.X25519,
    },
    CipherSuites: []uint16{
        tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
        tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
        tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
        tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    },
}

s := &http.Server{
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  120 * time.Second,
    Handler:      r, // where r is my router
    TLSConfig:    tlsConfig,
}

// redirect http to https
redirect := &http.Server{
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  120 * time.Second,
    Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Connection", "close")
        url := "https://" + r.Host + r.URL.String()
        http.Redirect(w, r, url, http.StatusMovedPermanently)
    }),
}
go func() {
    log.Fatal(redirect.ListenAndServe())
}()

log.Fatal(s.ListenAndServeTLS(certFile, keyFile))

Here is a screenshot from my Digital Ocean dashboard.

enter image description here

As you can see memory keeps growing and growing. So I started looking at https://github.com/google/pprof. Here is the output of top5.

Type: inuse_space
Time: Nov 7, 2018 at 10:31am (CET)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top5
Showing nodes accounting for 289.50MB, 79.70% of 363.24MB total
Dropped 90 nodes (cum <= 1.82MB)
Showing top 5 nodes out of 88
      flat  flat%   sum%        cum   cum%
  238.98MB 65.79% 65.79%   238.98MB 65.79%  crypto/tls.(*block).reserve
   20.02MB  5.51% 71.30%    20.02MB  5.51%  crypto/tls.Server
   11.50MB  3.17% 74.47%    11.50MB  3.17%  crypto/aes.newCipher
   10.50MB  2.89% 77.36%    10.50MB  2.89%  crypto/aes.(*aesCipherGCM).NewGCM

The SVG shows the same huge amount of memory allocated by crypto/tls.(*block).reserve.

enter image description here

Here is the exact code.

enter image description here

I spent the last days reading every article, document, blog post, source code, help file I could find. However nothing helps. The code is running on a Ubuntu 17.10 x64 machine using Go 1.11 inside a Docker container.

It looks like the server doesn't close the connections to the client. I thought setting all the xyzTimeout would help but it didn't.

Any ideas?

Edit 12/20/2018:

fixed now https://github.com/golang/go/issues/28654#issuecomment-448477056

Quinton answered 7/11, 2018 at 12:17 Comment(9)
Keep-Alive is enabled by default. Call s.SetKeepAlivesEnabled(false) to disable it (but verify that this actually works for HTTP 2.0). In any case, the IdleTimeout should indeed close idle connections automatically. How many active connections are there when the memory consumption is high? Is the server directly exposed to the Internet or is there a proxy involved?Tisatisane
«memory keeps growing and growing» — from the chart, I would say memory gets allocated and then freed, oscillating around certain mean value — exactly as I'd expect from a GC'ed runtime. Or does those drops on the chart mean restarts of your applications?Mauceri
@Mauceri if you look at the time intervals, no way that's GC, these are daily more or less. probably deployments?Nacred
There is no proxy in front of it. The server is directly exposed to the internet. Those drops aren't restarts. Restarts happened between October 21 and October 28 where the memory didn't have time to grow as much. I think the drops are due to the oom killer. I would also say the time spans are too long for being caused by the GC.Quinton
Yep, you're both correct on this: I did not pay attention to the scale of the X axis.Mauceri
I'd hit the issue tracker by now. Please post the issue number here if you'll do that. I, for one, would be interested to follow.Mauceri
Yes, please do. I've been trying to track this down for months. Learned a lot about go tool pprof but am no closer to a solution than I was when my program first ran into its container's memory limit...Breakage
I created an issue github.com/golang/go/issues/28654. If you've got the same issue and can provide additional information please do so.Quinton
@zemirco, thank you! +1 for such a definitive bug report.Mauceri
O
1

Adding an answer so this doesn't keep showing up in the list of upvoted and unanswered questions.

It appears that the memory leak was related to the gorilla context bug https://github.com/gorilla/sessions/commit/12bd4761fc66ac946e16fcc2a32b1e0b066f6177 and had nothing to do with tls in the stdlib.

Onestep answered 1/2, 2019 at 16:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.