Od teme magazina do projektne prakse
Povezane stranice usluga i tehnologije za članak
Ako se Delphi-aplikacija tokom rada postupno napuše, povremeno se sruši s Access Violations ili nakon nekoliko dana rada iznenada postane nestabilna, često iza toga ne stoji pojedinačan bug, već obrazac: memorija se alocira, ali se ne oslobodi ispravno — ili se prerano oslobodi i kasnije se ponovo koristi. Upravo zbog toga je FastMM FullDebugMode izuzetno vrijedan. Ne kao trajno stanje, već kao ciljano dijagnostičko sredstvo koje iz „negdje u heapu nešto je pokvareno“ ponovno radi prepoznatljiv uzrok.
Problem je: FullDebugMode generira mnogo outputa, košta performansi i lako vodi do pogrešnih interpretacija. Leak-Report ne pokazuje automatski mjesto gdje je „greška“. A Stacktrace je vrijedan samo onoliko koliko je dobra razlučivost simbola (MAP-datoteka, debug-informacije, inlining). U ovom članku prolazim kroz tipični rubni slučaj, objašnjavam čist pristup i zamke – tako da na kraju ne samo pronađeš curenja, nego ih i trajno otkloniš.
Kada je FastMM FullDebugMode zaista smislen
FastMM je u modernim Delphi-verzijama često već zadani memory manager ili se u mnogim projektima ionako uključi. FullDebugMode je posebna konfiguracija: obilježava memorijske blokove dodatnim kontrolnim obrascima, prikuplja Allokations-Stacktraces i agresivnije provjerava Heap-Korruption (tj. oštećene upravljačke podatke u heapu, npr. zbog Buffer-Overruns).
FullDebugMode ciljano koristim kad se pojavi jedna od sljedećih situacija:
- Reproduzierbares Leak: potrošnja memorije raste tokom test-pokretanja po operaciji (npr. po Request, po Import, po UI-akciji).
- Sporadische AVs: naročito one koje se dešavaju „ponekad ovdje, ponekad tamo“ u istom području (klasično: Use-after-free).
- Heap-Korruption: poruke poput „Invalid pointer operation“, „Access violation in ntdll“ ili padovi pri završetku/finalizaciji.
- Regressionssuche: nakon refaktoriranja, update-a biblioteke ili promjene kompajlera iznenada se pojavi nova nestabilnost.
FullDebugMode nije smislen kao „upalimo to u svim buildovima“. Režija je visoka, timing se mijenja, i naročito race-conditioni mogu zbog toga nestati ili se premjestiti. Za stalni rad prikladnije je tanje monitoring rješenje (npr. Prozess-Working-Set, Private Bytes, brojila po operaciji) – FullDebugMode je skalpel, ne pulsni mjerač.
Osnovno načelo: Leak-Report je simptom, Stacktrace je trag
Leak-Report ti prvo pokaže: ovi blokovi su na kraju programa još alocirani. To je automatski problem samo ako su ti blokovi zapravo trebali biti oslobođeni. Postoje legitimna „leaks“: globalni singletoni, cache-ovi, OS-handleri s životnim vijekom procesa ili third-party biblioteke koje namjerno ne finaliziraju. Te slučajeve želiš znati, ali ih ne smiješ slijepo „popraviti“.
Stacktrace u reportu pokazuje mjesto gdje je blok alociran. To često nije mjesto gdje si „zaboravio pozvati Free“. U praksi rastaćih sistema često se dešava:
- Alokacija u UI- ili servisnom sloju, oslobađanje bi trebalo da se desi u dubljem sloju (ownership nejasan).
- Alokacija u einer Factory, ownership se prenosi na caller — ali caller misli da je on „owner“.
- Objekti se drže u Collections (liste, Dictionaries), ali Ownership-Model nije konzistentan.
- Putanja iznimke preskače cleanup, jer try/finally nedostaje ili počinje prekasno.
Jasan postupak je dakle: reproducirati → izolirati → razriješiti Stacktrace → pronaći grešku u vlasništvu → ispravka s regresionim testom. FastMM ti daje tragove, ali ih moraš prevesti u arhitekturu i životne cikluse.
Ispravno aktivirati FastMM FullDebugMode (bez propuštanja nuspojava)
FullDebugMode se u praksi aktivira putem FastMM opcija i odgovarajuće FastMM konfiguracije. Manje je važno „kako se tačno zove include-datoteka“, a važnije šta konfiguracija uzrokuje i pod kojim uvjetima izgradnje je koristiš.
Preporučeni okvirni uvjeti za Debug-Build
- Debug DCUs i debug-informacije: Stacktraceovi su korisni samo ako se mogu razriješiti na stvarnu jedinicu/liniju/adresu. Provjeri da su generirane debug-informacije i da je MAP-datoteka dostupna.
- Svjesno odabrati optimizaciju: Za čitljivost stacktrace-a obično je bolji neoptimiziran build. Inlining i agresivne optimizacije mogu „zbiti“ stack frameove.
- Isti uvjeti izvršavanja: Koristi po mogućnosti iste podatke, istu konfiguraciju, ista prava. Mnogi slučajevi curenja memorije ovise o podacima (npr. rijetki formati, posebne putanje).
- Odvojiti 64-bit i 32-bit: Ponašanje memorije, poravnanje i biblioteke trećih strana se razlikuju. Debugiraj na ciljnoj platformi na kojoj se problem javlja.
Jednu stvar koju administratori i tehnički voditelji često potcjenjuju: FullDebugMode može promijeniti i timing. Ako koristiš višestruko izvršavanje niti, race-conditioni se zbog toga mogu pojavljivati drugačije. Zato je korisno paralelno imati i pokretanje bez FullDebugMode koje samo potvrđuje reprodukciju. FullDebugMode je tada korak za dijagnostiku.
Pažnja pri „ReportMemoryLeaksOnShutdown“
Delphi može preko ReportMemoryLeaksOnShutdown prijaviti curenja memorije pri završetku programa. To je praktično, ali u složenim aplikacijama (servisi, plug-in host, dugotrajni rad) može zavarati: pri gašenju se izvode finalizacijski odjeljci, niti se zaustavljaju, cacheovi se čiste. Curenje koje je u sredini vremena izvršavanja kritično može do kraja nestati – ili obratno: prividno curenje pojavljuje se tek pri gašenju zato što još radi pozadinski posao.
Za praktično traženje curenja memorije važnije je: mjeriti curenje po operaciji (npr. nakon 100 zahtjeva), a ne samo pri zatvaranju. FastMM može pomoći, ali postavka testa to mora reproducirati.
Tipičan rubni slučaj: izvještaj o curenju memorije prikazuje „neki objekt“, ali uzrok je u vlasništvu
Klasičan slučaj u poslovnim aplikacijama: proces uvoza stvara po zapis pomoćne objekte (npr. StringLists, JSON-Parser, privremene liste). U normalnom toku (Happy Path) oni se pravilno oslobode. U rijetkim slučajevima (preskakanje zbog validacije, Exception, rani Exit) neki objekt ostane neosloboden. Nakon 10.000 zapisa to postane vidljivo.
FastMM FullDebugMode pomaže ovdje, jer pokazuje mjesto alokacije. Ali „Fix“ nije „free na mjesto alokacije“. Pravo rješenje je robustan vlasnički obrazac (Ownership-Pattern):
- Ko stvori objekt nije automatski njegov Owner.
- Vlasništvo mora biti jasno u API-ugovoru (Parameter/Return, dokumentacija, konvencije imenovanja).
- Kolekcije moraju biti jednoznačne: owning vs. non-owning. Mješoviti oblici se osvete.
- Putanje s Exception trebaju rane try/finally-blokove.
Ako iz Stacktrace-a vidiš samo „TStringList.Create“, informacija nije bezvrijedna – ali ti govori samo: ovdje nešto nastaje. Pitanje je: gdje bi to trebalo završiti? I tu pomaže arhitektonsko razmišljanje više nego akrobatika s debuggerom.
Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst
Stacktrace iz FastMM je obično lista povratnih adresa koje se – uz debug-simbolе – preslikavaju na Units, procedure i idealno brojeve linija. Kad to čitaš, tri stvari su presudne:
- Top-of-Stack nije uvijek uzrok greške: Najviše frame-ove često čine Memory-Manager/RTL. Interesantno postaje tamo gdje počinje tvoj kod.
- Call-Chain umjesto pojedinačne linije: Linija je samo točka. Lanac ti pokazuje koji put je vodio do alokacije.
- Više identičnih blokova: Ako FastMM prijavi više leak-ova iste veličine, to je često ponavljajući put. To je dobro: imaš reprodukciju.
Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs
Mnogi timovi zapnu na toj točki: FullDebugMode je aktivan, leak-report stigne, ali umjesto Unit/linije dobiješ samo adrese ili kriptične simbole. Tipični uzroci:
- Nema MAP-datoteke ili debug-informacije nisu generisane.
- Pokrećeš se protiv Release-DCUs ili tuđih DLL-ova bez simbola.
- Aplikacija koristi Runtime Packages: tada dijelovi koda leže u BPLs, i rješavanje simbola mora biti usklađeno.
- Optimizacija/Inlining je učinila stacktrace teže čitljivim.
U praksi to znači: za potragu za leakovima trebaš build koji je svjesno „dijagnostički sposoban“. To je drugačiji cilj od „što brže moguće“. Tehnički Leads trebaju to tretirati kao poseban build-profil, kako ne bi svaki član tima ad hoc mijenjao projektne opcije.
Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben
Primjer iz prakse (bez konkretnog klijentskog koda): Stacktrace ti kao prvi frame u tvom kodu pokazuje rutinu „LoadConfig“. Vidiš tamo kreiranje objekta. Dodaješ Free, leak je nestao – i odjednom puca negdje drugo sa Double Free. Zašto? Zato što „LoadConfig“ smješta objekt u cache, a drugi put kroz kod je već Owner i kasnije očisti.
Ispravna interpretacija bi bila: Stacktrace ti pokazuje gdje nastaje blok. Fix je često u definiciji: tko posjeduje objekt nakon return? Ako na to pitanje ne odgovoriš jasno, samo mijenjaš sliku greške (Leak → AV).
Heap-Korruption vs. Leak: Warum FullDebugMode oft den echten Übeltäter findet
Mnogi „Leaks“ su zapravo posljedični problemi: buffer-overrun prepisuje heap-metapodatke, menadžer memorije kasnije ne može uredno osloboditi, i na kraju vidiš naizgled slučajne Leaks ili Invalid Pointer Operations. FullDebugMode je ovdje jak zato što radi s provjeriteljskim obrascima i pri Free/Reuse radi dodatne validacije.
Važno je razlikovati:
- Leak: blok je alociran i nikada nije oslobođen. Stabilnost pati kroz vrijeme, crash nije nužan.
- Use-after-free: blok je oslobođen, ali se kasnije još koristi. Vodi do sporadičnih AV-ova koji su teško reprodukovljivi.
- Double Free: blok se oslobađa dvaput. Može odmah puknuti ili tek kasnije (kad je blok ponovno upotrijebljen).
- Heap-Korruption: neko piše izvan granica bloka. Simptomi se često pojavljuju s odgodom.
FullDebugMode je posebno vrijedan kad simptomi imaju vremensko kašnjenje. Dodatna validacija čini greške vidljivijima ranije – često upravo na mjestu gdje se događa pogrešan pristup, a ne tek minute kasnije u nekom nasumičnom Free.
Postupak u projektima: Reproducibilna Leak-potjera umjesto „Debugging im Nebel“
Ako želiš loviti memorijske curenja, trebaš postupak koji se može ponoviti i dijeliti u timu. Radim rado s fiksnim okvirom dijagnostike:
1) Reprodukcija u determinističkom scenariju
Postavi testnu sekvencu koja pouzdano pokazuje leak: „Pokreni Service, obradi 500 poruka, zaustavi Service“ ili „Otvori prozor X, izvrši akciju Y 200 puta“. Važno je da sekvencu dokumentiraš s parametrima (sadržaj podataka, tenant, feature-flagovi), kako bi je drugi mogli rekonstruirati.
2) Minimiranje: učini leak vidljivim po koraku
Ako sekvenca traje 20 minuta, podijeli je. Cilj je: želiš što brže usporediti „prije“ i „poslije“. U velikim aplikacijama je to često pravi gubitak vremena, ne samo popravljanje.
3) FullDebugMode einschalten und Report interpretieren
Sada tek FastMM FullDebugMode dolazi u igru. Prikupljaj izvještaje, grupiši po veličini bloka/callstacku i traži ponavljanja. Jedan preostali blok može biti legitimni cache. 10.000 identičnih blokova su gotovo uvijek stvarno curenje memorije.
4) Razjašnjenje vlasništva i ispravak u odgovarajućem sloju
Ispravljaj curenja tamo gdje se definira vlasništvo: Factory, API-ugovor, Collection-Wrapper. „Brzo ubaciti Free“ direkt pored Create često je pogrešno mjesto ako se objekat prosljeđuje.
5) Regresija: ista sekvenca, isti Build, isti Report
Ispravka je dobra tek kada se sekvenca ponovo izvršava i ne javljaju se ni curenja memorije ni novi greške u memoriji. Posebno kod Use-after-free, „nestanak curenja“ nije dokaz, već samo novi simptom.
Tipične zamke u Delphi-Code, koje FastMM čini vidljivim
Kolekcije i vlasništvo (liste, rječnici, interfejsi)
Mnoge pojave curenja ne potiču iz složenih algoritama, već iz svakodnevnih struktura podataka. Dva klasična obrasca grešaka:
- Lista sadrži objekte, ali niko ne zna ko ih oslobađa. Rješenje: koristiti owning listu ili dosljedno prazniti u finally-bloku.
- Ein Dictionary drži objekte kao Values; pri Remove se Value ne oslobađa ili se pri Clear zaboravi.
Dodatno tricky su Interfaces: referentno brojanje (ARC-ähnlich) je praktično, ali mješoviti rad s objekt-ownershipom može pri cikličnim referencama ili Events uzrokovati curenja. FullDebugMode često pokaže put alokacije, ali uzrok je referencni ciklus (A drži B preko Interface, B drži A preko Callback).
Exceptions i rani izlazi
U razvijenim business-softverskim sistemima su Exceptions često dio normalne kontrole toka (npr. validacija, prekid, Retry). Problem rijetko leži u samoj Exception, već u okolnom putu: objekat se kreira prije try/finally, zatim nastane Exception i Cleanup se preskoči. FullDebugMode ti daje den Stacktrace alokacije – i moraš provjeriti postoji li garantovan put za oslobađanje.
Threadovi i životni vijek: „Oslobađanje u pogrešnom Threadu“
Kod VCL/FMX i servisa s Worker-Threads nastaje još jedan rubni slučaj: objekat se stvori u jednom Threadu, ali se oslobodi u UI-Threadu (ili obrnuto), jer se preko Queue/Synchronize „samo brzo“ nešto prebacuje. To može funkcionisati, ali može dovesti i do Use-after-free ako Producer nastavi raditi dok Consumer već oslobađa.
FastMM FullDebugMode može pomoći ovdje jer detektuje vremenski odgođene greške ranije. Pravi popravak je međutim čist model životnog vijeka: jasna vlasnička prava, prijenos samo preko nepromjenljivih (immutable) podataka ili jasne tačke prijenosa vlasništva.
Kako učiniti izvještaje upotrebljivim: filtriranje, uspoređivanje, dokumentiranje
U timovima se isplati Leak-Reports ne samo „pogledati“, već ih tretirati kao artefakt. Tri pragmatične mjere koje su se pokazale:
- Baseline-Report: Jedan „poznat stan“ (npr. trenutna verzija proizvoda) jednom se pokrene s FullDebugMode i pohrani kao referenca. Tako odmah prepoznaš nova curenja.
- Usporedba po Use-Case: Za kritične tokove (Import, Export, API-Request, UI-masovna operacija) definiraš po jednu kratku sekvencu koja se redovno može ponavljati.
- Dokumentovana „legitimna curenja“: Ako se Cache svjesno ne finalizira, dokumentiraj to. Inače će za šest mjeseci neko opet juriti iste unose.
Ovo nije birokracija, već ušteda vremena: potraga za curenjima inače brzo postane beskonačna petlja, jer se isti obrasci ponavljaju u svakom sprintu.
Kada se trud isplati – a kada treba postupiti drugačije
FastMM FullDebugMode je dijagnostički alat koji nosi troškove. Trud se posebno isplati ako:
- Aplikacija dugo radi (servis, Terminalserver-klijent, sistem smjena, 24/7 procesi).
- obrađuješ stvarne tokove podataka klijenata i ne pokrivaš sve puteve u testiranju.
- stabilnost je važnija od kratkoročne brzine dodavanja funkcionalnosti (tipično za procesno bliska softverska rješenja).
Ako, s druge strane, imaš mali desktop-pomoćnik koji se nakon 30 sekundi završi, potraga za curenjima često je sporedna. Isto tako: ako imaš jednokratni memory-spike problem (npr. veliki izvoz), često nije riječ o curenju memorije nego o strategiji streaminga i vršnoj opterećenosti Heap-a.
Praktični zaključak: FullDebugMode nije prekidač, već proces
FastMM FullDebugMode unosi strukturu u potragu za greškama memorije: čini alokacije vidljivim, otkriva korupciju Heap-a ranije i isporučuje stacktrace-ove pomoću kojih možeš ispraviti uzrok umjesto simptoma. Presudna poluga nije alat, već tijek rada: reproducibilni scenariji, dijagnostički buildovi, jasna pravila vlasništva i regresno testiranje u odnosu na referentnu osnovu.
Ako zapneš na upornom curenju ili sporadičnoj grešci Heap-a i želiš to dugoročno stabilizirati u većem Delphi-sistemu, isplati se kratak, uredan dijagnostički setup sa jasnom sekvencom i izvještajima koji se mogu evaluirati. Ako za to trebaš podršku pri analizi, build-profilima ili refaktoringu arhitekture: kontaktiraj Net-Base Software GmbH.
Za ovu temu su također važni Delphi pronalazak curenja memorije i čitanje Fastmm Leak Report-a. Članak ove aspekte jasno pozicionira i pokazuje na što se u praksi treba fokusirati.
Sljedeći korak
Kada se tema pretvori u stvarni projekat, arhitektura, postojeći sistem i operacije trebaju se rano sagledati zajedno.
Pružamo podršku ne samo pri pojedinačnim pitanjima, već i kada iz fragmenata izvornog koda, naslijeđenih sistema ili ideja za portal treba nastati robustan poslovni projekat.
- Postojeće stanje, ciljno stanje i tehnički rizici procjenjuju se zajedno.
- REST, pristup podacima, portali i Rollout se ne odgađaju kao naknadne posljedice.
- Vi rano vidite koji je put ekonomski i operativno održiv.