Где используется recover в Go?
Что проверяют
Понимаете ли вы, что паника это не замена ошибкам, и знаете ли редкие места, где восстановление уместно.
Как отвечать
recover останавливает панику и возвращает переданное ей значение. Работает только при вызове внутри отложенной функции: вне defer он всегда вернёт nil.
func handle(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("паника в обработчике: %v\n%s", rec, debug.Stack())
http.Error(w, "внутренняя ошибка", http.StatusInternalServerError)
}
}()
...
}Где восстановление действительно уместно:
- на границе обработки запроса: паника в одном обработчике не должна ронять весь сервер. В стандартном
net/httpтакая защита встроена, в своих воркерах и обработчиках очередей её пишут руками; - в долгоживущем цикле обработки: одна плохая задача не должна останавливать пул;
- в библиотеке, которая внутри использует панику для быстрого выхода из глубокой рекурсии: панику ловят на границе пакета и возвращают наружу обычную ошибку. Так устроен, например, разбор в некоторых парсерах;
- чтобы дописать контекст в лог перед завершением.
Важные ограничения: паника в другой горутине этой защитой не ловится, каждая горутина отвечает за себя, и незащищённая паника валит весь процесс. Некоторые состояния восстановлению не подлежат вовсе: нехватка памяти, гонка при одновременной записи в карту. И после восстановления состояние программы может быть неконсистентным, поэтому продолжать как ни в чём не бывало нельзя.
Где спотыкаются
Вызывают recover прямо в теле функции, а не в отложенной, и удивляются, что он не работает.
Используют панику вместо возврата ошибки в обычной логике.
Запускают горутину без защиты и роняют весь сервис одной ошибкой в фоновой задаче.