Охота на EOutOfResources

Вопрос:

Есть ли простой способ получить список типов ресурсов, которые просочились в работающее приложение? IOW путем подключения к приложению?

Я знаю, что memproof может это сделать, но он настолько тормозит, что приложение не будет работать даже минуту. Большинство лайков менеджера задач могут показывать номер, но не тип.

Это не проблема, что сама проверка является катастрофической (останавливает процесс приложения), так как я могу проверить с помощью taskmgr, если я подхожу близко (или, по крайней мере, я надеюсь)

Любое другое понимание поиска утечек ресурсов (не памяти) также приветствуется.

Фон:

У меня есть приложение Delphi 7/2006/2009 (компилируется со всеми тремя), и через несколько недель оно начинает вести себя забавно. Однако только в одном из мест, где он работает, в нескольких других системах он работает, пока не отключится питание.

Я попытался вставить некоторый отладочный код, чтобы сузить проблему. и обнаружил, что исключением является EOutofResources при сохранении файла. (сохранение файла может происходить тысячи раз в день).

Я пытался выяснить утечки памяти (с fastmm), но так как поток данных довольно высок (60 Мбайт / с от гигабитной промышленной камеры), я могу исключить только "ползучие" утечки памяти с fastmm, а не быстрые вспышки утечек памяти, которые истощают память о времени, когда это происходит. Если что-то идет не так, приложение заполняет память менее чем за полминуты.,

Основными подозреваемыми являются файловые дескрипторы, которые каким-то образом остаются при некоторых ошибках, и TMetafiles (которые передаются в эти файлы). Незначительными подозреваемыми являются VST, popupmenu и tframes

Обновления:

Другой возможный совет: в течение двух лет он работал нормально с D7, и теперь проблемы с Turbo Explorer (который я использую для стабильных проектов, не преобразованных в D2009).

Пол-Ян: Поскольку это происходит только раз в неделю (а это может происходить ночью), сбор информации происходит медленно. Именно поэтому я задаю этот вопрос, нужно объединить вещи, когда я буду там в четверг. Короче говоря: нет, я не знаю, уверен на 100%. Я намерен принести всю коллекцию Systemtools, чтобы посмотреть, смогу ли я найти что-то (потому что тогда она будет работать в течение нескольких дней). Также есть вероятность, что я вижу открытые файлы. (возможно, следует попытаться найти mingw lsof и запланировать это)

Но приложение видит очень мало действий с графическим интерфейсом (это приложение для проверки машинного зрения), за исключением обновления экрана +/- 15/ с, которое представляет собой tbitmap stretchdraw + tmetafile, но я получаю эту ошибку, когда дескрипторы сохранения на диск (TFileStream), вероятно, действительно истощены. Однако в том же потоке TMetafile также сохраняется в потоковом режиме, чего у более поздних приложений больше нет, и они могут работать в течение нескольких месяцев.

------------------- ОБНОВИТЬ

Я искал и искал и искал, и мне удалось воспроизвести проблемы в пробирке два или три раза. Проблемы возникали, когда значение memusage составляло +/- 256 МБ (системы имеют 2 ГБ), пользовательские объекты 200, объекты gdi 500, ни один файл не был открыт больше, чем ожидалось).

Это не совсем исключительное. Я замечаю, что я пропускаю небольшое количество дескрипторов, возможно, из-за перерисовки кадров (что-то в VCL, похоже, пропускает HPalette), но я подозреваю, что основная причина - это другая проблема. Я повторно использую TMetafile, и.clear его между ними. Я думаю, что очистка метафайла на самом деле (всегда?) Не изменяет размер ресурса, в конечном итоге каждый метафайл во всем пуле tmetafile в максимальном размере, и с 20-40+ tmetafiles (которые могут быть несколько 100ks каждый) это попадет на рабочий стол предел кучи.

Это теория, но я попытаюсь проверить это, установив ограничение рабочего стола на 10 МБ для клиентов, но пройдет несколько недель, прежде чем я получу подтверждение, если это что-то изменит. Эта теория также подтверждает, почему эта машина особенная (возможно, эта машина имеет в среднем метафайлы немного большего размера). Иногда может помочь освобождение и воссоздание tmetafile в пуле.

К счастью, все эти проблемы (как tmetafile, так и reparenting) уже разработаны в новых поколениях приложений.

Из-за особых обстоятельств (а также из-за того, что у меня очень ограниченные тестовые окна), это займет некоторое время, но я решил пока использовать кучу рабочих столов в качестве примера (хотя материал GDILeaks также был несколько полезен).

