A magazintémától a projektgyakorlatig
A bejegyzéshez tartozó szolgáltatási és technikai oldalak
Ha egy Delphi-alkalmazás üzem közben lassan „felfúvódik”, időnként Access Violationt dob vagy napok futása után hirtelen instabillá válik, gyakran nem egyetlen bug áll a háttérben, hanem egy mintázat: memória allokálódik, de nem kerül tisztán felszabadításra – vagy túl korán szabadítják fel és később még használják. Pont itt ér igazán aranyat a FastMM FullDebugMode. Nem állandó állapotként, hanem célzott diagnóziseszközként, amelyből a „valahol a heapben valami elromlott” újra követhető ok lesz.
A bökkenő: FullDebugMode sok kimenetet generál, teljesítményt vesz el és könnyen félrevezető következtetésekhez vezet. Egy leak-jelentés nem mutatja automatikusan azt a helyet, ahol „a hiba” van. És egy Stacktrace csak olyan jó, mint a szimbólumfeloldás (MAP-fájl, debug-információk, inlining). Ebben a bejegyzésben végigveszem a tipikus szélső esetet, elmagyarázom a tiszta megközelítést és a buktatókat – úgy, hogy a végén ne csak megtaláld a memóriaszivárgásokat, hanem tartósan el is hárítsd őket.
Mikor valóban indokolt a FastMM FullDebugMode használata
FastMM gyakran már alapértelmezett memória-kezelő a modern Delphi-verziókban, vagy sok projekt amúgy is beépíti. A FullDebugMode azonban különleges konfiguráció: memóriablokkokat lát el kiegészítő ellenőrző mintákkal, gyűjti az allokációs Stacktrace-eket és agresszívebben ellenőrzi a heap-korrupciót (azaz a heap adminisztratív adatok sérülését, pl. puffer túlcsordulások miatt).
Célzottan használom a FullDebugMode-ot, ha az alábbi helyzetek egyike áll fenn:
- Reprodukálható memóriaszivárgás: a memóriahasználat növekszik a tesztfutás során műveletenként (pl. kérésenként, importonként, UI-műveletenként).
- Sporadikus AV-k: különösen azok, amelyek „néha itt, néha ott” ugyanabban a tartományban történnek (klasszikus: use-after-free).
- Heap-korrupció: üzenetek, mint „Invalid pointer operation”, „Access violation in ntdll” vagy összeomlások a leállítás/finalizálás során.
- Regressziós keresés: refaktorálás, könyvtárfrissítés vagy fordítóváltás után hirtelen fellépő új instabilitás.
Nem érdemes a FullDebugMode-ot úgy használni, hogy „kapcsoljuk be minden buildben”. A többletterhelés nagy, megváltozik a timing, és különösen a versenyfeltételek így eltűnhetnek vagy eltolódhatnak. Hosszú távú üzemhez inkább egy karcsú monitorozás való (pl. folyamat Working Set, Private Bytes, számlálók műveletenként) – a FullDebugMode a szike, nem a pulzusmérő.
Alapelv: a memóriaszivárgás-jelentés tünet, a Stacktrace a nyom
Egy leak-jelentés először azt mutatja: Ez a blokkok a program végeztével még allokálva vannak. Ez csak akkor automatikusan probléma, ha ezeket a blokkokat valójában fel kellett volna szabadítani. Vannak jogos „leakek”: globális singletonok, cache-ek, OS-handle-ok a folyamat-élettartammal vagy third-party könyvtárak, amelyek szándékosan nem finalizálnak. Ezeket is ismerned kell, de nem szabad vakon „eltüntetni” őket.
A reportban szereplő Stacktrace azt a helyet mutatja, ahol a blokkot igényelték. Ez gyakran nem az a hely, ahol a Free elmaradt. Gyakori valóság érett rendszerekben:
- Allokáció az UI- vagy service-rétegben, a felszabadításnak mélyebb rétegben kellene történnie (tulajdonjog nem egyértelmű).
- Allokáció egy factory-ban, az ownership átadódik a hívónak – de a hívó úgy gondolja, hogy az az ő tulajdona.
- Objektumok gyűjteményekben tárolódnak (listák, dictionary-k), de az ownership-modell nem konzisztens.
- Egy kivételút kihagyja az erőforrás-felszabadítást, mert hiányzik a try/finally vagy túl későn kezdődik.
A tiszta munkafolyamat tehát: reprodukálás → izolálás → stacktrace feloldása → ownership-hiba megtalálása → javítás regressziós teszttel. FastMM adja meg a nyomokat, de neked kell ezeket az architektúrába és az életciklusokba átfordítani.
FastMM FullDebugMode tiszta aktiválása (anélkül, hogy mellékhatásokat figyelmen kívül hagynál)
A FullDebugMode a gyakorlatban a FastMM-opciókon és egy megfelelő FastMM-konfiguráción keresztül aktiválódik. Kevésbé fontos a kérdés, hogy „pontosan hogyan hívják az include-fájlt“, sokkal inkább az, mit okoz a konfiguráció és milyen buildfeltételek mellett használod.
Ajánlott keretek a debug buildhez
- Debug DCU-k és debug-információk: A stacktrace csak akkor hasznos, ha valódi unit/sor/cím alapján feloldható. Gondoskodj róla, hogy generálódjanak debug-információk és hogy elérhető legyen MAP-fájl.
- Optimalizálást tudatosan válaszd: A stacktrace olvashatóságához általában jobb egy nem-optimalizált build. Az inlining és az agresszív optimalizáció „elmoshatják” a stackframe-eket.
- Azonos futási feltételek: Használj lehetőleg azonos adatokat, azonos konfigurációt és azonos jogosultságokat. Sok leak adatfüggő (pl. ritka formátumok, speciális áramlások).
- 64-bit és 32-bit külön kezelése: A memória-viselkedés, az illesztés és a harmadik féltől származó könyvtárak különböznek. Debugolj azon a célplatformon, ahol a probléma jelentkezik.
Egy pont, amit a rendszergazdák és a műszaki vezetők gyakran alábecsülnek: a FullDebugMode megváltoztathatja a timingot. Ha vannak szálak, a race conditionök eltérően jelentkezhetnek. Ezért érdemes párhuzamosan futtatni egy futást FullDebugMode nélkül is, amely csak a reprodukciót igazolja. A FullDebugMode legyen a diagnózis lépése.
Óvatosság a „ReportMemoryLeaksOnShutdown” használatakor
Delphi a ReportMemoryLeaksOnShutdown segítségével jelezheti a leak-eket a program leállásakor. Ez kényelmes, de összetett alkalmazásoknál (szolgáltatások, plug-in hostok, hosszú futásidő) megtévesztő lehet: a shutdown alatt lefutnak finalizációs szakaszok, leállnak a threadek, a cache-ek kiürülnek. Egy, a futás közepén kritikus leak a végére eltűnhet – vagy fordítva: egy látszólagos leak csak a shutdown során keletkezik, mert még háttérmunka folyik.
Gyakorlati leak-vadászathoz ezért fontosabb: műveletenként mérni a leak-et (pl. 100 kérés után), ne csak a leálláskor. FastMM ebben tud segíteni, de a tesztkörnyezetnek ezt le kell képeznie.
A tipikus szegélyes eset: a leak-jelentés „valamilyen objektumot“ mutat, de az ok az ownership
Egy klasszikus eset vállalati alkalmazásokban: egy importfolyamat rekordonként létrehoz segédobjektumokat (pl. StringLists, JSON-Parser, ideiglenes listák). A Happy Path mentén ezek tisztán felszabadulnak. Ritka esetekben (validáció miatti skip, Exception, korai Exit) egy objektum fennakad. 10 000 rekord után ez láthatóvá válik.
FastMM FullDebugMode itt segít, mert megmutatja az allokáció helyét. De a „fix“ nem az, hogy a helyén free-olj. A megoldás egy robosztus Ownership-Pattern:
- Aki létrehoz egy objektumot, az nem lesz automatikusan Owner.
- Ownership-nek az API-szerződésben világosnak kell lennie (parameter/return, dokumentáció, elnevezési konvenciók).
- A kollekcióknak egyértelműnek kell lenniük: owning vs. non-owning. Vegyes megoldások megbosszulják magukat.
- A kivételútvonalakhoz korai try/finally-blokkok kellenek.
Ha a stacktrace-ből csak a „TStringList.Create” látszik, az információ nem értéktelen – de csak azt mondja meg: itt jön létre valami. A kérdés az: hol kellene véget érnie? Itt az architekturális gondolkodás többet ér a debugger-akrobatikánál.
Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst
Egy FastMM által előállított stacktrace általában visszatérési címek listája, amelyek – debug-szimbólumokkal – Unitokra, procedúrákra és ideális esetben sorokhoz rendelhetők. Ennek olvasásakor három dolog döntő:
- Top-of-Stack ist nicht immer der Fehler: A felsőbb frame-ek gyakran a Memory-Manager/RTL részei. Érdekes az a pont, ahol a saját kódod kezdődik.
- Call-Chain statt Einzelzeile: A sor csak egy pont. A lánc megmutatja, melyik útvonal vezetett az allokációhoz.
- Mehrere identische Blöcke: Ha FastMM több, azonos méretű szivárgást jelez, az gyakran ismétlődő útvonalra utal. Ez jó: reprodukálhatóságot ad.
Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs
Sok csapat elakad itt: FullDebugMode aktív, jön a Leak-Report, de Unit/Zeile helyett csak címeket vagy kriptikus szimbólumokat kapsz. Tipikus okok:
- Nincs MAP-fájl, vagy a debug-információk nem lettek generálva.
- Release-DCU-k vagy harmadik féltől származó DLL-ek futnak szimbólumok nélkül.
- Az alkalmazás Runtime Packages-t használ: ilyenkor a kód részei BPL-ekben vannak, és a szimbólumfeloldásnak ehhez kell igazodnia.
- Optimalizáció/Inlining áttekinthetetlenebbé tette a stacktrace-et.
Gyakorlatban ez azt jelenti: a szivárgásvadászathoz olyan buildre van szükséged, amely tudatosan „diagnosztizálható”. Ez más cél, mint a „lehető leggyorsabb”. A technikai leadeknek külön Build-Profilként kell kezelniük ezt, hogy ne minden csapattag állítson projektopciókat ad hoc módon.
Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben
Egy valós példa (konkrét ügyfélkód nélkül): a stacktrace az első frame-ként a kódodban a „LoadConfig” rutinra mutat. Ott látszik egy objektum létrehozása. Hozzáadsz egy Free hívást, a Leak eltűnik – és hirtelen máshol Double Free miatt felrobban a program. Miért? Mert a „LoadConfig” az objektumot egy Cache-be helyezi, és egy másik kódútvonal már Owner, és később felszabadítja.
A helyes olvasat az lett volna: a stacktrace megmutatja, hol keletkezik a Block. A javítás gyakran a definícióban rejlik: ki a Block (objektum) tulajdonosa a Return után? Ha ezt a kérdést nem válaszolod tisztán, csak a hibakép változik (Leak → AV).
Heap-Korruption vs. Leak: Miért találja a FullDebugMode gyakran a valódi hibaforrást
Sok „Leak” valójában következményprobléma: egy Buffer-Overrun felülírhatja a Heap-Metadaten-t, a Memory-Manager később nem tud tisztán felszabadítani, és végül látszólag véletlenszerű Leakeket vagy Invalid Pointer Operations-t látsz. A FullDebugMode ebben erős, mert ellenőrző mintákkal dolgozik, és Free/Reuse esetén további validációkat végez.
Fontos a megkülönböztetés:
- Leak: A Block allokálva lett és soha nem lett felszabadítva. Az alkalmazás stabilitása idővel romlik, összeomlás nem feltétlenül következik be.
- Use-after-free: A Block felszabadításra kerül, de később még használatba veszik. Sporadikus AV-kat okoz, amelyek nehezen reprodukálhatók.
- Double Free: A Block kétszer kerül felszabadításra. Azonnal összeomolhat vagy csak később (ha a Block újra felhasználásra került).
- Heap-Korruption: Valaki a Block határain túllépve ír. A tünetek gyakran késleltetetten jelentkeznek.
A FullDebugMode különösen értékes, ha a tünetek késleltetetten jelentkeznek. A további validáció korábban teszi láthatóvá a hibákat – gyakran pontosan ott, ahol a helytelen hozzáférés történik, nem csak percekkel vagy percekkel később egy tetszőleges Free-ben.
Eljárás projektekben: reprodukálható Leak-járás a „ködben végzett hibakeresés” helyett
Ha memóriaszivárgásokat akarsz vadászni, szükséged van egy olyan folyamathoz, amely ismételhető és megosztható a csapatban. Én szeretek egy rögzített diagnosztikai keretrendszerrel dolgozni:
1) Reprodukció determinisztikus szcenárióban
Határozz meg egy tesztszekvenciát, amely megbízhatóan előidézi a Leak-et: „Starte Service, verarbeite 500 Nachrichten, stoppe Service” vagy „Öffne Maske X, führe Aktion Y 200-mal aus”. Fontos, hogy a szekvenciát paraméterekkel dokumentáld (adatállomány, Mandant, Feature-Flags), hogy mások reprodukálni tudják.
2) Minimieren: Leak pro Schritt sichtbar machen
Ha a szekvencia 20 percet vesz igénybe, bontsd kisebb részekre. A cél: minél gyorsabban össze tudj hasonlítani „vorher” és „nachher” állapotot. Nagy alkalmazásoknál ez gyakran maga a tényleges időrabló, nem a javítás.
3) FullDebugMode einschalten und Report interpretieren
Most lép be a képbe a FastMM FullDebugMode. Gyűjtsd össze a jelentéseket, csoportosítsd blokkméret/Callstack szerint és keresd az ismétlődéseket. Egyetlen megmaradt blokk lehet jogos cache. 10.000 azonos blokk majdnem mindig valódi memória-szivárgás.
4) Ownership-Klärung und Fix in der passenden Schicht
Javítsd a szivárgásokat ott, ahol az Ownership definiálva van: Factory, API-szerződés, Collection-Wrapper. „Schnell ein Free einbauen“ közvetlenül a Create mellé gyakran rossz hely, ha az objektum tovább van adva.
5) Regression: gleiche Sequenz, gleicher Build, gleicher Report
A javítás csak akkor jó, ha a szekvencia ismét lefut, és se szivárgás, se újabb memóriaprobléma nem jelentkezik. Különösen Use-after-free esetén a „Leak weg” nem bizonyíték, csupán egy új tünet.
Typische Fallstricke in Delphi-Code, die FastMM sichtbar macht
Collections und Ownership (Listen, Dictionaries, Interfaces)
Sok szivárgás nem bonyolult algoritmusokból származik, hanem hétköznapi adatszerkezetekből. Két klasszikus hibakép:
- Egy lista objektumokat tartalmaz, de senki sem tudja, ki szabadítja fel azokat. Megoldás: owning Liste verwenden oder következetesen a finally blokkban üríteni.
- Egy Dictionary értékként tartja az objektumokat; beim Remove wird der Value nicht freigegeben oder beim Clear vergessen.
További trükkös pont az Interfaces: a referenciaszámlálás (ARC-ähnlich) kényelmes, de a Mischbetrieb mit Objekt-Ownership ciklikus hivatkozásoknál vagy eseményeknél szivárgást okozhat. FullDebugMode gyakran megmutatja az Allokationspfad-ot, de az ok egy referenciaciklus (A hält B über Interface, B hält A über Callback).
Exceptions und frühe Exits
Növekedett üzleti szoftverrendszerekben a kivételek gyakran a normál vezérlés részei (z. B. Validierung, Abbruch, Retry). A probléma ritkán maga a kivétel, sokkal inkább a környező útvonal: egy objektum a try/finally előtt jön létre, aztán kivétel dobódik, és a cleanup kimarad. FullDebugMode megadja az Stacktrace der Allokation – és ellenőrizned kell, hogy létezik-e garantáltan végrehajtott Freigabepfad.
Threads und Lebenszeit: „Freigeben im falschen Thread“
VCL/FMX és Services mit Worker-Threads esetén egy további szegélyes eset fordul elő: egy objektum egy szálon jön létre, de a UI-Threadben szabadítják fel (oder umgekehrt), mert man per Queue/Synchronize „nur schnell“ átpasszol valamit. Ez működhet, de vezethet Use-after-free-hoz, ha a Producer weiterarbeitet, miközben der Consumer bereits freigibt.
FastMM FullDebugMode itt segíthet, mert zeitversetzte Fehler korábban detektálhatók. A tényleges javítás azonban egy tiszta Lebenszeitmodell: klare Besitzverhältnisse, átadás csak über immutable Daten vagy egyértelmű Ownership-Transfer-Punkte.
Wie du Reports nutzbar machst: Filtern, vergleichen, dokumentieren
Csapatoknál érdemes Leak-Reports nem csak „anzuschauen“, hanem mint artefakt kezelni. Három pragmatikus intézkedés, amelyek beváltak:
- Baseline-Report: Egy „bekannter Zustand“ (z. B. aktuelle Produktversion) egyszer FullDebugMode alatt lefut és referenciaként eltárolódik. Így új Leaks azonnal felismerhetők.
- Vergleich nach Use-Case: Kritische Workflows (Import, Export, API-Request, UI-Massenoperation) esetén definiálj egy rövid, rendszeresen ismételhető szekvenciát.
- Dokumentierte „legitime Leaks“: Ha egy Cache szándékosan nincs finalisiert, dokumentáld azt. Különben hat hónap múlva valaki újra ugyanazokat a bejegyzéseket fogja üldözni.
Ez nem bürokrácia, hanem időmegtakarítás: a memóriaszivárgás-keresés különben gyorsan végtelen ciklussá válik, mert ugyanazok a minták minden sprintben ismétlődnek.
Mikor éri meg a ráfordítás – és mikor érdemes másképp eljárnod
A FastMM FullDebugMode egy költséggel járó diagnosztikai eszköz. A ráfordítás különösen akkor éri meg, ha:
- Az alkalmazás hosszú ideig fut (Service, Terminalserver-kliens, műszakrendszer, 24/7 folyamatok).
- valódi ügyféladatfolyamokat dolgozol fel, és a tesztek nem fednek le minden útvonalat.
- Az stabilitás fontosabb, mint a rövid távú funkciófejlesztés sebessége (jellemző folyamatközeli szoftvermegoldásoknál).
Ha ezzel szemben csak egy kis asztali segédeszközöd van, amely 30 másodperc múlva leáll, a szivárgás-keresés gyakran másodlagos. Ugyancsak: ha egyszeri memória-tüskefelhatásod van (pl. nagy export), gyakran nem szivárgásról van szó, hanem a streamelési stratégiáról és a heap csúcsterheléséről.
Gyakorlati tanulság: a FullDebugMode nem egy kapcsoló, hanem egy folyamat
FastMM FullDebugMode struktúrát hoz a memóriahibák felderítésébe: láthatóvá teszi az allokációkat, korábban feltárja a heap-korrupciót és stacktrace-eket szolgáltat, amelyekkel a tünet helyett az okot tudod javítani. A döntő tényező azonban nem maga az eszköz, hanem a munkafolyamat: reprodukálható forgatókönyvek, diagnosztizálható buildek, tiszta ownership-szerződések és regresszió egy baseline ellen.
Ha egy makacs szivárgásnál vagy szórványos heap-hibánál akadsz el, és a témát egy nagyobb Delphi-rendszerben tartósan stabilizálni szeretnéd, megéri egy rövid, tiszta diagnosztikai beállítás egyértelmű sorrenddel és kiértékelhető riportokkal. Ha ehhez támogatásra van szükséged az elemzésben, build-profilokban vagy az architektúra-refaktorálásban: lépj kapcsolatba a következővel: Net-Base Software GmbH.
Ehhez a témához a Delphi memóriaszivárgás-megtalálása és a Fastmm Leak Report olvasása is fontos. A cikk ezeket a szempontokat érthetően rendszerezi és bemutatja, mire kell figyelni a napi gyakorlatban.
Projekt vagy modernizációs kezdeményezés megbeszélése: Net-Base.
Következő lépés
Ha a téma valós projektté válik, az architektúrát, a meglévő rendszert és az üzemeltetést már korán együtt kell értékelni.
Nemcsak egyedi kérdésekben támogatunk, hanem akkor is, amikor forráskódrészletekből, örökölt rendszerekkel kapcsolatos témákból vagy portálötletekből robusztus vállalati projektet kell kialakítani.
- A jelenlegi állapotot, a célállapotot és a műszaki kockázatokat együttesen értékeljük.
- REST, az adathozzáférés, a portálok és a Rollout nem kerülnek utólagos teendőkként elhalasztásra.
- Már korán láthatja, melyik út gazdaságilag és üzemeltetési szempontból életképes.