Od tématu magazínu k projektové praxi
Vhodné stránky služeb a technické stránky k příspěvku
Pokud se Delphi-aplikace v provozu postupně „nafukuje“, občas padá s Access Violations nebo po dnech běhu náhle ztrácí stabilitu, za tím často nestojí jediná chyba, ale vzorec: paměť je alokována, ale není správně uvolněna – nebo je uvolněna příliš brzy a později se ještě používá. Právě tady je FastMM FullDebugMode k nezaplacení. Ne jako trvalý režim, ale jako cílený diagnostický nástroj, který z „někde v heapu je něco rozbitého“ udělá opět vysledovatelnou příčinu.
Háček: FullDebugMode produkuje hodně výstupu, stojí výkon a rychle vede k mylným interpretacím. Report úniků neukáže automaticky místo, kde „je ta chyba“. A stacktrace je tak dobrý, jak dobré je rozlišení symbolů (MAP-Datei, debug-informace, inlining). V tomto příspěvku projdu typický okrajový případ, vysvětlím čistý přístup a úskalí – tak, abys na konci nejen našel úniky paměti, ale je trvale odstranil.
Kdy má FastMM FullDebugMode skutečně smysl
FastMM je v moderních Delphi-verzích často již výchozím správcem paměti nebo je v mnoha projektech stejně zapojen. FullDebugMode je však speciální konfigurace: opatří paměťové bloky dodatečnými kontrolními vzory, sbírá stacktracy alokací a agresivněji kontroluje korupci heapu (tedy poškozená řídicí data v heapu, např. způsobená buffer-overruny).
Používám FullDebugMode cíleně, když nastane jedna z následujících situací:
- Reprodukovatelný únik paměti: spotřeba paměti během testu roste na operaci (např. na request, při importu, při UI-akci).
- Sporadické AV: zejména ty, které se objevují „někdy tady, někdy tam“ ve stejném rozsahu (klasický případ: use-after-free).
- Korupce heapu: hlášení jako „Invalid pointer operation“, „Access violation in ntdll“ nebo pády při ukončování/finalizaci.
- Hledání regresí: po refaktoringu, aktualizaci knihovny nebo změně kompilátoru náhlá nová nestabilita.
Není vhodné zapínat FullDebugMode „ve všech sestaveních“. Režie je vysoká, časování se změní a právě race-condition se tím mohou skrýt nebo posunout. Pro trvalý provoz se více hodí štíhlé monitorování (např. Working Set procesu, Private Bytes, počítadla na operaci) – FullDebugMode je skalpel, ne pulzní snímač.
Základní princip: report úniků je symptom, stacktrace je stopa
Report úniků ti nejdřív ukáže: tyto bloky jsou na konci programu stále alokované. To je automaticky problém pouze tehdy, pokud měly být tyto bloky skutečně uvolněny. Existují legitimní „úniky“: globální singletony, cache, OS-handle s životností procesu nebo knihovny třetích stran, které záměrně neprovádějí finalizaci. Tyto případy chceš znát, ale nesmíš je slepě „odstraňovat“.
Stacktrace v reportu ukazuje místo, kde byl blok alokován. To často není místo, kde jsi „zapomněl Free“. Běžná realita ve vyrostlých systémech:
- Alokace v UI- nebo servisní vrstvě, uvolnění by mělo proběhnout v hlubší vrstvě (vlastnictví nejasné).
- Alokace ve factory, vlastnictví je předáno volajícímu – ale volající si myslí, že je objekt v jeho vlastnictví.
- Objekty jsou drženy v kolekcích (seznamy, slovníky), ale model vlastnictví není konzistentní.
- Cesta výjimky přeskočí úklid, protože chybí try/finally nebo začíná příliš pozdě.
Čistý postup je tedy: reprodukovat → izolovat → vyřešit Stacktrace → naleznout chybu vlastnictví → oprava s regresním testem. FastMM ti poskytne stopy, ale musíš je přeložit do architektury a životních cyklů.
FastMM FullDebugMode správně aktivovat (bez přehlédnutí vedlejších účinků)
FullDebugMode se v praxi aktivuje přes možnosti FastMM a odpovídající konfiguraci FastMM. Rozhodující není tolik „jak přesně se jmenuje Include-Datei“, ale co konfigurace způsobí a za kterých build‑podmínek ji používáš.
Doporučené rámcové podmínky pro Debug-Build
- Debug DCUs und Debug-Infos: Stacktracey jsou užitečné jen tehdy, když je lze rozklíčovat na skutečnou unit/řádek/adresu. Ujisti se, že se generují debug‑informace a že je dostupný MAP‑soubor.
- Volba optimalizace s rozmyslem: Pro čitelnost stacktracu je obvykle lepší neoptimalizované sestavení. Inlining a agresivní optimalizace mohou stackframy „rozmazat“.
- Stejné běhové podmínky: Používej co nejstejné vstupy, stejnou konfiguraci a stejná oprávnění. Mnoho úniků paměti je závislých na datech (např. vzácné formáty, speciální cesty).
- Oddělit 64-bit a 32-bit: Chování paměti, zarovnání a knihovny třetích stran se liší. Debuguj na cílové platformě, na které problém nastává.
Jedna věc, kterou admini a techničtí vedoucí často podceňují: FullDebugMode může také měnit timing. Pokud pracuješ s vlákny, mohou se race‑conditions projevovat odlišně. Proto je vhodné mít paralelně i běh bez FullDebugMode, který pouze potvrdí reprodukci. FullDebugMode je pak krok k diagnostice.
Pozor na „ReportMemoryLeaksOnShutdown“
Delphi může přes ReportMemoryLeaksOnShutdown hlásit úniky při ukončení programu. To je praktické, ale v složitých aplikacích (služby, hostitel plug‑inů, dlouhé běhy) to může klamat: při shutdownu probíhají finalizační sekce, vlákna se ukončují, cache se čistí. Únik, který je ve středu běhu kritický, může do konce zmizet – nebo naopak: zdánlivý únik vznikne až při ukončení, protože stále běží pozadí.
Pro praktické pátrání po únikách paměti je proto důležitější: měřit únik na operaci (např. po 100 požadavcích), ne jen při ukončení. FastMM v tom může pomoci, ale testovací scénář to musí věrně zobrazit.
Typický okrajový případ: Leak‑Report ukáže „nějaký objekt“, ale příčina je vlastnictví
Klasika v podnikovém softwaru: importní proces vytvoří pro každý záznam pomocné objekty (např. StringLists, JSON-Parser, dočasné seznamy). V Happy Path jsou korektně uvolněny. Ve vzácných případech (skip z důvodu validace, výjimka, předčasný exit) nějaký objekt zůstane viset. Po 10 000 záznamech je to vidět.
FastMM FullDebugMode zde pomáhá, protože ukáže místo alokace. Ale „oprava“ není „free na místě alokace“. Oprava je robustní vlastnický vzor:
- Kdo objekt vytvoří, není automaticky jeho vlastník.
- Vlastnictví musí být v API kontraktu jasné (parametry/return, dokumentace, pojmenovací konvence).
- Kolekce musí být jednoznačné: owning vs. non-owning. Smíšené formy se mstí.
- Cesty s výjimkami potřebují brzké try/finally bloky.
Když ze stacktracu vidíš jen „TStringList.Create“, informace není bezcenná – ale říká ti jen: tady se něco vytváří. Otázka je: kde by to mělo skončit? A v tom pomáhá víc architektonické myšlení než debuggerová akrobacie.
Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst
Stacktrace z FastMM je obvykle seznam návratových adres, které – s debug symboly – mapují na Units, procedury a ideálně čísla řádků. Když to čteš, jsou tři rozhodující věci:
- Top-of-Stack ist nicht immer der Fehler: Horní framy jsou často Memory-Manager/RTL. Zajímavé je to tam, kde začíná tvůj kód.
- Call-Chain statt Einzelzeile: Řádek je jen bod. Řetězec ukáže, která cesta vedla k alokaci.
- Mehrere identische Blöcke: Když FastMM nahlásí několik úniků stejné velikosti, jde často o opakující se cestu. To je dobré: máš reprodukovatelnost.
Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs
Mnoho týmů na tom zakopává: FullDebugMode je aktivní, leak-report přijde, ale místo Unit/řádku jsou tam jen adresy nebo kryptická symbolika. Typické příčiny:
- Neexistuje MAP-Datei nebo debug-informace nebyly vygenerovány.
- Padáš na Release-DCUs nebo třetí DLL bez symbolů.
- Aplikace používá Runtime Packages: části kódu leží v BPL a rezoluce symbolů tomu musí odpovídat.
- Optimalizace/Inlining znečitelnily stacktrace.
V praxi to znamená: pro lov úniků potřebuješ build, který je záměrně „diagnosefähig“. To má jiné cíle než „co nejrychlejší“. Technické vedení by to mělo považovat za samostatné build‑profil, aby si každý člen týmu nepořizoval ad hoc změny v projektových nastaveních.
Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben
Příklad z reálného světa (bez konkrétního zákaznického kódu): Der Stacktrace ukáže jako první rámec ve tvém kódu rutinu „LoadConfig“. Vidíš tam vytvoření objektu. Přidáš Free, leak zmizí – a najednou to jinde bouchne Double Free. Proč? Protože „LoadConfig“ uloží objekt do Cache, a jiná větev kódu je již Owner a později uklidí.
Správné čtení by bylo: Der Stacktrace ti ukazuje, kde blok vzniká. Oprava často leží v definici: kdo vlastní objekt po návratu? Pokud na tuto otázku neodpovíš jasně, pouze změníš podobu chyby (Leak → AV).
Heap-Korruption vs. Leak: Warum FullDebugMode oft den echten Übeltäter findet
Mnoho „Leaků“ je ve skutečnosti následný problém: Buffer-Overrun přepíše metadata haldy, správce paměti později nemůže provést čisté uvolnění, a nakonec vidíš zdánlivě náhodné Leaky nebo neplatné operace s ukazateli. FullDebugMode je v takových případech silný, protože pracuje s kontrolními vzory a při Free/Reuse provádí dodatečné validace.
Důležité je rozlišit:
- Leak: Blok byl alokován a nikdy neuvolněn. Stabilita se s časem snižuje, pád není nutně následkem.
- Use-after-free: Blok je uvolněn, ale později je stále používán. Vede k sporadickým AV, které jsou těžko reprodukovatelné.
- Double Free: Blok je uvolněn dvakrát. Může to okamžitě selhat nebo až později (když byl blok znovu použit).
- Heap-Korruption: Někdo zapisuje mimo hranice bloku. Symptomy se často projeví se zpožděním.
FullDebugMode je obzvlášť cenný, pokud symptomy vidíš se zpožděním. Dodatečná validace odhalí chyby dříve – často přímo v místě, kde dochází k chybnému přístupu, ne až o minuty později v libovolném Free.
Přístup v projektech: Reprodukovatelný lov leaků místo „Debugging im Nebel“
Pokud chceš lovit úniky paměti, potřebuješ postup, který je opakovatelný a sdílitelný v týmech. Já rád pracuji s pevně daným diagnostickým rámcem:
1) Reprodukce v deterministickém scénáři
Definuj testovací sekvenci, která leak spolehlivě ukáže: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ nebo „Öffne Maske X, führe Aktion Y 200-mal aus“. Důležité je, že sekvenci zdokumentuješ s parametry (Datensatz, Mandant, Feature-Flags), aby ji ostatní mohli reprodukovat.
2) Minimalizace: Leak krok za krokem zpřehlednit
Pokud sekvence trvá 20 minut, rozděl ji. Cílem je: chceš co nejrychleji porovnat „před“ a „po“. V rozsáhlých aplikacích je to často hlavní žrout času, ne samotné opravy.
3) Zapnout FullDebugMode a interpretovat report
Až teprve teď přichází na řadu FastMM FullDebugMode. Sbírej reporty, seskupuj podle velikosti bloku/callstacku a hledej opakování. Jeden zbývající blok může být legitimní cache. 10.000 identických bloků jsou téměř vždy skutečný únik paměti.
4) Vyjasnění vlastnictví a oprava ve vhodné vrstvě
Opravuj úniky tam, kde se definuje vlastnictví: Factory, API-Vertrag, Collection-Wrapper. „Schnell ein Free einbauen“ přímo vedle Create je často špatné místo, pokud je objekt předáván dál.
5) Regrese: stejná sekvence, stejný Build, stejný Report
Oprava je dobrá teprve tehdy, když se sekvence znovu spouští a nevyskytují se ani úniky ani nové chyby paměti. Zejména u Use-after-free není „zmizení úniku“ důkaz, ale pouze nový symptom.
Typické nástrahy v Delphi-kódu, které FastMM odhalí
Kolekce a vlastnictví (seznamy, slovníky, rozhraní)
Mnoho úniků nevzniká v komplikovaných algoritmech, ale v běžných datových strukturách. Dva klasické scénáře chyb:
- Seznam obsahuje objekty, ale nikdo neví, kdo je uvolní. Řešení: použít owning-seznam nebo důsledně v finally uvolňovat.
- Dictionary drží objekty jako values; při Remove se hodnota neuvolní nebo se na Clear zapomene.
Navíc jsou problémová rozhraní: počítání referencí (ARC-ähnlich) je pohodlné, ale kombinace s objektovým vlastnictvím může při cyklických referencích nebo událostech způsobit úniky. FullDebugMode ti pak často ukáže cestu alokace, ale příčinou je referenční cyklus (A drží B přes Interface, B drží A přes Callback).
Výjimky a předčasné ukončení
V rozrostlých podnikových softwarových systémech jsou Exceptions často součástí běžného řízení (např. validace, zrušení, Retry). Problém zřídka spočívá ve výjimce samotné, ale v cestě kolem ní: objekt je vytvořen před try/finally, pak nastane Exception a cleanup je přeskočen. FullDebugMode ti poskytne Stacktrace alokace – a musíš ověřit, zda existuje garantovaná cesta uvolnění.
Vlákna a životnost: „Freigeben im falschen Thread“
U VCL/FMX a služeb s worker-threads vzniká další hraniční případ: objekt je vytvořen v jednom vlákně, ale v UI-Thread uvolněn (nebo naopak), protože se přes Queue/Synchronize „jen rychle“ něco přeposílá. To může fungovat, ale také vést k Use-after-free, pokud producent pokračuje v práci, zatímco consumer už uvolňuje.
FastMM FullDebugMode může zde pomoci, protože detekuje časově posunuté chyby dříve. Skutečná oprava je však čistý model životnosti: jasné vlastnické vztahy, předávání pouze přes immutable data nebo jednoznačné Ownership-Transfer-Punkte.
Jak využít reporty: filtrování, porovnávání, dokumentace
V týmech se vyplatí s Leak-Reporty ne jen „koukat“, ale zacházet s nimi jako s artefaktem. Tři pragmatická opatření, která se osvědčila:
- Baseline-Report: „známý stav“ (např. aktuální produktová verze) se jednou spustí s FullDebugMode a uloží jako reference. Pak nové úniky poznáš okamžitě.
- Porovnání podle Use-Case: Pro kritické pracovní toky (Import, Export, API-Request, hromadná operace v UI) nastavíš vždy krátkou sekvenci, kterou lze pravidelně opakovat.
- Zdokumentované „legitimní úniky“: Pokud je cache záměrně nezrušená, zdokumentuj to. Jinak o šest měsíců později někdo znovu začne řešit ty samé položky.
To není byrokracie, ale úspora času: honba za úniky se jinak rychle stane nekonečnou smyčkou, protože stejné vzorce se v každém sprintu opakují.
Kdy se námaha vyplatí – a kdy je lepší postupovat jinak
FastMM FullDebugMode je diagnostický nástroj, který má své náklady. Úsilí se zvlášť vyplatí, pokud:
- Aplikace běží dlouho (služba, klient terminálového serveru, směnový systém, procesy 24/7).
- Zpracovávají se reálné proudy zákaznických dat a v testech nejsou pokryty všechny cesty.
- Stabilita je důležitější než krátkodobá rychlost nasazování funkcí (typické u procesně orientovaných softwarových řešení).
Pokud máš však jen malého desktopového pomocníka, který po 30 sekundách skončí, je honba za úniky často druhořadá. Stejně tak: pokud jde o jednorázový výkyv využití paměti (např. velký export), často se nejedná o únik, ale o otázku strategie streamování a špičkového zatížení haldy.
Praktické shrnutí: FullDebugMode není přepínač, ale proces
FastMM FullDebugMode vnáší do vyhledávání chyb v paměti strukturu: zpřístupňuje alokace, odhaluje korupci haldy dříve a poskytuje stacktracy, díky nimž můžeš opravit příčinu místo symptomu. Klíčovou pákou ale není nástroj, nýbrž postup: reprodukovatelné scénáře, buildy vhodné pro diagnostiku, jasné dohody o vlastnictví a regresní testy vůči baseline.
Pokud uvízneš na houževnatém úniku nebo sporadické chybě haldy a chceš problém trvale stabilizovat v rozsáhlejším Delphi-systému, vyplatí se krátké, čisté nastavení diagnostiky s jasnou sekvencí a vyhodnotitelnými reporty. Potřebuješ-li k tomu podporu při analýze, profilech buildu nebo architektonickém refaktoringu: kontakt na Net-Base Software GmbH.
Pro toto téma jsou také důležité Delphi nalezení úniku paměti a čtení Fastmm Leak Report. Článek tyto aspekty srozumitelně zařazuje a ukazuje, na co záleží v běžné praxi.
další krok
Když se z tématu stane reálný projekt, měly by být architektura, stávající systém a provoz posuzovány společně již v rané fázi.
Podporujeme nejen při jednotlivých otázkách, ale i v případě, že se z útržků zdrojového kódu, legacy témat nebo nápadů na portál má vyvinout robustní podnikový projekt.
- Současný stav, cílový stav a technická rizika jsou hodnoceny společně.
- REST, přístup k datům, portály a rollout nebudou přesunuty do pozdějších fází.
- Včas zjistíte, která varianta je ekonomicky i provozně životaschopná.