Od teme magazina do projektne prakse
Povezane stranice usluga i tehnologije za članak
Ako se Delphi-aplikacija u radu postupno „nadima“, povremeno pada s pogreškama pristupa (Access Violations) ili nakon nekoliko dana rada iznenada postane nestabilna, iza toga često nije pojedinačni bug nego obrazac: memorija se alocira, ali se ne oslobodi ispravno — ili se oslobodi prerano pa se kasnije ponovno koristi. Upravo je u takvim slučajevima FastMM FullDebugMode izuzetno vrijedan. Ne kao stalno stanje, nego kao ciljani dijagnostički alat koji iz „negdje u heapu nešto je pokvareno“ stvara ponovno razumljiv uzrok.
Problem je u tome: FullDebugMode generira velike količine izlaza, smanjuje performanse i lako vodi do pogrešnih tumačenja. Izvještaj o curenju memorije ne pokazuje automatski mjesto gdje je „greška“. A ein Stacktrace je vrijedan onoliko koliko je dobra razlučivost simbola (MAP-Datei, Debug-Infos, inlining). U ovom ću članku proći tipične rubne slučajeve, objasniti ispravan pristup i zamke — tako da na kraju ne samo pronađeš curenja, nego ih i trajno ukloniš.
Kada je FastMM FullDebugMode zaista smislen
U modernim verzijama Delphi FastMM je često već zadani memory manager ili se u mnogim projektima ionako uključuje. FullDebugMode je međutim posebna konfiguracija: označava blokove memorije dodatnim kontrolnim uzorcima, prikuplja Allokations-Stacktraces i agresivnije provjerava Heap-Korruption (tj. oštećenje upravljačkih podataka u heapu, npr. zbog Buffer-Overruns).
FullDebugMode koristim ciljano kada se pojavi jedna od sljedećih situacija:
- Reproduktivno curenje memorije: potrošnja memorije raste tijekom testnog izvođenja po operaciji (npr. po requestu, po importu, po UI-akciji).
- Povremene AVs: osobito one koje se događaju „povremeno ovdje, povremeno ondje“ u istom području (klasično: Use-after-free).
- Heap-Korruption: poruke poput „Invalid pointer operation“, „Access violation in ntdll“ ili padovi pri zatvaranju/finalizaciji.
- Pretraga regresija: nakon refaktoriranja, ažuriranja biblioteke ili promjene kompajlera iznenadna nova nestabilnost.
Nije smisleno koristiti FullDebugMode kao „uključimo to u svim buildovima“. Overhead je velik, mijenja se vremensko ponašanje, i baš zbog toga Race-Conditions mogu nestati ili se premjestiti. Za trajni rad prikladnije je lagano nadgledanje (npr. Prozess-Working-Set, Private Bytes, Zähler pro Vorgang) – FullDebugMode je skalpel, ne pulsni mjerač.
Osnovno načelo: Leak-Report ist Symptom, Stacktrace ist Spur
Izvještaj o curenju memorije prvo pokazuje: ovi blokovi su na kraju programa još uvijek alocirani. To je automatski problem samo ako su ti blokovi zapravo trebali biti oslobođeni. Postoje legitimna „curenja“: globalni Singletons, Caches, OS-Handles s životnim ciklusom procesa ili third-party biblioteke koje namjerno ne finaliziraju. Te slučajeve trebaš poznavati, ali ih ne trebaš slijepo „popravljati“.
Der Stacktrace im Report zeigt die Stelle, an der der Block angefordert wurde. Das ist oft nicht der Ort, an dem du „Free vergessen“ hast. Häufige Realität in gewachsenen Systemen:
- Alokacija u UI‑ ili servisnom sloju, oslobađanje bi se trebalo dogoditi u nižem sloju (Ownership nije jasno).
- Alokacija u einer Factory, Ownership wird an den Caller übergeben – aber der Caller denkt, es wäre „owned“.
- Objekti se drže u Collections (Liste, Dictionaries), ali model vlasništva nije konzistentan.
- Eine Exception‑Pfad preskače Cleanup, jer try/finally nedostaje ili počinje prekasno.
Ispravan postupak je stoga: reproducirati → izolirati → razriješiti Stacktrace → pronaći grešku u vlasništvu → ispravak uz regresijski test. FastMM ti daje tragove, ali ih moraš prevesti u arhitekturu i životne cikluse.
Pravilno aktivirati FastMM FullDebugMode (bez previdanja nuspojava)
FullDebugMode se u praksi aktivira putem FastMM opcija i odgovarajuće FastMM konfiguracije. Presudno nije toliko „kako se točno zove include-datoteka“, koliko što konfiguracija uzrokuje i pod kojim build-uvjetima je koristiš.
Preporučeni okvirni uvjeti za Debug-Build
- Debug DCUs und Debug-Infos: Stacktrace-i su korisni samo ako se mogu razriješiti na pravu unit/liniju/adresu. Osiguraj da se generiraju debug-informacije i da je MAP-datoteka dostupna.
- Svjesan odabir optimizacije: Za čitljivost stacktracea obično je bolji neoptimizirani build. Inlining i agresivne optimizacije mogu stackframe-ove „zamagliti“.
- Isti uvjeti izvođenja: Koristi po mogućnosti iste podatke, istu konfiguraciju, ista prava. Mnogi curenja memorije ovise o podacima (npr. rijetki formati, posebni putevi).
- Razdvojiti 64-bit i 32-bit: Ponašanje memorije, poravnavanje i biblioteke trećih strana razlikuju se. Debugiraj na ciljnoj platformi na kojoj se problem pojavljuje.
Jedna točka koju administratori i tehnički voditelji često podcjenjuju: FullDebugMode također može mijenjati timing. Ako koristiš threading, race-conditioni se zbog toga mogu manifestirati drugačije. Zato je korisno paralelno imati i pokret bez FullDebugMode koji samo potvrđuje reprodukciju. FullDebugMode je tada korak za dijagnostiku.
Pazljivo s „ReportMemoryLeaksOnShutdown“
Delphi može putem ReportMemoryLeaksOnShutdown prijaviti curenja pri završetku programa. To je praktično, ali u složenim aplikacijama (servisi, plug-in host, dugotrajna izvođenja) može zavarati: pri gašenju se izvršavaju finalizacijski odjeljci, dretve se zaustavljaju, cachevi se čiste. Curanje koje je tijekom izvođenja kritično može do kraja nestati — ili obrnuto: prividno curenje nastane tek pri gašenju jer se još odvija pozadinski rad.
Za praktično lovljenje curenja stoga je važnije: mjeriti curenje po operaciji (npr. nakon 100 zahtjeva), a ne samo pri završetku. FastMM može u tome pomoći, ali testni scenarij to mora oponašati.
Tipičan rubni slučaj: Leak-Report prikazuje „neki objekt“, ali uzrok je u vlasništvu
Klasik u poslovnim aplikacijama: proces uvoza stvara za svaki zapis pomoćne objekte (npr. StringLists, JSON-parsere, privremene liste). U normalnom toku oni se uredno oslobode. U rijetkim slučajevima (preskakanje zbog validacije, Exception, rani Exit) neki objekt ostane „visjeti“. Nakon 10.000 zapisa to postane vidljivo.
FastMM FullDebugMode pomaže ovdje jer prikazuje mjesto alokacije. Aber der „Fix“ ist nicht „free an die Stelle der Allokation“. Rješenje je robustan Ownership-Pattern:
- Tko stvori objekt nije automatski Owner.
- Ownership mora biti jasno definiran u API-ugovoru (Parameter/Return, dokumentacija, konvencije imenovanja).
- Kolekcije moraju biti jednoznačne: owning vs. non-owning. Mješoviti oblici se osvećuju.
- Exception-Pfade trebaju rane try/finally-blokove.
Ako iz stacktracea vidiš samo „TStringList.Create“, ta informacija nije bezvrijedna – ali ti govori samo: ovdje se nešto stvara. Pitanje je: gdje bi to trebalo završiti? I u tom pogledu pomaže razmišljanje s arhitekturne razine više nego akrobatika s debuggerom.
Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst
Stacktrace iz FastMM obično je lista povratnih adresa koje se – uz debug-simbolе – preslikavaju na unita, procedure i idealno brojeve redaka. Kada to čitaš, tri stvari su presudne:
- Top-of-Stack ist nicht immer der Fehler: gornji frameovi su često Memory-Manager/RTL. Zanimljivo postaje tamo gdje počinje tvoj kod.
- Call-Chain statt Einzelzeile: linija je samo točka. Lanac ti pokazuje koji je put doveo do alokacije.
- Mehrere identische Blöcke: ako FastMM prijavljuje više leakova iste veličine, to je često ponavljajući put. To je dobro: imaš reproduktivnost.
Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs
Mnogi timovi posrću ovdje: FullDebugMode je aktivan, dolazi leak-report, ali umjesto Unit/Zeile postoje samo adrese ili kriptični simboli. Tipični uzroci:
- Nema MAP-datoteke ili debug-informacije nisu generirane.
- Radiš s Release-DCUs ili DLL-ovima trećih strana bez simbola.
- Aplikacija koristi Runtime Packages: tada su dijelovi koda u BPL-ovima i razlučivanje simbola mora tomu odgovarati.
- Optimizacija/Inlining je učinila stacktrace manje čitljivim.
U praksi to znači: za lov na leakove trebaš build koji je svjesno „diagnostički sposoban“. To je drugačiji cilj od „što brže“. Tehnički Leads trebaju to tretirati kao zaseban build-profil, kako svaki član tima ne bi ad hoc mijenjao projektne opcije.
Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben
Jedan primjer iz prakse (bez konkretnog korisničkog koda): Stacktrace ti u prvom okviru u svom kodu pokazuje rutinu „LoadConfig“. Tamo vidiš stvaranje objekta. Dodaješ Free, leak nestane – i odjednom na drugom mjestu puca s Double Free. Zašto? Zato što „LoadConfig“ stavlja objekt u cache, a drugi kodni put je već vlasnik i kasnije ga posprema.
Ispravno čitanje bi bilo: Stacktrace ti pokazuje, gdje nastaje blok. Popravak leži često u definiciji: Tko je vlasnik objekta 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 u stvarnosti posljedični problemi: Buffer-Overrun prepisuje Heap-Metadaten, Memory-Manager kasnije ne može pravilno osloboditi memoriju, i na kraju vidiš navodno nasumične Leaks ili Invalid Pointer Operations. FullDebugMode je u tim slučajevima jak, jer radi s provjerama uzoraka i pri Free/Reuse izvodi dodatne validacije.
Važno je razlučiti:
- Leak: Blok je alociran i nikad oslobođen. Stabilnost trpi tijekom vremena, pad programa nije nužan.
- Use-after-free: Blok je oslobođen, ali se kasnije još koristi. Dovodi do sporadičnih AV-ova koji se teško reproduciraju.
- Double Free: Blok se oslobađa dvaput. Može odmah uzrokovati pad ili tek kasnije (kad je blok ponovno iskorišten).
- Heap-Korruption: Netko piše izvan granica bloka. Simptomi se često pojavljuju s vremenskim pomakom.
FullDebugMode je posebno vrijedan kad simptome vidiš s vremenskim pomakom. Dodatna validacija otkriva greške ranije – često upravo na mjestu gdje se događa nepravilni pristup, a ne tek minutes kasnije u nekakvom Free.
Vorgehen in Projekten: Reproduzierbare Leak-Jagd statt „Debugging im Nebel“
Ako želiš loviti memorijske curke, treba ti postupak koji se može ponoviti i dijeliti unutar tima. Volim raditi s fiksnim dijagnostičkim okvirom:
1) Reproduktion in einem deterministischen Szenario
Definiraj testnu sekvencu koja pouzdano pokazuje leak: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ ili „Öffne Maske X, führe Aktion Y 200-mal aus“. Važno je da sekvencu dokumentiraš s parametrima (podatkovni set, tenant, Feature-Flags), tako da je drugi mogu reproducirati.
2) Minimieren: Leak pro Schritt sichtbar machen
Ako sekvenca traje 20 minuta, podijeli je. Cilj je: želiš što brže usporediti „prije“ i „poslije“. U velikim aplikacijama to je često pravi gubitak vremena, a ne samo popravljanje.
3) FullDebugMode einschalten und Report interpretieren
Sada tek dolazi do izražaja FastMM FullDebugMode. Sakupi izvještaje, grupiraj prema veličini bloka/Callstacku i provjeri ponavljanja. Pojedinačni preostali blok može biti legitimni cache. 10.000 identičnih blokova gotovo uvijek znači 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“ neposredno uz Create često je krivo mjesto ako se objekt dalje prosljeđuje.
5) Regresija: ista sekvenca, isti build, isti izvještaj
Popravak je dobar tek kad se sekvenca ponovno izvršava i ne pojavljuju se ni curenja memorije ni novi pogreške u memoriji. Pogotovo kod use-after-free, „nestanak leaka“ nije dokaz, već samo novi simptom.
Tipične zamke u Delphi-kodu, koje FastMM čini vidljivima
Kolekcije i vlasništvo (liste, rječnici, sučelja)
Mnogi slučajevi curenja ne proizlaze iz složenih algoritama, nego iz svakodnevnih podatkovnih struktura. Dva klasična obrasca pogrešaka:
- Lista sadrži objekte, ali nitko ne zna tko ih oslobađa. Rješenje: koristiti vlasničku listu ili dosljedno čistiti u finally bloku.
- Rječnik drži objekte kao vrijednosti; pri Remove vrijednost se ne oslobađa ili se pri Clear zaboravi.
Dodatno problematična su sučelja: brojanje referenci (slično ARC) je praktično, ali miješani rad s vlasništvom objekata može kod cikličnih referenci ili događaja uzrokovati curenja memorije. FullDebugMode često pokaže put alokacije, ali uzrok je referencijalni ciklus (A drži B preko sučelja, B drži A preko callbacka).
Iznimke i rani izlazi
U rastućim poslovnim softverskim sustavima iznimke su često dio normalne kontrole toka (npr. validacija, prekid, retry). Problem rijetko leži u samoj iznimci, već u putu oko nje: objekt se stvori prije try/finally, zatim se dogodi iznimka i cleanup se preskoči. FullDebugMode daje ti stacktrace alokacije – i moraš provjeriti postoji li zagarantiran put za oslobađanje.
Niti i životni vijek: „oslobađanje u pogrešnoj niti”
Kod VCL/FMX i servisa s worker-nitima javlja se još jedan rubni slučaj: objekt se stvori u jednoj niti, ali se oslobodi u UI-niti (ili obrnuto), zato što se preko Queue/Synchronize „samo brzo“ nešto prebaci. To može funkcionirati, ali može dovesti i do use-after-free ako proizvođač nastavi raditi dok potrošač već oslobađa.
FastMM FullDebugMode može pomoći jer ranije detektira vremenski odgođene pogreške. Pravi popravak je međutim čist model životnog vijeka: jasni odnosi vlasništva, prijenos samo preko immutable podataka ili jasno definirane točke prijenosa vlasništva.
Kako učiniti izvještaje korisnima: filtriranje, uspoređivanje, dokumentiranje
U timovima se isplati tretirati izvještaje o curenju memorije ne samo kao nešto za „pogledati“, već kao artefakt. Tri pragmatične mjere koje su se pokazale:
- Baseline-Report: Jedno „poznato stanje“ (npr. aktualna verzija proizvoda) jednom se prođe s FullDebugMode i pohrani kao referenca. Tako odmah uočavaš nova curenja memorije.
- Usporedba prema Use-Case: Za kritične radne tokove (Import, Export, API-Request, UI-masovne operacije) definiraš za svaki kratku sekvencu koja se redovito može ponavljati.
- Dokumentirana „legitimna curenja memorije“: Ako se cache svjesno ne finalizira, dokumentiraj to. Inače će za šest mjeseci netko ponovno ganjati iste unose.
Ovo nije birokracija, već ušteda vremena: potraga za curenjem brzo postane beskonačna petlja jer se isti obrasci ponavljaju u svakom sprintu.
Kada se trud isplati – i kada trebaš postupiti drugačije
FastMM FullDebugMode je dijagnostički alat s troškovima. Trud se osobito isplati kada:
- Aplikacija dugo radi (servis, terminal-server-klijent, rad u smjenama, 24/7 procesi).
- obrađuješ stvarne tokove podataka kupaca i ne pokrivaš sve puteve u testiranju.
- stabilnost je važnija od kratkoročne brzine isporuke funkcionalnosti (tipično za softverska rješenja bliska procesu).
Ako pak imaš samo mali desktop-pomoćnik koji završi nakon 30 sekundi, potraga za curenjem često je drugorazredna. Isto tako: ako imaš jednokratni memory-spike problem (npr. veliki izvoz), često nije riječ o curenju već o strategiji streamanja i vršnoj opterećenosti heap-a.
Zaključak iz prakse: FullDebugMode nije prekidač, već proces
FastMM FullDebugMode unosi strukturu u pronalaženje pogrešaka u memoriji: čini alokacije vidljivima, ranije otkriva korupciju heap-a i isporučuje stack trace-ove pomoću kojih možeš popraviti uzrok, a ne simptom. Međutim odlučujuća poluga nije alat nego postupak: reproducibilni scenariji, buildovi pogodni za dijagnostiku, jasni ugovori o vlasništvu i regresija prema referentnoj osnovi.
Ako zapneš na tvrdokornom curenju ili sporadičnoj pogrešci heap-a i želiš temu dugoročno stabilizirati u većem Delphi-sistemu, isplati se kratki, uredni dijagnostički setup s jasnom sekvencom i analiziranim izvještajima. Ako za to trebaš pomoć pri analizi, build-profilima ili arhitektonskom refaktoriranju: kontaktiraj Net-Base Software GmbH.
Za ovu temu su također važni Delphi Pronalaženje memorijskih curenja i čitanje Fastmm Leak Reporta. Članak te aspekte jasno kontekstualizira i pokazuje na što se u svakodnevnom radu treba usredotočiti.
Razgovaraj o projektu ili modernizacijskom zahvatu s Net-Base.
sljedeći korak
Ako se tema pretvori u stvarni projekt, arhitekturu, postojeće sustave i operacije trebalo bi rano zajednički razmotriti.
Podržavamo vas ne samo u pojedinačnim pitanjima, već i kada iz isječaka izvornog koda, naslijeđenih sustava ili ideja za portale treba nastati pouzdan poslovni projekt.
- Postojeće stanje, ciljna slika i tehnički rizici procjenjuju se zajedno.
- REST, pristup podacima, portali i rollout neće biti odgođeni kao naknadne posljedice.
- Rano prepoznajete koji je put ekonomski i operativno održiv.