Od témy magazínu k projektovej praxi
Súvisiace stránky služieb a technológií k príspevku
Keď sa Delphi-aplikácia v prevádzke pomaly „nafukuje“, občas vypadne s Access Violations alebo po dňoch behu náhle začne byť nestabilná, často nejde o jedinú chybu, ale o vzorec: pamäť sa alokuje, ale nie je správne uvoľnená – alebo je uvoľnená príliš skoro a neskôr sa ešte používa. Práve tu má FastMM FullDebugMode veľkú hodnotu. Nie ako trvalý stav, ale ako cielený diagnostický nástroj, ktorý z „niečo je rozbité niekde v Heap-e“ spraví zrozumiteľnú príčinu.
Háčik: FullDebugMode produkuje veľa výstupu, stojí výkon a rýchlo vedie k nesprávnym interpretáciám. Ein Leak-Report neukáže automaticky miesto, kde je „chyba“. A ein Stacktrace je len tak dobrý, ako je rozlíšenie symbolov (MAP-súbor, debug-informácie, inlining). V tomto príspevku prechádzam typický okrajový prípad, vysvetľujem čistý prístup a nástrahy – tak, aby si na konci nielen našiel leaky, ale ich trvalo odstránil.
Kedy má FastMM FullDebugMode skutočný zmysel
FastMM je v moderných verziách Delphi často už predvoleným memory managerom alebo sa v mnohých projektoch aj tak integruje. FullDebugMode je však špeciálna konfigurácia: pridáva do pamäťových blokov kontrolné vzory, zhromažďuje allokačné stacktracy a agresívnejšie kontroluje korupciu v Heap-e (t. j. poškodené správcovské dáta v Heape, napr. spôsobené buffer-overrunmi).
Používam FullDebugMode cielene, ak nastane niektorý z týchto prípadov:
- Reprodukovateľný Leak: spotreba pamäte rastie počas testu na jeden úkon (napr. na Request, na Import, na UI-akciu).
- Sporadické AVs: najmä také, ktoré sa vyskytujú „sem-tam“ v tom istom mieste (klasicky: Use-after-free).
- Heap-Korupcia: hlásenia ako „Invalid pointer operation“, „Access violation in ntdll“ alebo pády pri ukončovaní/Finalizácii.
- Hľadanie regresie: po Refactoringu, aktualizácii knižnice alebo zmene kompilátora náhla nová nestabilita.
FullDebugMode nie je vhodný ako „zapneme to vo všetkých Builds“. Režijné náklady sú vysoké, načasovanie sa mení a práve Race-Conditions sa môžu týmto spôsobom stratiť alebo posunúť. Na trvalý prevádzku je vhodnejší štíhly monitoring (napr. Prozess-Working-Set, Private Bytes, čítače na úkon) – FullDebugMode je skalpel, nie Pulzfühler.
Základný princíp: Leak-Report je Symptom, Stacktrace je Spur
Ein Leak-Report ti najskôr ukáže: tieto bloky sú na konci programu stále alokované. To je problém len vtedy, ak tieto bloky mali byť skutočne uvoľnené. Existujú legitímne „Leaks“: globálne Singletons, Caches, OS-Handles s životnosťou procesu alebo third-party Bibliotheken, ktoré zámerne nefinalizujú. Tieto prípady treba poznať, nie ich slepo „wegfixen“.
Der Stacktrace im Report ukazuje miesto, kde bol blok alokovaný. To často nie je miesto, kde si „Free vergessen“ hast. Bežná realita vo vyspelých systémoch:
- Allokácia v UI- alebo Service-Layer, uvoľnenie by malo prebiehať v hlbšej vrstve (Ownership nejasné).
- Allokácia vo Factory, Ownership sa prenesie na Caller – ale Caller predpokladá, že je to „owned“.
- Objekty sú držané v Collections (Listen, Dictionaries), ale model Ownership nie je konzistentný.
- Cesta výnimky preskočí Cleanup, pretože try/finally chýba alebo začína príliš neskoro.
Čistý postup je preto: reproduzieren → isolieren → Stacktrace auflösen → Ownership-Fehler finden → Fix mit Regressionstest. FastMM liefert dir die Spuren, aber du musst sie in Architektur und Lebenszyklen übersetzen.
FastMM FullDebugMode sauber aktivieren (ohne Nebenwirkungen zu übersehen)
Der FullDebugMode wird in der Praxis über die FastMM-Optionen und eine passende FastMM-Konfiguration aktiviert. Entscheidend ist weniger „wie genau heißt die Include-Datei“, sondern was die Konfiguration bewirkt und unter welchen Build-Bedingungen du sie nutzt.
Empfohlene Rahmenbedingungen für den Debug-Build
- Debug DCUs und Debug-Infos: Stacktraces sind nur nützlich, wenn sie auf echte Unit/Zeile/Adresse auflösbar sind. Stelle sicher, dass Debug-Informationen erzeugt werden und eine MAP-Datei verfügbar ist.
- Optimierung bewusst wählen: Für Stacktrace-Lesbarkeit ist ein nicht-optimierter Build meist besser. Inlining und aggressive Optimierung können Stackframes „verwischen“.
- Gleiche Laufzeitbedingungen: Nutze möglichst gleiche Daten, gleiche Konfiguration, gleiche Rechte. Viele Leaks sind datenabhängig (z. B. seltene Formate, Sonderpfade).
- 64-bit vs. 32-bit trennen: Speicherverhalten, Alignment und Drittbibliotheken unterscheiden sich. Debugge in der Zielplattform, in der das Problem auftritt.
Ein Punkt, den Admins und technische Leads oft unterschätzen: FullDebugMode kann auch Timing verändern. Wenn du Threading im Spiel hast, können Race-Conditions dadurch anders auftreten. Deshalb ist es sinnvoll, parallel auch einen Lauf ohne FullDebugMode zu haben, der nur die Reproduktion bestätigt. FullDebugMode ist dann der Schritt zur Diagnose.
Vorsicht bei „ReportMemoryLeaksOnShutdown“
Delphi kann über ReportMemoryLeaksOnShutdown Leaks beim Programmende melden. Das ist praktisch, aber in komplexen Anwendungen (Services, Plug-in Host, lange Laufzeiten) kann es trügen: Beim Shutdown laufen Finalization-Abschnitte, Threads stoppen, Caches werden weggeräumt. Ein Leak, das in der Mitte der Laufzeit kritisch ist, kann bis zum Ende wieder verschwinden – oder umgekehrt: ein scheinbarer Leak entsteht erst im Shutdown, weil noch Hintergrundarbeit läuft.
Für praxistaugliches Leak-Hunting ist daher wichtiger: Leak pro Vorgang messen (z. B. nach 100 Requests), nicht nur beim Beenden. FastMM kann dabei helfen, aber der Testaufbau muss das abbilden.
Der typische Randfall: Leak-Report zeigt „irgendein Objekt“, aber die Ursache ist Ownership
Klasika v podnikových aplikáciách: importný proces vytvára pre každý záznam pomocné objekty (napr. StringLists, JSON-Parser, dočasné zoznamy). V štandardnom (happy path) priebehu sa správne uvoľnia. V zriedkavých prípadoch (preskočenie z dôvodu validácie, Exception, predčasný exit) zostane objekt „visieť“. Po 10 000 záznamoch je to viditeľné.
FastMM FullDebugMode tu pomáha, pretože ukáže miesto alokácie. Ale „Fix“ nie je „free na mieste alokácie“. Riešenie je robustné Ownership-Pattern:
- Kto objekt vytvorí, nie je automaticky jeho vlastníkom.
- Ownership musí byť v API-zmluve jasné (parametre/return, dokumentácia, konvencie pomenovania).
- Kolekcie musia byť jednoznačné: owning vs. non-owning. Zmiešané formy sa pomstia.
- Cesty s výnimkami potrebujú skoré try/finally bloky.
Ak zo stacktracu vidíš len „TStringList.Create“, informácia nie je bezcenná – ale hovorí ti len: tu sa niečo vytvára. Otázka znie: kde by sa to malo skončiť? A v tom pomáha architektonické myslenie viac než debuggerová akrobacia.
Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst
Výpis zásobníka z FastMM je zvyčajne zoznam návratových adries, ktoré sa – s debug-symbolmi – mapujú na jednotky, procedúry a ideálne aj čísla riadkov. Pri jeho čítaní sú rozhodujúce tri veci:
- Top-of-Stack nie je vždy chyba: Horné framy sú často Memory-Manager/RTL. Zaujímavé to je tam, kde začína tvoj kód.
- Call-Chain namiesto jedného riadku: Riadok je len bod. Reťaz ukáže, ktorá cesta viedla k alokácii.
- Viacero identických blokov: Ak FastMM hlási viacero leakov rovnakej veľkosti, ide často o opakujúcu sa cestu. To je výhoda: máš reprodukovateľnosť.
Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs
Mnoho tímov narazí tu: FullDebugMode je aktívny, leak-report príde, ale namiesto Unit/riadku sú len adresy alebo kryptické symboly. Typické príčiny:
- Chýba MAP-súbor alebo sa nevygenerovali debug-informácie.
- Narážaš na Release-DCUs alebo tretie DLL bez symbolov.
- Aplikácia používa Runtime Packages: časti kódu sú v BPLs a rozlúštenie symbolov tomu musí zodpovedať.
- Optimalizácia/Inlining urobili stacktrace menej čitateľným.
V praxi to znamená: na lov leakov potrebuješ build, ktorý je vedome „diagnostický“. To je iný cieľ než „čo najschodnejší/ najrýchlejší“. Technické vedenie by to malo riešiť ako samostatný build-profil, aby si každý člen tímu nemusel ad hoc meniť projektové nastavenia.
Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben
Príklad z praxe (bez konkrétneho kódu klienta): Stacktrace ti v prvom frame v tvojom kóde ukáže rutinu „LoadConfig“. Vidíš tam vytvorenie objektu. Pridáš volanie Free, leak je preč – a zrazu to inde zlyhá s Double Free. Prečo? Pretože „LoadConfig“ uloží objekt do cache a iná vetva kódu už je vlastníkom a neskôr ho upratuje.
Správne čítanie by bolo: Stacktrace ti ukazuje, kde blok vzniká. Fix je často v definícii: kto vlastní objekt po returne? Ak na túto otázku nedáš čistú odpoveď, len zmeníš obraz chyby (Leak → AV).
Heap-Korruption vs. Leak: Warum FullDebugMode oft den echten Übeltäter findet
Mnoho „leakov“ sú v skutočnosti následné problémy: buffer-overrun prepíše metaúdaje heapu, správca pamäte potom nedokáže bloky korektne uvoľniť, a nakoniec vidíš zdanlivo náhodné leaky alebo neplatné operácie s ukazovateľmi. FullDebugMode tu pomáha, pretože pracuje s kontrolnými vzormi a pri Free/Reuse robí dodatočné validácie.
Dôležité je rozlíšenie:
- Leak: Blok bol alokovaný a nikdy neuvoľnený. Stabilita sa časom zhoršuje, crash nie je nevyhnutný.
- Use-after-free: Blok bol uvoľnený, ale neskôr sa ešte používa. Vedie k sporadickým AVs, ktoré sú ťažko reprodukovateľné.
- Double Free: Blok je uvoľnený dvakrát. Môže okamžite zlyhať alebo až neskôr (keď bol blok znovu použitý).
- Heap-Korruption: Niekto zapisuje za hranice bloku. Symptómy sú často oneskorené.
FullDebugMode je obzvlášť cenný, keď symptómy vidíš oneskorene. Dodatočné validácie urobia chyby viditeľnými skôr – často priamo v mieste, kde prebieha nesprávny prístup, nie až minúty neskôr pri nejakom Free.
Vorgehen in Projekten: Reproduzierbare Leak-Jagd statt „Debugging im Nebel“
Ak chceš loviť pamäťové úniky, potrebuješ postup, ktorý je opakovateľný a dá sa zdieľať v tíme. Pracujem rád s pevne definovaným diagnostickým rámcom:
1) Reproduktion in einem deterministischen Szenario
Definuj testovaciu sekvenciu, ktorá leak spoľahlivo zobrazuje: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ alebo „Öffne Maske X, führe Aktion Y 200-mal aus“. Dôležité je, aby si sekvenciu zdokumentoval s parametrami (dátový stav, Mandant, Feature-Flags), aby si ju iní mohli reprodukovať.
2) Minimieren: Leak pro Schritt sichtbar machen
Ak sekvencia trvá 20 minút, rozdeľ ju. Cieľ je: chceš čo najrýchlejšie porovnať „pred“ a „po“. V rozsiahlych aplikáciách je to často hlavný časový žrút, nie samotné opravy.
3) FullDebugMode einschalten und Report interpretieren
Až teraz prichádza do hry FastMM FullDebugMode. Zbieraj reporty, zoskupuj podľa veľkosti bloku/callstacku a hľadaj opakovania. Jeden zostávajúci blok môže byť legitímny cache. 10.000 identických blokov sú takmer vždy skutočný únik pamäte.
4) Vyjasnenie ownershipu a oprava v príslušnej vrstve
Opravuj úniky tam, kde sa definuje ownership: Factory, API-kontrakt, Collection-Wrapper. „Rýchle pridanie Free“ priamo vedľa Create je často nesprávne miesto, ak sa objekt ďalej odovzdáva.
5) Regresia: rovnaká sekvencia, rovnaký build, rovnaký report
Oprava je platná len vtedy, keď sa sekvencia opäť spúšťa a nenastávajú ani úniky pamäte, ani nové chyby pamäte. Najmä pri Use-after-free nie je „Leak weg“ dôkazom, ale iba novým symptómom.
Typické nástrahy v Delphi-kóde, ktoré FastMM zviditeľní
Kolekcie a vlastníctvo (zoznamy, slovníky, rozhrania)
Mnohé úniky nevznikajú v zložitých algoritmoch, ale v bežných dátových štruktúrach. Dva klasické vzory chýb:
- Zoznam obsahuje objekty, ale nikto nevie, kto ich uvoľní. Riešenie: používať owning-Liste alebo konzistentne vyprázdniť v finally.
- Dictionary drží objekty ako hodnoty; pri Remove sa hodnota neuvoľní alebo sa to zabudne pri Clear.
Ďalej sú problematické rozhrania: počítanie referencií (podobné ARC) je pohodlné, ale kombinácia s objektovým ownershipom môže pri cyklických referenciách alebo udalostiach spôsobiť úniky. FullDebugMode ti často ukáže cestu alokácie, ale príčinou je referenčný cyklus (A drží B cez Interface, B drží A cez callback).
Výnimky a predčasné ukončenia
V zrelých business softvérových systémoch sú výnimky často súčasťou bežného riadenia (napr. validácia, prerušenie, retry). Problém zriedka spočíva vo výnimke ako takej, ale v okolitej ceste: objekt je vytvorený pred try/finally, potom nastať výnimka a cleanup sa preskočí. FullDebugMode ti poskytne stacktrace alokácie – a musíš skontrolovať, či existuje garantovaná cesta na uvoľnenie.
Vlákna a životnosť: „uvoľnenie v nesprávnom vlákne“
Pri VCL/FMX a službách s worker-threadmi vzniká ďalší okrajový prípad: objekt je vytvorený v jednom vlákne, ale uvoľnený v UI-vlákne (alebo naopak), pretože sa cez Queue/Synchronize „rýchlo“ niečo prenesie. To môže fungovať, ale tiež viesť k Use-after-free, ak producent pokračuje v práci, zatiaľ čo consumer už uvoľnil.
FastMM FullDebugMode tu môže pomôcť, pretože dokáže včas odhaliť oneskorené chyby. Skutočná oprava je však čistý model životnosti: jasné vlastnícke vzťahy, odovzdávanie iba cez immutable dáta alebo jednoznačné body transferu ownershipu.
Ako sprístupniť reporty: filtrovanie, porovnávanie, dokumentovanie
V tímoch sa oplatí nielen „pozerať“ leak-reporty, ale zaobchádzať s nimi ako s artefaktom. Tri pragmatické opatrenia, ktoré sa osvedčili:
- Baseline-Report: „známy stav“ (napr. aktuálna verzia produktu) sa raz spustí s FullDebugMode a uloží ako referencia. Potom nové úniky rozpoznáš okamžite.
- Vergleich nach Use-Case: Pre kritické workflowy (Import, Export, API-Request, UI-masová operácia) si pre každý stanovíš krátku sekvenciu, ktorú je možné pravidelne opakovať.
- Dokumentované „legitímne Leaks“: Ak je cache zámerne neskončený, zdokumentuj to. Inak o šesť mesiacov niekto opäť bude loviť tie isté záznamy.
To nie je byrokracia, ale úspora času: lov na úniky sa inak rýchlo stane nekonečnou slučkou, pretože tie isté vzory sa v každom sprinte opakujú.
Kedy sa námaha oplatí – a kedy by si mal postupovať inak
FastMM FullDebugMode je diagnostický nástroj, ktorý má svoje náklady. Námaha sa oplatí najmä, ak:
- Aplikácia beží dlhodobo (služba, klient terminálového servera, systém pracovných zmien, 24/7 procesy).
- Spracúvaš reálne zákaznícke dátové toky a v testoch nemáš pokryté všetky cesty.
- Stabilita je dôležitejšia než krátkodobá rýchlosť zavádzania funkcií (typické pre procesne orientované softvérové riešenia).
Ak máš však len malého desktopového pomocníka, ktorý sa po 30 sekundách ukončí, hľadanie únikov je často až druhoľadé. Rovnako: ak máš jednorazový problém s nárazovým nárastom spotreby pamäte (napr. veľký export), často nejde o únik, ale o otázku streamovacej stratégie a špičkového zaťaženia heapu.
Praktické zhrnutie: FullDebugMode nie je prepínač, ale proces
FastMM FullDebugMode vnáša štruktúru do hľadania chýb pamäte: sprístupňuje alokácie, odhaľuje poškodenie heapu skôr a poskytuje stacktracy, pomocou ktorých môžeš opraviť príčinu namiesto symptómu. Rozhodujúci pákový bod nie je však nástroj, ale postup: reprodukovateľné scenáre, buildy vhodné na diagnostiku, jasné pravidlá vlastníctva a regresné testovanie voči baseline.
Ak sa zasekneš na tvrdohlavom úniku alebo na sporadickej chybe heapu a chceš túto tému v rámci väčšieho Delphi-systému udržateľne stabilizovať, oplatí sa krátke, čisté diagnostické nastavenie so zrozumiteľnou sekvenciou a vyhodnotiteľnými reportami. Ak potrebuješ pri tom podporu pri analýze, profile buildov alebo refaktoringu architektúry: Kontakt na Net-Base Software GmbH.
Pre túto tému sú tiež dôležité Delphi Nájdenie úniku pamäte a Čítanie Fastmm Leak Reportu. Článok tieto aspekty zrozumiteľne zaradí a ukáže, na čo sa v praxi zamerať.
ďalší krok
Keď sa z témy stane reálny projekt, architektúru, existujúci stav a prevádzku treba včas posudzovať spoločne.
Podporujeme nielen pri jednotlivých otázkach, ale aj vtedy, keď sa z fragmentov zdrojového kódu, tém súvisiacich s legacy systémami alebo nápadov na portál má stať robustný podnikový projekt.
- Stav, cieľový obraz a technické riziká sa hodnotia spoločne.
- REST, prístup k údajom, portály a nasadenie nebudú odložené na neskôr ako následné úlohy.
- Včas identifikujete, ktorá cesta je ekonomicky a prevádzkovo životaschopná.