Во время паники внутри одной функции зарегистрировано несколько defer. В каком порядке они выполняются?
Действия, отложенные через defer, выполняются в порядке LIFO: последняя зарегистрированная функция выполняется первой. При панике Go запускает отложенные функции во время раскрутки стека, поэтому они выполняются до передачи паники вызывающей функции.
Defer был введён как механизм структурированного освобождения ресурсов: закрытия файлов, разблокировки mutex и восстановления состояния. Его поведение не зависит от способа выхода из функции — обычного возврата или паники.
Такой подход позволяет размещать очистку рядом с операцией, которая требует очистки, вместо ручного дублирования действий во всех ветвях выхода.
Неверное понимание порядка defer приводит к ошибкам при работе с зависимыми ресурсами. Например, если ресурс B зависит от ресурса A, сначала нужно освободить B, а затем A.
При панике отложенные функции всё равно запускаются. Если очистка зарегистрирована в неправильном порядке, внешний код может увидеть уже повреждённое состояние или получить ошибку при закрытии ресурса.
Каждый вызов defer добавляет функцию в стек отложенных вызовов текущей функции. При завершении функции этот стек обрабатывается с конца к началу.
Результат будет таким:
Сначала выполняется последний зарегистрированный defer, затем предыдущий. После завершения всех defer в work паника продолжает раскрутку стека и достигает обработчика в main.
То же правило LIFO действует и при обычном возврате. При панике дополнительно важно, что отложенные функции выполняются в каждой функции по пути раскрутки стека. Если deferred-функция сама вызовет panic, начнётся обработка новой паники; это может скрыть исходную причину.
Для зависимых ресурсов регистрацию обычно выполняют в обратном порядке освобождения: сначала регистрируют очистку внешнего ресурса, затем внутреннего. Тогда при завершении функции внутренний ресурс будет закрыт первым.
Функция открывает транзакцию и создаёт временный файл, используемый внутри этой транзакции. Возможны три варианта.
Выбирают второй вариант: каждый ресурс получает свой defer сразу после успешного создания. В результате очистка выполняется и при обычном возврате, и при панике, а порядок закрытия соответствует зависимостям ресурсов.
Да. Перед фактическим возвратом функция выполняет все зарегистрированные defer в обратном порядке. Поэтому defer нельзя считать механизмом, предназначенным только для обработки паники.
Нет. Defer выполняются как часть выхода из функции, но обычное выполнение с места паники не возобновляется. Если какой-либо deferred-обработчик вызвал recover корректно, паника прекращается; сама функция после этого не продолжает инструкции, расположенные после точки сбоя.
Начнётся обработка новой паники. Остальные отложенные функции текущей функции и вызывающих функций продолжат выполняться, но исходная паника может быть потеряна как непосредственная причина. Поэтому код очистки в defer не должен без необходимости паниковать; ошибки очистки следует аккуратно обработать или явно согласовать с политикой обработки основной ошибки.