Другое дело, что аудит выявил использование GDI-типов в потоке (хотя сохранял только tmetafiles (которые не использовались или не подключались иным образом) в потоки.

------------- Обновление 2.

Увеличение лимита рабочего стола, казалось, лишь незначительно увеличивало время до возникновения проблемы.

К сожалению, я не смогу продолжить этот процесс, так как машины были обновлены до более новой версии фреймворка, у которого нет проблем.

Подводя итог, я могу только заявить, что три основных модификации шли от старого к новому фреймворку:

  • Я больше не меняю экраны, перерисовывая кадры. Теперь я работаю с формами, которые я скрываю и показываю. Я изменил это, так как у меня также были очень редкие сбои или исключения (которые могли быть удалены) из-за этого. Аварии происходили во время работы с графическим интерфейсом, а не спонтанно, как основная проблема
  • Рутина, в которой произошел сбой, касалась TMetafile. TMetafile был разработан и заменен более простым форматом собственного производства. (в основном массивы с вершинами Opengl)
  • Рисование больше не происходило с tbitmap с накладываемым на него надписью tmetafile, но с использованием OpenGL.

Конечно, это может быть что-то еще, что изменилось в переписывании вышеупомянутых частей, исправляя некоторые очень неприятные ошибки детализации. Это должно быть очень плохо, так как я проанализировал вышеупомянутую систему настолько, насколько мог.

Обновленный ноябрь 2012 после частной дискуссии по почте: в ретроспективе следующим шагом было бы добавление счетчика к объектам метафайлов и просто повторное создание их при каждом использовании x * 1000 или около того, и посмотреть, изменит ли это что-нибудь. Если у вас есть похожие проблемы, попробуйте посмотреть, сможете ли вы регулярно уничтожать и повторно инициализировать долгоживущие ресурсы, которые динамически распределяются.

7 ответов

Решение

Если это утечка GDI, вы можете взглянуть на MSDN Magazine January 2003, в котором используется инструмент GDILeaks. Другими инструментами являются GDIObj или GDIView. Также смотрите здесь.

Другим источником EOutOfResources может быть то, что куча рабочего стола заполнена. У меня была эта проблема на загруженных терминальных серверах с большими экранами.

Если у вас много утечек файловых дескрипторов, вы можете проверить Process Explorer, взглянуть на дескрипторы открытых файлов вашего процесса и увидеть любые необычные. Или используйте WinDbg с командой ! Htrace.

Существует небольшая вероятность того, что ошибка вводит в заблуждение. VCL наивно сообщает EOutOfResources, если он не может получить DC для окна (см. TWinControl.GetDeviceContext в Controls.pas).

Я говорю "наивно", потому что существуют другие причины, по которым GetDC() может возвращать дескриптор NULL, а VCL должен сообщать об ошибке ОС, а не предполагать состояние нехватки ресурсов (требуется проверка версии Windows, чтобы это было надежно возможным, но VCL мог и должен принять это тоже).

У меня была ситуация, когда я получал ошибку EOutOfResources в результате того, что дескриптор окна стал недействительным. Как только я обнаружил настоящую проблему, найти причину и устранить ее было просто, но я потратил много- много часов, пытаясь найти несуществующую утечку ресурсов.

Если возможно, я бы изучил трассировку стека, ведущую к этому исключению - если он исходит из TWinControl.GetDeviceContext, тогда проблема может быть не в том, что вы думаете (невозможно сказать, что это может быть, конечно, но устранение невозможного всегда является первым шаг к поиску решения, каким бы невероятным оно ни было).

Я столкнулся с этой проблемой раньше. Из того, что я смог сказать, Delphi может выдавать EOutOfResources в любое время, когда Windows API возвращает ERROR_NOT_ENOUGH_MEMORY, и (как обсуждаются здесь другие ответы) Windows может возвращать ERROR_NOT_ENOUGH_MEMORY для различных условий.

В моем случае EOutOfResources был вызван TBitmap - в частности, вызов TBitmap к CreateCompatibleBitmap, который он использует со своим PixelFormat по умолчанию pfDevice. Очевидно, что Windows может применять довольно строгие системные ограничения на объем памяти, доступной для зависящих от устройства растровых изображений (см., Например, это обсуждение), даже если ваша система в противном случае имеет много памяти и много ресурсов GDI. (Эти общесистемные ограничения, очевидно, обусловлены тем, что Windows может выделять зависимые от устройства растровые изображения в памяти видеокарты.)

Решение состоит в том, чтобы просто использовать независимые от устройства растровые изображения (DIB) вместо этого (хотя они могут не обеспечивать столь же хорошую производительность). Чтобы сделать это в Delphi, установите TBitmap.PixelFormat в любое значение, отличное от pfDevice. В этой статье базы знаний описывается, как выбрать оптимальный формат DIB для устройства, хотя я обычно просто использую pf32Bit вместо того, чтобы пытаться определить оптимальный формат для каждого из мониторов, на которых отображается приложение.

В большинстве случаев я видел EOutOfResources, это была какая-то утечка дескриптора.

Вы пробовали что-то вроде MadExcept?

--jeroen

"Я попытался вставить некоторый отладочный код, чтобы сузить проблему. Я обнаружил, что исключение составляет EOutofResources при сохранении файла (сохранение файла может происходить тысячи раз в день)".

Я снимаю в темноте, но может ли быть так, что вы используете Windows API для (GetTempFileName) создания временного файла, и вы выдавливаете некоторые индексы файловой системы или забыли закрыть дескриптор файла?

В любом случае, я согласен, что с вашим предположением, что это проблема с дескриптором файла. Кажется, это наиболее вероятно, учитывая ваши симптомы и диагноз.

Также попробуйте проверить количество дескрипторов для приложения с помощью Process Explorer из SysInternals. Утечки ручек могут быть очень опасными, и они со временем накапливаются медленно.

В настоящее время у меня возникает эта проблема в программном обеспечении, которое явно не пропускает никаких дескрипторов в моем собственном коде, поэтому, если есть утечки, они могут происходить в исходном коде компонента или в самом исходном коде VCL.

Количество дескрипторов, GDI и количество пользовательских объектов не увеличиваются, и ничего не создается. Ответ Делтика показывает угловые случаи, когда сообщение является разновидностью красной сельди, и Аллен предполагает, что даже запись в файл может вызвать эту ошибку.

Пока что лучшая стратегия, которую я нашел для их поиска, - это использовать либо трассировки стека JCL JCLDEBUG, либо функции сохранения отчетов об исключениях в MadExcept, чтобы сгенерировать контекстную информацию, чтобы выяснить, что на самом деле не работает.

Во-вторых, AQTime содержит много инструментов, которые могут вам помочь, в том числе профилировщик ресурсов, который может хранить ссылки между тем, где находится код, который создал ресурсы, и как он вызывался, а также подсчетами общего числа дескрипторов. Он может захватывать результаты MID RUN, и поэтому он не ограничивается обнаружением неосвобожденных ресурсов после выхода. Итак, запустите AQTime, сделайте захват результатов в середине выполнения, подождите несколько часов и повторите захват, и у вас должно быть два момента времени для сравнения количества дескрипторов. На всякий случай это очевидная вещь. Но, как мудро указывает Делтикс, этот класс исключений возникает в тех случаях, когда он, вероятно, не должен был быть.

Я потратил весь сегодня на поиски этой проблемы. Я нашел множество полезных ресурсов, указывающих мне в сторону GDI, с тем фактом, что я использую GDI+ для создания высокоскоростной анимации непосредственно в основной форме с помощью timer/invalidate/onpaint (анимация выполняется в отдельном потоке). У меня также есть панель в этой форме с некоторыми динамически создаваемыми элементами управления, чтобы пользователь мог вносить изменения в анимацию.

Это было крайне случайно и спонтанно. Это не сломалось бы нигде в моем коде, и когда появилось диалоговое окно с ошибкой, анимация в основной форме продолжала работать. В какой-то момент две из этих ошибок возникли одновременно (в отличие от последовательных).

Я внимательно изучил свой код и убедился, что у меня нет дескрипторов, связанных с GDI. Фактически, согласно диспетчеру задач, все мое приложение имеет менее 300 дескрипторов. Тем не менее, эта ошибка будет появляться случайно. И это всегда будет соответствовать простейшему действию, связанному с пользовательским интерфейсом, например, просто наведению указателя мыши на стандартный элемент управления VCL.

Решение

Я считаю, что решил эту проблему, изменив логику выполнения рисования в настраиваемом элементе управления, а не непосредственно в основной форме, как я делал раньше. Я думаю, что тот факт, что я быстро рисовал на одном и том же холсте формы, который разделял другие элементы управления, каким-то образом мешал. Теперь, когда у него есть собственный холст, на котором можно рисовать, он, кажется, полностью исправлен.

То есть, по крайней мере, с 1 часом интенсивного тестирования.

[Скрещенные пальцы]

Другие вопросы по тегам