Тихая смерть памяти c time.
Внутри select часто используют таймеры для отмены долгих операций. Но есть нюанс, который может положить вам прод по памяти, если у вас высокий RPS или быстрые циклы.
Смотрим код:
for {
select {
case msg := <-ch:
process(msg)
case <-time.After(3 * time.Minute):
return errors.New("timeout")
}
}
В чем проблема?
time.After создает таймер, который не собирается мусорщиком, пока не истечет его время. Даже если case msg := <-ch сработает мгновенно, таймер на 3 минуты останется висеть в памяти.
В цикле с высокой нагрузкой вы создадите тысячи таймеров, которые будут ждать свои 3 минуты, пожирая хип.
Решение:
Используйте time.NewTimer и останавливайте его вручную.
timer := time.NewTimer(3 * time.Minute)
defer timer.Stop()
for {
timer.Reset(3 * time.Minute)
select {
case msg := <-ch:
process(msg)
if !timer.Stop() {
<-timer.C
}
case <-timer.C:
return errors.New("timeout")
}
}
Да, кода больше. Да, выглядит не так элегантно. Зато OOM Killer не придет к вам ночью.
#golang #performance #memory_leak
👉 @golang_lib