В сервисе сохраняют пойманное исключение, после чего память большого локального объекта не освобождается: какая связь в traceback объясняет это удержание?
Объект удерживается цепочкой traceback → frame → локальные переменные. Сохранённое исключение хранит traceback, traceback ссылается на кадр выполнения функции, а кадр — на его локальные объекты, включая уже ненужный большой объект. Чтобы разорвать удержание, нужно не хранить исключение с traceback без необходимости либо очистить traceback перед длительным сохранением.
Traceback предназначен для диагностики: он сохраняет место возникновения ошибки, последовательность вызовов и состояние локальных переменных, доступное через кадры стека. Это делает сообщения об ошибках полезными для отладки, но одновременно означает, что traceback не является только текстом — он может сохранять ссылки на объекты из завершившихся вызовов.
Такая модель особенно важна для долгоживущих процессов, где исключения могут попадать в очереди, кэши, списки ошибок или объекты мониторинга. В короткоживущем скрипте удержание обычно незаметно, а в сервисе оно превращается в накопление памяти.
Функция обработала большой буфер и завершилась с исключением. Интуитивно кажется, что после выхода из функции её локальные переменные исчезли, но это верно только при отсутствии других ссылок на соответствующий кадр или его локальные значения.
Если приложение сохраняет исключение для повторной обработки, передаёт его через очередь или помещает в структуру диагностики, вместе с ним может сохраниться traceback. Последствия — рост памяти, повышенный RSS и misleading-профилирование: объект выглядит как утечка, хотя его удерживает обычная достижимая ссылка.
У исключения есть ссылка на объект traceback. Traceback содержит ссылку на предыдущий traceback и на frame — кадр, в котором произошла ошибка. Frame содержит локальное пространство имён, поэтому большой локальный объект остаётся достижимым даже после возврата из функции.
Минимальная иллюстрация:
В этом примере saved удерживает исключение. До очистки его traceback ведёт к кадру обработавшей функции, а через локальные переменные — к large. traceback.clear_frames очищает локальные ссылки в кадрах traceback, а with_traceback(None) убирает саму цепочку traceback у сохраняемого исключения.
Если исключение не нужно хранить, предпочтительно ограничить его область жизни и не помещать объект исключения в долгоживущую структуру. Для долговременной диагностики обычно сохраняют текст, тип, идентификатор запроса и отформатированный traceback, а не само исключение с живыми frame-ссылками.
Очистка traceback не всегда может освободить кадр немедленно: нельзя очистить текущий выполняющийся frame, а другие независимые ссылки на объект всё равно сохранят его. Кроме того, освобождение объекта Python не гарантирует немедленного уменьшения RSS процесса: это отдельный вопрос поведения аллокатора и операционной системы.
В очереди ошибок сервиса хранились объекты исключений для повторной отправки в систему мониторинга. После редкой ошибки с большим временным буфером память процесса увеличивалась на десятки мегабайт за каждый такой элемент.
Рассматривались три варианта:
Выбрали третий вариант для внутренней очереди и дополнительно ограничили её размер. Для внешнего мониторинга сохраняли отформатированный traceback как текст. После этого большой буфер перестал удерживаться очередью, а диагностика ошибки осталась доступной.
del для большой локальной переменной внутри функции?Нет, это не универсальное решение. del удаляет имя из локального пространства имён, но если объект уже удерживается другой ссылкой, например в замыкании, глобальной структуре, другом локальном имени или traceback, он не освобождается. В рассматриваемом случае удаление имени до возникновения исключения может помочь, но правильнее устранить долгоживущую ссылку на traceback либо очистить его.
Отформатированный traceback — это строковое представление стека и ошибки. Оно не обязано сохранять исходные frame-объекты и их локальные значения, поэтому большой буфер из завершившейся функции обычно не удерживается. Однако форматирование может раскрыть чувствительные данные из локальных переменных, поэтому содержимое нужно контролировать отдельно от вопроса потребления памяти.
Нет. Это может быть намеренное время жизни объекта: например, система временно хранит ошибку до отправки уведомления. Утечкой это становится, когда ссылка сохраняется дольше требуемого срока или количество таких ссылок не ограничено. При диагностике нужно проверить цепочку удержания и срок жизни владельца ссылки, а не считать любой traceback ошибкой сам по себе.