Утечка памяти в стандартной библиотеке Go http?

Пусть двоичный файл Go реализует http-сервер:

package main

import (
    "net/http"
)

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

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

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

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

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"

person Mark    schedule 12.01.2014    source источник
comment
Это кеширование данных или сбор логов, отладочной информации и диагностики?   -  person Rygu    schedule 13.01.2014
comment
Это не очень много делает. Я нажимаю /, который открывает файл (ioutil.ReadAll), запускает его через текст/шаблон и выводит его. На этой странице нет никаких вызовов SQL, но все закрыто. В этот момент никакие данные/журналы/информация/диагностика не записываются. Я удалил все, что мог придумать.   -  person Mark    schedule 13.01.2014
comment
Какую версию Go вы используете?   -  person Preetam Jinka    schedule 13.01.2014
comment
go версия go1.2 darwin/amd64   -  person Mark    schedule 13.01.2014
comment
Вы пробовали go prof?   -  person joshlf    schedule 13.01.2014
comment
Недавно я использовал один из них в своем веб-приложении: gist.github.com/ancarda/92d82f4da8f63012424d. Я не совсем уверен, как читать его вывод.   -  person Mark    schedule 13.01.2014
comment
Я не уверен, но я предполагаю, что это означает, что этот объем памяти был выделен внутри этой функции (вероятно, с вызовами new и make - это только использование кучи, верно?).   -  person joshlf    schedule 13.01.2014
comment
Он становится очень медленным при 16 тыс. запросов на ab, потому что у вас закончились эфемерные порты. См. этот ответ stackoverflow .com/questions/1216267/   -  person kouton    schedule 02.08.2014


Ответы (1)


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

Обратитесь к этой теме машинного обучения: 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")
}
person az_    schedule 12.01.2014
comment
Да, похоже на утечку в CookieStore. Когда я убрал это, использование памяти было довольно нормальным. Я не понимаю, зачем мне context.ClearHandler. Что касается растущего использования памяти на простом HTTP-сервере, то это, похоже, просто сборщик мусора. - person Mark; 13.01.2014
comment
gorilla/context внутренне хранит данные в map[request]...sessions использует context), поэтому обработчик должен удалить запрос с карты после завершения запроса. Похоже, что gorilla/sessions был разработан для использования с роутером gorilla/mux (он автоматически очищает карту). - person az_; 13.01.2014
comment
Спасибо, я проверил сайт, и там ничего не упоминается. - person Mark; 13.01.2014