defer в цикле и os.Exit
Аня на ревью указала на цикл импорта: уборка копилась до конца функции, и сервис упирался в лимит дескрипторов. А старая утилита выгрузки завершалась так, что отложенная уборка не выполнялась вовсе. Миша починил оба места.
defer живёт до выхода из функции, поэтому в цикле он копит отложенные вызовы, а os.Exit не выполняет их вообще.
Каждая итерация цикла добавляет новый отложенный вызов, и все они ждут выхода из функции, а не конца итерации.
Defer в цикле копит ресурсы
for _, path := range paths {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // файлы копятся до выхода из функции
}
Тысяча путей — тысяча открытых файлов до самого конца функции: рано или поздно упрёшься в лимит дескрипторов. На горячем пути импорта так и случилось.
Вынести тело итерации в функцию
Правильное решение — граница функции. Именно она является границей жизни defer:
for _, path := range paths {
if err := process(path); err != nil {
return err
}
}
func process(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
// работа с файлом
return nil
}
Не хочется заводить отдельную функцию — оберни тело цикла в анонимную с немедленным вызовом, эффект тот же.
Os.Exit не выполняет defer
Один способ выйти из программы defer не переживает:
defer fmt.Println("не напечатается")
os.Exit(1)
os.Exit завершает процесс немедленно, не выполняя отложенные вызовы. Это не возврат из функции, а выход из программы. os.Exit — инструмент для фатальных ситуаций в main или при старте, а не способ завершить работу «аккуратно». Если нужна аккуратная остановка — возвращай ошибку наверх и дай defer сделать своё дело.
Вы узнаете:
- почему тысяча открытых файлов упирается в лимит ОС
- как анонимная функция с немедленным вызовом заменяет именованную
Проверьте себя
defer fmt.Println("не напечатается"); os.Exit(1). Что напечатается?
defer f.Close() внутри цикла for — что произойдёт?
Как исправить defer в цикле?
правильных ответов
defer привязан к функции, а не к блоку или итерации. В цикле — выноси тело в функцию. os.Exit — не возврат: отложенные вызовы он пропускает.
10 000 ₽ · 60 минут · онлайн