Ошибка утечки памяти в HTTP-библиотеке Go?

У вас есть двоичный код Go для запуска http-сервера:

package main

import (
    "net/http"
)

func main() {
    http.ListenAndServe(":8080", nil)
}

Он начнется с ~ 850 kb или около того памяти. Отправьте несколько запросов через ваш веб-браузер. Наблюдайте за ним, быстро поднимаясь до 1 мб. Если вы подождете, вы увидите, что он никогда не опускается. Теперь забейте его скамьей Apache (используя script ниже) и постоянно увеличивайте использование памяти. Через некоторое время это будет плато около 8,2 МБ или около того.

Изменить: он, похоже, не останавливается на отметке 8.2, а значительно замедляется. В настоящее время он составляет 9,2 и продолжает расти.

Короче говоря, почему это происходит? Я использовал эту оболочку script:

while [ true ]
do
    ab -n 1000 -c 100 http://127.0.0.1:8080/
    sleep 1
end

Пытаясь разобраться в этом, я попытался настроить настройки. Я попытался закрыть с помощью r.Close = true, чтобы предотвратить Keep-Alive. Кажется, что ничего не работает.

Я нашел это изначально, пытаясь определить, была ли утечка памяти в программе, которую я пишу. В нем много обработчиков HTTP и вызовов ввода-вывода. После проверки я закрыл все мои подключения к базе данных, и я продолжал наблюдать за ростом использования памяти. Моя программа на плато около 433 МБ.

Здесь вывод Goenv:

GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/mark/Documents/Programming/Go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
TERM="dumb"
CC="clang"
GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fno-common"
CXX="clang++"
CGO_ENABLED="1"

Ответы

Ответ 1

Из кучи pprof, который вы указали в комментариях, похоже, что вы пропускаете память через gorilla/sessions и gorilla/context (почти 400 МБ).

Обратитесь к этому потоку ML: https://groups.google.com/forum/#!msg/gorilla-web/clJfCzenuWY/N_Xj9-5Lk6wJ и этот вопрос GH: https://github.com/gorilla/sessions/issues/15

Вот версия, которая протекает очень быстро:

package main

import (
    "fmt"
    // "github.com/gorilla/context"
    "github.com/gorilla/sessions"
    "net/http"
)

var (
    cookieStore = sessions.NewCookieStore([]byte("cookie-secret"))
)

func main() {
    http.HandleFunc("/", defaultHandler)
    http.ListenAndServe(":8080", nil)
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
    cookieStore.Get(r, "leak-test")
    fmt.Fprint(w, "Hi there")
}

Вот тот, который очищает и имеет относительно статический RSS:

package main

import (
    "fmt"
    "github.com/gorilla/context"
    "github.com/gorilla/sessions"
    "net/http"
)

var (
    cookieStore = sessions.NewCookieStore([]byte("cookie-secret"))
)

func main() {
    http.HandleFunc("/", defaultHandler)
    http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
    cookieStore.Get(r, "leak-test")
    fmt.Fprint(w, "Hi there")
}