Может ли recover в одной горутине перехватить panic, возникшую в другой?
Нет. recover действует только внутри той же горутины, в которой возникла panic, и вызывается из отложенной функции во время раскрутки её стека. Для безопасного перехвата паники в рабочей горутине нужно установить defer с recover непосредственно внутри этой горутины; обработчик в вызывающей горутине её панику не увидит.
Модель конкурентности Go строится вокруг независимых горутин с собственными стеками выполнения. Поэтому механизм panic/recover привязан к конкретному стеку, а не является глобальным каналом уведомлений между горутинами.
Обычные ожидаемые сбои в Go принято передавать как значения error. panic предназначена для аварийных ситуаций или нарушений внутренних предположений, а recover позволяет локализовать такую аварию на границе компонента, если это действительно необходимо.
Представим функцию, которая запускает рабочую горутину и сама ожидает её завершения. Если паника возникнет в рабочей горутине, defer с recover во внешней функции не сработает: внешняя функция находится на другом стеке.
Неверная попытка перехватить панику снаружи может привести к завершению всей программы вместо передачи сбоя как ошибки. Особенно опасно это для серверов, очередей и фоновых обработчиков, где одна неисправная задача не должна автоматически останавливать весь процесс.
Каждая горутина должна сама установить границу восстановления, если компонент допускает преобразование паники в ошибку. recover возвращает значение паники только при вызове из отложенной функции той же горутины и в момент, когда эта горутина раскручивает стек из-за паники.
Минимальный пример:
Здесь recover выполняется в той же горутине, что и panic, поэтому паника превращается в ошибку и передаётся через канал. Если убрать runSafely и разместить recover только в main, он не перехватит панику рабочей горутины.
Перехватывать следует не каждую панику без разбора. Нужно решить, можно ли после неё безопасно продолжать работу: паника может указывать на повреждение инвариантов или неконсистентное состояние. Также важно передавать результат выполнения через канал, errgroup или другой согласованный механизм, а не пытаться использовать recover как замену обычной обработке error.
В HTTP-сервисе каждый запрос обрабатывается в отдельной горутине. В одном обработчике обнаружилась ошибка программиста, вызывающая panic. Если серверный слой не устанавливает восстановление в горутине запроса, авария может завершить весь процесс.
Рассматривались два варианта. Первый — поставить recover только вокруг вызова запуска обработчика: это не работает, потому что обработчик выполняется в другой горутине. Второй — добавить defer с recover в middleware, выполняемом внутри горутины запроса: это изолирует сбой, позволяет записать стек и вернуть клиенту контролируемый ответ.
Выбран второй вариант, но только как защитная граница сервера. Внутри бизнес-логики ожидаемые ситуации по-прежнему возвращаются через error, а после восстановления запрос помечается неуспешным и состояние, потенциально затронутое паникой, не переиспользуется. В результате единичная ошибка не обрушивает процесс, но скрытые дефекты не маскируются обычной логикой обработки ошибок.
recover в любой функции той же горутины?Нет. Он должен быть вызван из функции, зарегистрированной через defer, пока стек этой горутины раскручивается из-за паники. Обычный вызов recover после завершения паники или вне отложенной функции вернёт nil и не остановит аварийное завершение.
recover?Передать само значение можно, но это уже не восстановление исходной паники. recover работает только с активной паникой текущей горутины; полученное через канал значение можно лишь обработать как обычные данные или ошибку. Чтобы преобразовать исходную панику, восстановление нужно выполнить до отправки результата в другую горутину.
recover?Паника продолжит раскрутку стека этой горутины. Если она не будет восстановлена в этой же горутине, выполнение программы завершится с диагностикой паники; обработчик в другой горутине не сможет вмешаться. Поэтому границу recover размещают там, где создаётся или обслуживается горутина, а не произвольно на вызывающем уровне.