Что происходит с паниками в Go?
Что проверяют
Понимаете ли вы, что паника означает «программа в неизвестном состоянии», а не «произошла ошибка». И знаете ли границы recover, их обычно нащупывают уже на упавшем сервисе.
Как отвечать
panic прекращает нормальное выполнение функции и разматывает стек своей горутины, выполняя по дороге отложенные вызовы. Если никто не перехватил, среда выполнения печатает сообщение и стеки горутин, а процесс завершается с ненулевым кодом.
recover работает, только если вызван непосредственно отложенной функцией. Он возвращает значение паники и останавливает разматывание, после чего выполнение продолжается с возврата из той функции, где сработал defer, а не с места паники. Именованный результат при этом можно подменить:
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("восстановлено: %v", r)
}
}()Границы назовите сами:
- паника не переходит между горутинами: упавшая горутина уносит процесс, и
recoverу родителя её не поймает; - часть отказов не перехватывается вовсе: одновременная запись в карту, исчерпание памяти, взаимоблокировка всех горутин это фатальные ошибки среды выполнения;
- уместные места для паники это нарушенный инвариант и инициализация, наружу через границу пакета отдают
error.
Где спотыкаются
Прячут recover во вспомогательную функцию, которую вызывает отложенная. Так он вернёт nil, паника продолжит разматывать стек, и защита окажется бумажной. Вызов должен стоять прямо в теле отложенной функции.
Ставят перехват в обработчике и считают сервис защищённым. Горутина, запущенная внутри этого обработчика, живёт отдельно, её паника роняет весь процесс, поэтому свой defer нужен в каждой запускаемой горутине.
Используют панику как исключение, чтобы не тащить ошибку через слои. Читающий код перестаёт видеть места отказа, состояние после разматывания не то, на которое рассчитывали, а перехват с повторной паникой другим значением стирает исходное место падения.