Поиск несуществующего memory leak

Posted by Андрей on Февраль 09, 2008

Очень забавная история. Был работающий и написанный сервер вещаний (a-la Red5) на Python. В связи с развитием основной класс – класс протокола – был разрезан на несколько более мелких классов, которые собирались вместе множественным наследованием в единый монолит, который должен был по функциональности повторять исходный класс (до рефакторинга). Как показало вскрытие, старая версия стабильно работает неделю за неделей, а новая версия через неопределенный, но достаточно короткий период времени (несколько часов) откушивает всю доступную память и погибает.

Типичная ситуация для memory leak, причём опытным путём стало понятно, что эта утечка как-то коррелирует с нагрузкой на сервер (что логично), но зависимость неоднозначная. Момент появления утечки был сужен до одного коммита – казалось бы дело за малым. Анализ исходников в поисках того момента, когда была внесена утечка ничего дает – исходники испаханы вдоль и поперек. Ничего.

Анализ во время выполнения с помощью с помощью довольно замысловатых утилит из этого поста ни к чему не привёл – нет ничего. Я пробовал считать количество объектов, которые видит модуль gc в питоне (то есть тех, кто поддерживает garbage collection), но количество не растет существенно и пропорционально нагрузке на сервер. Однако память процесс кушает и довольно быстро.

В проекте было расширение, написанное на Си, – его анализ, исправление мелких неточностей результата не дает.

Остается неутешительный вывод – в силу того, что число объектов в программе не растет, то значит либо растет размер каких-то объектов (например, строк путем конкатенации), либо это какая-то бага в Python, ОС или где-то еще.

Последнее выглядит романтично и привлекательно, но на самом деле ничего не дает – такую ошибку еще тяжелее найти и поправить, однако багтрекеры FreeBSD, Python и Twisted молчат по поводу таких ошибок.

Двигаемся дальше – создается тестовый клиент вещаний, который имитирует нагрузку на сервер: разное количество вещаний, авторы, подписчики вещания, различные ошибочные ситуации, чат. Тест, запущенный локально с локальным сервером (а нелокально тестовое окружение собрать тяжело – поток порядка 50-100 Мбит/с) не дает результатов – никаких следов утечки памяти ни за час, ни за два.

Отчаяние, практически неделя потерянного впустую времени, тестирование различных вариантов… Что еще? Есть еще Twisted-Conch+Twisted-Manhole – живая консоль с интерпретатором питона в работающем сервере. Но и она не дает никаких результатов – garbage collection работает, никаких разумных объектов, которые могли бы дать утечку памяти нет.

Озарение как всегда приходит в тот момент, когда его совсем не ждешь: ведь сервер вещает, то есть получает на вход поток в 0,5 Мбит/с, раздает его 200 клиентам и делает на выходе 100 Мбит/с, если у кого-то из этих 200 клиентов канал меньше по ширине 0,5 Мбит/с, то данные в буферах процессах начнут скапливаться. Для этого есть решение – дроп пакетов, он был реализован и работал…

Эврика! После разделения большого класса на кучу мелких фрагментов некоторые методы «разрезались» на части. И в них надо поставить вызов метода предка, чтобы все «куски» метода отработали, как и раньше. Но ведь в питоне есть еще MRO (Method Resolution Order), и надо отвыкнуть от того, что базовый класс в моём ощущении является последним в цепочке, то есть у него нет super(Klass, self) – он есть! Просто метод, который включал анализ буфера записи, который в свою очередь вовремя включал механизм пропуска пакетов отключился. И хотя всё работало, но пропуск пакетов не включался и буферы записи росли неограниченно. Исправление в одной строке – вызова метода предка решило проблему. Обидно :)

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

Comments