Утечка памяти при множественных соединениях в net/http Golang
У меня есть следующая функция:
var client = &http.Client{
Timeout: time.Millisecond * 100,
}
func scan(ip string) (error, string, string, string) {
resp, err := client.Get("http://" + ip)
// Закрытие неиспользуемых соединений
client.CloseIdleConnections()
if err != nil {
return err, "", "", ""
}
headers := buildHeaders(resp.Header)
body, _ := io.ReadAll(resp.Body)
// закрытие тела ответа
resp.Body.Close()
return err, headers, string(body), resp.Status
}
Данный код приводит к огромной утечке памяти. В процессе дебага выяснилось что в net/http в функции queueForIdleConn()
в файле transport.go происходит следующее:
if t.idleConnWait == nil {
t.idleConnWait = make(map[connectMethodKey]wantConnQueue)
}
q := t.idleConnWait[w.key]
q.cleanFront()
q.pushBack(w)
t.idleConnWait[w.key] = q
return false
Получается так что в idleConnWait = {map[http.connectMethodKey]http.wantConnQueue}
постоянно записываются новые элементы, но судя по размеру никогда не освобождается. Как правильно сделать так что бы этот массив очищался?
Примечания:
- Программа кидает соединения множеству адресов 95% которых недействительны (не отвечают).
- Эта функция запускается сразу в большом кол-ве горутин (
go scan()
)
Ответы (1 шт):
Ну идиоматично использовать для закрытия defer resp.Body.Close(), т.к. в неких пограничных случая, если даже вам вернулась ошибка, тело запроса может быть открыто.
Объявите свой собственный транспорт, там вы сможете ограничить размерность оной маппы:
var client = &http.Client{ Timeout: time.Millisecond * 100, Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 10, }, }
Если в вашем случае повторное использование соединений не критично, вы можете полностью отключить его, установив параметр DisableKeepAlives в true.
var client = &http.Client{ Timeout: time.Millisecond * 100, Transport: &http.Transport{ DisableKeepAlives: true, }, }