Программирование GoGo CoreРазработчик серверных приложений на Go

В какой точке выполнения recover действительно перехватывает панику в Go?

В какой точке выполнения recover действительно перехватывает панику в Go?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

recover перехватывает панику только при прямом вызове из функции, зарегистрированной через defer, пока паника распространяется в той же горутине. Вызов recover из обычной функции, вызванной отложенной функцией, не восстанавливает выполнение.

После успешного восстановления функция, содержащая отложенный обработчик, завершается; выполнение не возвращается к инструкции, вызвавшей panic.

Исторический контекст

Модель panic/recover предназначена для исключительных ситуаций, когда продолжать обычное выполнение невозможно или нежелательно. Штатные ожидаемые ошибки в Go обычно возвращают явно, а panic и recover позволяют обозначить границу аварийного завершения, например на уровне обработчика запроса.

Связка с defer обеспечивает выполнение завершающей логики при раскрутке стека: освобождение ресурсов, запись диагностики и, при необходимости, преобразование паники в контролируемый результат.

Постановка проблемы

Недостаточно просто вызвать recover: важны момент вызова и место, где он выполняется. Если обработчик вызовет recover слишком поздно, в другой горутине или через промежуточную функцию, паника продолжит распространяться и может завершить горутину с аварией.

Ошибочная попытка продолжить работу после паники также опасна. Состояние функции, вызвавшей panic, уже не считается обычной точкой продолжения, поэтому код после неё не возобновляется.

Подробное решение

Во время паники Go раскручивает стек вызовов текущей горутины и выполняет отложенные функции. Если одна из них непосредственно вызывает recover, паника останавливается, а recover возвращает переданное в panic значение.

package main import "fmt" func guarded() { defer func() { if value := recover(); value != nil { fmt.Println("caught:", value) } }() panic("boom") } func main() { guarded() fmt.Println("after") }

Здесь анонимная функция является непосредственно отложенным обработчиком, поэтому паника перехватывается. После завершения этой функции guarded возвращает управление вызывающему коду, и main печатает after.

Критично различать прямой и косвенный вызов. Если отложенная функция вызывает другую функцию, а уже та вызывает recover, восстановление не произойдёт: recover должен быть вызван непосредственно отложенной функцией, выполняемой в процессе обработки паники.

Паника локальна для горутины. Обработчик в одной горутине не может перехватить панику другой; для каждой горутины нужна собственная граница с defer и recover.

Использовать recover следует на чётко определённой границе, например в адаптере HTTP-обработчиков или в рабочем цикле фоновой горутины. Не стоит повсеместно скрывать паники: это может замаскировать ошибку программы и привести к продолжению работы с повреждёнными предположениями.

Ситуация из практики

В серверном приложении пользовательский обработчик может вызвать panic, а внешний HTTP-слой должен вернуть контролируемый ответ и записать стек вызовов.

Вариант без recover прост, но одна паника может прервать обработку запроса или завершить рабочую горутину. Вариант с recover внутри каждого обработчика даёт локальный контроль, однако дублирует код и повышает риск забыть обработчик.

Практичнее разместить единый отложенный обработчик в middleware или в функции запуска рабочей горутины. Он перехватывает панику на границе подсистемы, логирует значение и стек, после чего возвращает безопасный результат; при этом ошибки, которые ожидаемо обрабатываются бизнес-логикой, по-прежнему передаются через error.

Что кандидаты часто упускают

  1. Можно ли вызвать recover в обычной функции, если она была вызвана из deferred-обработчика?

Нет. Вызов должен быть непосредственным: отложенная функция сама должна выполнить recover. Если deferred-функция вызывает промежуточную функцию, а та вызывает recover, результатом будет nil, и паника не будет остановлена.

  1. Может ли recover в одной горутине перехватить панику из другой?

Нет. Раскрутка стека и выполнение отложенных функций происходят внутри горутины, где возникла паника. Поэтому запуск работы в отдельной горутине требует отдельного defer с recover внутри этой горутины.

  1. Продолжится ли выполнение с инструкции после вызова panic после успешного recover?

Нет. После восстановления завершается функция, в которой сработал отложенный обработчик, а управление возвращается её вызывающему коду. Инструкции, расположенные после panic в той же функции, не выполняются.