Разберите последствие: что увидит внешний recover, если функция в процессе раскрутки после panic сама вызовет ещё одну panic из defer?
Внешний recover увидит новую панику, возникшую в defer; исходное значение первой паники обычным recover уже недоступно. Поэтому паника из функции очистки может замаскировать настоящую причину сбоя.
В Go обычные ожидаемые ошибки возвращаются как значения error, а panic предназначен для аварийного прекращения текущего потока выполнения. Механизм defer используется для гарантированного освобождения ресурсов и выполняется во время раскрутки стека, поэтому ошибка в отложенной функции может пересечься с уже активной паникой.
Такое разделение позволяет не использовать исключения для штатных веток, но требует осторожно проектировать аварийные и очистительные обработчики.
Пусть функция обнаружила критическую ошибку и вызвала panic. Затем её defer закрывает ресурс, завершает трассировку или записывает метрику, но сама операция очистки тоже вызывает panic.
Если не обработать вторую панику, внешний обработчик обычно получит значение из defer, а не исходную причину. В результате диагностика становится misleading: стек и сообщение указывают на вторичную ошибку, а отказавшая основная операция теряется.
При раскрутке стека Go выполняет отложенные функции. Если такая функция вызывает новую panic, она становится активной паникой для дальнейшей раскрутки. Внешний recover, выполненный в подходящем defer, извлечёт именно это новое значение.
Минимальный пример:
Внешний обработчик напечатает panic from defer. Значение original panic не будет возвращено этим recover.
Если отложенная функция сначала вызывает recover для исходной паники, а затем сама вызывает panic, новая паника всё равно станет распространяемой. Исходную причину в таком случае нужно явно сохранить, например записать в лог или включить в собственную диагностическую структуру до повторного вызова panic.
Практическое правило: код очистки не должен без необходимости паниковать. Для потенциально ненадёжной очистки используют внутренний защищённый обработчик, который подавляет только ошибку очистки, не перехватывая внешнюю панику. Это сохраняет исходную причину, но требует осознанно решить, достаточно ли логирования вторичной ошибки.
Нельзя полагаться на recover вне отложенной функции или в другой горутине: он не извлекает произвольную панику из стека и не превращает межгорутинный сбой в локальную ошибку.
HTTP-сервис вызывает бизнес-операцию, которая паникует из-за нарушения внутреннего инварианта. В defer обработчика одновременно завершается span трассировки; из-за ошибки в реализации экспортера span также возникает паника.
Первый вариант — оставить всё как есть. Он прост, но внешний middleware увидит сбой экспортера и потеряет исходную причину.
Второй вариант — восстановить исходную панику в каждом defer. Это может полностью остановить её распространение и случайно превратить аварийный сбой в продолжение работы, что опасно для повреждённого состояния.
Выбранный вариант — сделать завершение span безопасным: обернуть только операцию трассировки во внутренний защищённый обработчик, записать ошибку экспортера отдельно и не допустить её выхода наружу. В результате внешний recover получает исходную панику, а вторичная проблема остаётся доступной для диагностики.
1. Если defer вызывает recover, а затем возвращается без новой паники, что произойдёт с исходной паникой?
Она будет остановлена. recover должен выполняться непосредственно в отложенной функции во время активной раскрутки; после успешного восстановления функция продолжит выполнение с конца этого defer, а паника дальше не пойдёт. Поэтому бездумный recover в общем middleware может скрыть программную ошибку.
2. Меняет ли порядок регистрации нескольких defer результат при возникновении паники?
Да. Отложенные функции выполняются в обратном порядке регистрации: последняя зарегистрированная запускается первой. Если ранний по времени defer вызывает новую панику, последующие обработчики увидят уже её; это может изменить и значение, получаемое внешним recover, и набор выполненных действий.
3. Как сохранить исходную панику и одновременно сообщить об ошибке очистки?
Нужно не допускать выхода паники очистки из защищённого участка: поймать её во внутренней функции, записать обе причины в журнал или другой канал диагностики и позволить исходной панике продолжить раскрутку. Простое добавление второго panic не объединяет причины автоматически и, напротив, обычно делает вторичную причину основной для внешнего recover.