Database
 sql >> база данни >  >> RDS >> Database

Обработка на изтичане на GDI ресурс

Изтичането на GDI (или просто използването на твърде много GDI обекти) е един от най-често срещаните проблеми. Това в крайна сметка причинява проблеми с изобразяването, грешки и/или проблеми с производителността. Статията описва как отстраняваме този проблем.

През 2016 г., когато повечето програми се изпълняват в пясъчни кутии, откъдето дори и най-некомпетентният разработчик не може да навреди на системата, аз съм изумен да се изправя пред проблема, за който ще говоря в тази статия. Честно казано, надявах се, че този проблем е отишъл завинаги заедно с Win32Api. Въпреки това се сблъсках с това. Преди това просто чух истории на ужасите за него от стари по-опитни разработчици.

Проблемът

Изтичане или използване на огромното количество GDI обекти.

Симптоми

  1. Колоната GDI обекти в раздела Подробности на диспечера на задачите показва критични 10000 (ако тази колона липсва, можете да я добавите, като щракнете с десния бутон върху заглавката на таблицата и изберете Избор на колони).
  2. При разработване на C# или на други езици, които се изпълняват от CLR, възниква следната грешка с недостатъчна информация:
    Съобщение:Възникна обща грешка в GDI+.
    Източник:System.Drawing
    TargetSite:IntPtr GetHbitmap(System.Drawing.Color)
    Тип:System.Runtime.InteropServices.ExternalException
    Грешката може да не се появи при определени настройки или в определени версии на системата, но приложението ви няма да може да изобрази нито един обект:
  3. По време на разработката в С/С++ всички GDI методи, като Create%SOME_GDI_OBJECT%, започнаха да връщат NULL.

Защо?

Системите на Windows не позволяват създаване на повече от 65535 GDI обекти. Тази цифра всъщност е впечатляваща и трудно мога да си представя нормален сценарий, изискващ толкова огромно количество предмети. Има ограничение за процесите – 10 000 на процес, който може да бъде променен (чрез промяна на HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota стойност в диапазона от 256 до 65535), но Microsoft не препоръчва увеличаване на това ограничение. Ако все пак го направите, един процес ще може да замрази системата, така че да не може да изведе дори съобщението за грешка. В този случай системата може да бъде възстановена само след рестартиране.

Как да поправя?

Ако живеете в удобен и управляван CLR свят, има голяма вероятност да имате обичайно изтичане на памет във вашето приложение. Проблемът е неприятен, но е съвсем обикновен случай. Има поне дузина страхотни инструменти за откриване на това. Ще трябва да използвате който и да е профайлър, за да видите дали се увеличава броят на обектите, които обвиват GDI ресурси (Sytem.Drawing.Brush, Bitmap, Pen, Region, Graphics). Ако е така, можете да спрете да четете тази статия. Ако изтичането на обвиващи обекти не е било открито, вашият код използва директно GDI API и има сценарий, когато те не бъдат изтрити

Какво препоръчват другите?

Официалното ръководство на Microsoft или други статии по този въпрос ще ви препоръча нещо подобно:

Намерете всички Създаване %SOME_GDI_OBJECT% и да откриете дали съответният DeleteObject (или ReleaseDC за HDC обекти) съществува. Ако такъв DeleteObject съществува, може да има сценарий, който не го нарича.

Има леко подобрена версия на този метод, която съдържа допълнителна стъпка:

Изтеглете помощната програма GDIView. Може да показва точния брой GDI обекти по тип. Имайте предвид, че общият брой обекти не съответства на стойността в последната колона. Но можем да затворим очи за това, ако това ни помогне да стесним полето за търсене.

Проектът, върху който работя, има кодова база от 9 милиона записа, приблизително същото количество записи се намират в библиотеките на трети страни, стотици извиквания на функцията GDI, които са разпределени в десетки файлове. Изгубих много време и енергия, преди да разбера, че ръчният анализ без грешки е невъзможен.

Какво мога да предложа?

Ако този метод ви се струва твърде дълъг и досаден, значи не сте преминали всички етапи на отчаяние с предишния. Можете да опитате да следвате предишните стъпки, но ако това не помогне, не забравяйте за това решение.

В преследване на теча се запитах:Къде се създават изтичащите обекти? Беше невъзможно да се зададат точки на прекъсване на всички места, където се извиква API функцията. Освен това не бях сигурен, че това не се случва в .NET Framework или в някоя от библиотеките на трети страни, които използваме. Няколко минути в гугъл ме отведоха до помощната програма за монитор на API, която позволяваше да регистрирам и проследявам повиквания до всички системни функции. Лесно намерих списъка с всички функции, които генерират GDI обекти, намирах и ги избрах в API Monitor. След това зададох точки на прекъсване.

След това введох процеса на отстраняване на грешки Visual Studio и го избра в дървото Процеси. Петата точка на прекъсване се получи веднага:

Разбрах, че ще се удавя в този торент и че имам нужда от нещо друго. Изтрих точките на прекъсване от функциите и реших да видя дневника. Показваше хиляди обаждания. Стана ясно, че няма да мога да ги анализирам ръчно.

Задачата е Намиране на извикванията на GDI функциите, които не причиняват изтриването . Дневникът съдържа всичко, от което се нуждаех:списък с извиквания на функции в хронологичен ред, техните върнати стойности и параметри. Следователно трябваше да получа върната стойност на функцията Create%SOME_GDI_OBJECT% и да намеря извикването на DeleteObject с тази стойност като аргумент. Избрах всички записи в API Monitor, вмъкнах ги в текстов файл и получих нещо като CSV с разделителя TAB. Пуснах VS, където възнамерявах да напиша малка програма за синтактичен анализ, но преди да успее да се зареди, ми хрумна по-добра идея:да експортирам данни в база данни и да напиша заявка, за да намеря това, което ми трябва. Това беше правилният избор, тъй като ми позволи бързо да задавам въпроси и да получавам отговори.

Има много инструменти за импортиране на данни от CSV в база данни, така че няма да се спирам на тази тема (mysql, mssql, sqlite).

Имам следната таблица:

CREATE TABLE apicalls (
id int(11) DEFAULT NULL,
`Time of Day` datetime DEFAULT NULL,
Thread int(11) DEFAULT NULL,
Module varchar(50) DEFAULT NULL,
API varchar(200) DEFAULT NULL,
`Return Value` varchar(50) DEFAULT NULL,
Error varchar(100) DEFAULT NULL,
Duration varchar(50) DEFAULT NULL
)

Написах следната MySQL функция, за да получа дескриптора на изтрития обект от извикването на API:

CREATE FUNCTION getHandle(api varchar(1000))
RETURNS varchar(100) CHARSET utf8
BEGIN
DECLARE start int(11);
DECLARE result varchar(100);
SET start := INSTR(api,','); -- for ReleaseDC where HDC is second parameter. ex: 'ReleaseDC ( 0x0000000000010010, 0xffffffffd0010edf )'
IF start = 0 THEN
SET start := INSTR(api, '(');
END IF;
SET result := SUBSTRING_INDEX(SUBSTR(api, start + 1), ')', 1);
RETURN TRIM(result);
END

И накрая, написах заявка за намиране на всички текущи обекти:

SELECT creates.id, creates.handle chandle, creates.API, dels.API deletedApi
FROM (SELECT a.id, a.`Return Value` handle, a.API FROM apicalls a WHERE a.API LIKE 'Create%') creates
LEFT JOIN (SELECT
d.id,
d.API,
getHandle(d.API) handle
FROM apicalls d
WHERE API LIKE 'DeleteObject%'
OR API LIKE 'ReleaseDC%' LIMIT 0, 100) dels
ON dels.handle = creates.handle
WHERE creates.API LIKE 'Create%';

(По принцип той просто ще намери всички обаждания за изтриване за всички повиквания за създаване).

Както виждате от изображението по-горе, всички обаждания без нито едно изтриване са открити наведнъж.

И така, последният въпрос беше оставен:Как да определя откъде са извикани тези методи в контекста на моя код? И тук един фантастичен трик ми помогна:

  1. Стартирайте приложението във VS за отстраняване на грешки
  2. Намерете го в Api Monitor и го изберете.
  3. Изберете необходима функция в API и поставете точка на прекъсване.
  4. Продължавайте да щраквате върху „Напред“, докато не бъде извикан с въпросните параметри (наистина пропуснах условни точки на прекъсване от VS)
  5. Когато стигнете до необходимото обаждане, превключете към CS и щракнете върху Прекъсване на всички .
  6. VS Debugger ще бъде спрян точно там, където е създаден изтичащият обект и всичко, което трябва да направите, е да разберете защо не е изтрит.

Забележка:Кодът е написан за илюстрация.

Резюме:

Описаният алгоритъм е сложен и изисква много инструменти, но даде резултат много по-бързо в сравнение с тъпото търсене в огромната кодова база.

Ето обобщение на всички стъпки:

  1. Търсете течове на памет на GDI обвиващи обекти.
  2. Ако съществуват, елиминирайте ги и повторете стъпка 1.
  3. Ако няма течове, потърсете изрично извиквания към функциите на API.
  4. Ако количеството им не е голямо, потърсете скрипт, в който даден обект не е изтрит.
  5. Ако количеството им е голямо или трудно могат да бъдат проследени, изтеглете API Monitor и го настройте за регистриране на извиквания на GDI функциите.
  6. Стартирайте приложението за отстраняване на грешки във VS.
  7. Възпроизведете изтичането (това ще инициализира програмата, за да скрие кешираните обекти).
  8. Свържете се с API Monitor.
  9. Възпроизведете теча.
  10. Копирайте регистрационния файл в текстов файл, импортирайте го във всяка база данни (скриптовете, представени в тази статия са за MySQL, но могат лесно да бъдат приети за всяка система за управление на релационна база данни).
  11. Сравнете методите Create и Delete (можете да намерите SQL скрипта в тази статия по-горе) и намерете методите без извикванията Delete.
  12. Задайте точка на прекъсване в API Monitor при извикването на необходимия метод.
  13. Продължавайте да щракнете върху Продължи, докато методът бъде извикан с повторно придобити параметри.
  14. Когато методът бъде извикан с необходимите параметри, щракнете върху Break All в VS.
  15. Разберете защо този обект не е изтрит.

Надявам се, че тази статия ще ви бъде полезна и ще ви помогне да спестите време.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Време за подрязване от дата и час – последващо действие

  2. Липсващи индекси в MS SQL или оптимизация за нула време

  3. Reorgs на базата данни – защо са важни

  4. Променливата на средата LD_DEBUG

  5. Тайните на доминото, или модел на данни за играта домино