Od teme v reviji do projektne prakse
Ustrezne strani storitev in tehnični opisi k prispevku
Če se Delphi-aplikacija med obratovanjem počasi „napihuje“, občasno zruši z Access Violation ali pa po dnevih delovanja nenadoma postane nestabilna, za tem pogosto ni en sam bugg, temveč vzorec: pomnilnik se dodeli, a ni pravilno sproščen – ali pa je sproščen prezgodaj in se kasneje še uporablja. Natanko tukaj je FastMM FullDebugMode izjemno vreden. Ne kot trajno stanje, temveč kot ciljno diagnostično orodje, ki iz „nekje v heapu je nekaj pokvarjeno“ naredi sledljiv vzrok.
Težava: FullDebugMode proizvaja veliko izpisa, zmanjšuje zmogljivost in hitro vodi do napačnih razlag. Leak-poročilo ti ne pokaže samodejno mesta, kjer je „napaka“. In stacktrace je dober le toliko, kolikor dobro deluje razreševanje simbolov (MAP-datoteka, debug-info, inlining). V tem prispevku pregledaṁ tipičen robni primer, razložim čist pristop in pasti – tako da na koncu ne le najdeš leake, ampak jih trajno odpraviš.
Kdaj je FastMM FullDebugMode res smiseln
FastMM je v sodobnih različicah Delphi pogosto že privzeti memory manager ali pa je v mnogih projektih vseeno vključen. FullDebugMode je vendar posebna konfiguracija: označi pomnilniške bloke z dodatnimi kontrolnimi vzorci, zbira stacktrace-e ob allokaciji in agresivneje preverja heap-korupcijo (to pomeni poškodovane upravljalne podatke v heapu, npr. zaradi buffer-overrunov).
FullDebugMode uporabljam ciljano, kadar velja eden od naslednjih scenarijev:
- Ponovljivo puščanje pomnilnika: poraba pomnilnika v testnem zagonu raste na enoto obdelave (npr. na zahtevo, na import, na UI-dejanje).
- Občasne AV-ji: zlasti tisti, ki se pojavijo „tu ali tam“ v istem območju (klasično: Use-after-free).
- Heap-korupcija: sporočila, kot so „Invalid pointer operation“, „Access violation in ntdll“ ali zrušitve ob izhodu/finalizaciji.
- Iskanje regresije: po refactoringu, posodobitvi knjižnice ali menjavi compilerja se nenadoma pojavi nova nestabilnost.
FullDebugMode ni smiselno vklapljati kot „pa vklopimo to v vseh buildih“. Overhead je visok, timing se spremeni in prav race-conditioni se lahko zaradi tega izgubijo ali premaknejo. Za stalni obrat je primernejše vitko spremljanje (npr. procesni Working Set, Private Bytes, števec na operacijo) – FullDebugMode je skalpel, ne pulzomer.
Osnovno načelo: Leak-Report je simptom, Stacktrace je sled
Leak-poročilo ti najprej pokaže: Ti bloki so ob koncu programa še vedno allokirani. To je samodejno problem le, če bi ti bloki morali biti sproščeni. Obstajajo legitimne „puščanja“: globalni singletoni, cache-i, OS-handle z življenjsko dobo procesa ali third-party knjižnice, ki namerno ne finalizirajo. Te primere moraš poznati, a jih ne gre slepo „odpraviti“.
Stacktrace v poročilu pokaže mesto, kjer je bil blok zahtevan. To pogosto ni kraj, kjer si je nekdo „pozabil poklicati Free“. Pogoste realnosti v zrelih sistemih:
- Allokacija v UI- ali servisnem sloju, sprostitev bi morala potekati v nižjem sloju (lastništvo ni jasno).
- Allokacija v fabriki, lastništvo se prenese na callerja – a caller misli, da je to njegovo „owned“.
- Objekti se hranijo v kolekcijah (seznami, slovarji), vendar model lastništva ni skladen.
- Exception-poti preskočijo čiščenje, ker try/finally manjka ali se začne prepozno.
Čist potek je torej: reproducirati → izolirati → razrešiti Stacktrace → najti napake lastništva → popravek z regresijskim testom. FastMM ti zagotovi sledi, vendar jih moraš preslikati v arhitekturo in življenjske cikle.
FastMM FullDebugMode pravilno aktivirati (ne spregledati stranskih učinkov)
FullDebugMode se v praksi aktivira preko FastMM-izbir in ustrezne FastMM-konfiguracije. Ključno ni toliko „kako se natančno imenuje vključevalna datoteka“, temveč kaj konfiguracija povzroči in v katerih Build-Bedingungen jo uporabljaš.
Priporočeni Rahmenbedingungen für den Debug-Build
- Debug DCUs und Debug-Infos: Stacktrace-i so uporabni le, če jih je mogoče razrešiti na dejansko enoto/vrstico/naslov. Poskrbi, da se ustvarijo debug-informacije in da je na voljo MAP-datoteka.
- Optimizacijo premišljeno izberite: Za berljivost Stacktrace-ov je večinoma bolj primeren neoptimiziran Build. Inlineiranje in agresivna optimizacija lahko Stackframe-e „zameglijo“.
- Enaki pogoji izvajanja: Uporabljaj čim bolj enake podatke, enako konfiguracijo, enake pravice. Veliko puščanj pomnilnika je odvisnih od podatkov (npr. redki formati, posebne poti).
- Ločitev 64-bit in 32-bit: Vedenje pomnilnika, poravnava in knjižnice tretjih proizvajalcev se razlikujejo. Debugiraj na ciljni platformi, kjer se težava pojavi.
En vidik, ki ga skrbniki in tehnični vodje pogosto podcenjujejo: FullDebugMode lahko tudi timing spremeni. Če imaš v igri večnitenje, se lahko zaradi tega Race-Conditions pojavijo drugače. Zato je smiselno hkrati imeti tudi zagon brez FullDebugMode, ki le potrdi reprodukcijo. FullDebugMode je potem korak za diagnostiko.
Opozorilo pri „ReportMemoryLeaksOnShutdown“
Delphi lahko preko ReportMemoryLeaksOnShutdown prijavi puščanja ob koncu programa. To je praktično, vendar lahko v kompleksnih aplikacijah (Services, Plug-in Host, dolgi časi izvajanja) zavaja: ob zapiranju se izvajajo finalizacijski odseki, niti se ustavijo, predpomnilniki se počistijo. Puščanje, ki je sredi izvajanja kritično, se lahko do konca ponovno odpravi – ali obratno: navidezno puščanje nastane šele pri zapiranju, ker teče še ozadinsko delo.
Za praktično odkrivanje puščanj je zato pomembneje: meriti puščanje na operacijo (npr. po 100 zahtevah), ne le ob zaključku. FastMM lahko pri tem pomaga, vendar mora testni postopek to reproducirati.
Tipičen robni primer: poročilo o puščanju pokaže „nek objekt“, vendar je vzrok lastništvo
Klasika v poslovnih aplikacijah: proces uvoza za vsak zapis ustvari pomožne objekte (npr. StringLists, JSON-parserje, začasne sezname). V želenem poteku se ti pravilno sprostijo. V redkih primerih (preskok zaradi validacije, izjema, zgodnji exit) objekt ostane. Po 10.000 zapisih postane to opazno.
FastMM FullDebugMode tu pomaga, ker pokaže mesto alokacije. Vendar »popravek« ni »free na mestu alokacije«. Pravi popravek je ein robusten vzorec lastništva:
- Kdor ustvari objekt, ni avtomatično owner.
- Lastništvo mora biti jasno v pogodbi API‑ja (parametri/vrnitev, dokumentacija, konvencije poimenovanja).
- Kolekcije morajo biti enoznačne: owning vs. non-owning. Mešani pristopi se maščujejo.
- Poti za izjeme zahtevajo zgodnje try/finally bloke.
Če iz sled klicev vidiš le „TStringList.Create“, informacija ni brez vrednosti – vendar pove le: tu nekaj nastane. Vprašanje je: kje bi to moralo končati? In pri tem pomaga arhitekturno razmišljanje bolj kot akrobatika z debuggerjem.
Pravilno branje sledov klicev: kaj iz njih lahko res izpelješ
Sled klicev iz FastMM je navadno seznam povratnih naslovov, ki se – ob prisotnosti debug‑simbolov – preslikajo na enote, procedure in po možnosti številke vrstic. Ko to bereš, so trije vidiki odločilni:
- Top‑of‑Stack ni vedno vir napake: najzgornji frame‑i so pogosto Memory‑Manager/RTL. Zanimivo postane tam, kjer se začne tvoja koda.
- Veriga klicev namesto ene vrstice: vrstica je le pika. Veriga ti pokaže, katera pot je vodila do alokacije.
- Več identičnih blokov: če FastMM poroča o več puščanjih enake velikosti, gre pogosto za ponavljajočo se pot. To je dobro: imaš reproducibilnost.
Ko manjkajo številke vrstic: MAP‑datoteka, Packages, Release‑DCUs
Veliko ekip se tu spotakne: FullDebugMode je vklopljen, poročilo o puščanju prispe, vendar namesto enote/vrstice dobiš le naslove ali kriptne simbole. Tipični vzroki:
- Ni bila ustvarjena MAP‑datoteka ali debug‑informacije.
- Aplikacija uporablja Release‑DCUje ali zunanje DLL‑je brez simbolov.
- Aplikacija uporablja runtime packages: deli kode so v BPL‑jih in razreševanje simbolov mora temu ustrezati.
- Optimizacija/inlining je naredilo sled klicev manj berljiv.
V praksi to pomeni: za iskanje puščanj potrebuješ build, ki je namensko »diagnostičen«. To je drugačno cilj kot »kar se da hitro«. Tehnični vodje naj to obravnavajo kot ločen build‑profil, da ne spreminja vsak član ekipe ad hoc projektnih nastavitev.
Ocenjevanje frame‑ov: »zanimivo« je pogosto eno vrstico višje
Ena resnična situacija (brez konkretne kode stranke): Der Stacktrace ti kot prvi frame v kodi pokaže rutino „LoadConfig“. Tam vidiš ustvarjanje objekta. Dodaš ein Free, leak izgine – in nenadoma se drugje pojavi zrušitev z Double Free. Zakaj? Ker „LoadConfig“ postavi objekt v cache, in drug potek kode je že Owner in pozneje pospravi.
Pravilna interpretacija bi bila: Der Stacktrace ti pokaže, kje nastane blok. Popravilo pogosto leži v definiciji: Kdo ima lastništvo objekta po Return? Če tega vprašanja ne odgovoriš jasno, le zamenjaš simptom napake (Leak → AV).
Heap-Korruption vs. Leak: Zakaj FullDebugMode pogosto najde pravega krivca
Veliko „Leaks“ je v resnici posledica stranskih napak: Buffer-Overrun prepiše Heap-Metadaten, Memory-Manager kasneje ne more pravilno sprostiti pomnilnika in na koncu vidiš navidez naključne Leaks ali neveljavne operacije s kazalci (Invalid Pointer Operations). FullDebugMode je pri tem močan, ker uporablja preverjalne vzorce in pri Free/Reuse izvaja dodatne validacije.
Pomembna je razločitev:
- Leak: Blok je bil alociran in nikoli sproščen. Stabilnost se s časom poslabša, do zrušitve ni nujno.
- Use-after-free: Blok je bil sproščen, vendar se ga kasneje še uporablja. Povzroči občasne AV-je, ki jih je težko reproducirati.
- Double Free: Blok je sproščen dvakrat. Lahko se zruši takoj ali šele kasneje (ko je bil blok ponovno uporabljen).
- Heap-Korruption: Nekdo piše prek meja bloka. Simptomi se pogosto pojavijo z zamikom.
FullDebugMode je posebej vreden, če simptome opaziš z zamikom. Dodatna validacija naredi napake vidne prej – pogosto natanko tam, kjer pride do napačnega dostopa, ne šele minute kasneje v poljubnem Free.
Pristop v projektih: Reproducibilno iskanje leakov namesto „Debugging im Nebel“
Če želiš loviti puščanja pomnilnika, potrebuješ potek, ki je ponovljiv in ga lahko delijo ekipe. Rad delam z določenim diagnostičnim okvirjem:
1) Reprodukcija v determinističnem scenariju
Določi testno sekvenco, ki zanesljivo pokaže leak: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ ali „Öffne Maske X, führe Aktion Y 200-mal aus“. Pomembno je, da sekvenco dokumentiraš s parametri (podatkovni nabor, Mandant, Feature-Flags), da jo drugi lahko ponovijo.
2) Minimieren: Leak pro Schritt sichtbar machen
Če sekvenca traja 20 minut, jo razdeli. Cilj je, da kar najhitreje primerjaš „vorher“ in „nachher“. V velikih aplikacijah je to pogosto glavni porabnik časa, ne samo popravljanje.
3) FullDebugMode einschalten und Report interpretieren
Šele zdaj pride v igro FastMM FullDebugMode. Zberi poročila, združi po velikosti bloka/stezi klicev in poišči ponovitve. En sam preostali blok je lahko legitimni predpomnilnik. 10.000 identičnih blokov je skoraj vedno resnično puščanje pomnilnika.
4) Določitev lastništva in popravek v ustreznem sloju
Popravi puščanja tam, kjer je definirano lastništvo: Factory, API-kontrakt, Collection-Wrapper. Neposredno dodajanje Free zraven Create je pogosto napačen kraj, če se objekt posreduje naprej.
5) Regression: enaka sekvenca, enak build, enako poročilo
Popravek je dober šele, ko se sekvenca znova izvaja in ne pojavijo se niti puščanja pomnilnika niti nove napake s pomnilnikom. Zlasti pri Use-after-free ni »Leak izginil« dokaz, temveč le nov simptom.
Tipične pasti v Delphi-kodu, ki jih razkrije FastMM
Kolekcije in lastništvo (seznami, slovarji, vmesniki)
Mnoga puščanja niso posledica zapletenih algoritmov, temveč vsakdanjih podatkovnih struktur. Dve klasični napaki:
- Seznam vsebuje objekte, a nihče ne ve, kdo jih sprosti. Rešitev: uporabiti lastniški seznam ali dosledno počistiti v finally.
- Dictionary hrani objekte kot vrednosti; pri Remove se vrednost ne sprosti ali se to pozabi pri Clear.
Dodatno problematični so vmesniki: štetje referenc (podobno ARC) je udobno, vendar mešana raba z lastništvom objektov lahko pri cikličnih referencah ali dogodkih povzroči puščanja. FullDebugMode ti pogosto pokaže sled alokacije, a vzrok je referenčni cikel (A drži B preko vmesnika, B drži A preko callbacka).
Izjeme in zgodnji izhodi
V zrelih poslovnih sistemih so izjeme pogosto del običajnega upravljanja (npr. validacija, prekinitev, retry). Težava redko izhaja iz same izjeme, temveč iz poti okoli nje: objekt je ustvarjen pred try/finally, nato pride izjema in čiščenje se preskoči. FullDebugMode ti dostavi stacktrace alokacije – in preveriti moraš, ali obstaja zagotovljena pot za sprostitev.
Vlakna in življenjska doba: „Sproščanje v napačni niti“
Pri VCL/FMX in servisih z worker-threadi se pojavi še en robni primer: objekt je ustvarjen v eni niti, a sproščen v UI-niti (ali obratno), ker se z Queue/Synchronize »na hitro« nekaj prenese. To lahko deluje, lahko pa vodi do Use-after-free, če proizvajalec nadaljuje z delom, medtem ko potrošnik že sprošča.
FastMM FullDebugMode lahko tu pomaga, ker zazna časovno zamaknjene napake prej. Pravi popravek pa je čist model življenjske dobe: jasna lastniška razmerja, prenos le preko nespremenljivih podatkov ali enoznačne točke prenosa lastništva.
Kako poročila uporabiti v praksi: filtriranje, primerjava, dokumentiranje
V ekipah se izplača obravnavati Leak-poročila ne le kot nekaj za »pogledati«, ampak kot artefakt. Tri pragmatične ukrepe, ki so se izkazali:
- Baseline-Report: »znano stanje« (npr. trenutna različica produkta) enkrat zaženeš z FullDebugMode in shraniš kot referenco. Tako takoj zaznaš nova puščanja.
- Vergleich nach Use-Case: Za kritične poteke dela (Import, Export, API-Request, UI-množične operacije) določiš kratko, ponovljivo sekvenco, ki jo redno izvajaš.
- Dokumentirana „legitimna“ puščanja: Če je predpomnilnik namensko nefinaliziran, to dokumentiraj. Sicer bo čez šest mesecev nekdo spet iskal iste vnose.
To ni birokracija, temveč prihranek časa: iskanje puščanj pomnilnika se sicer hitro spremeni v začaran krog, ker se isti vzorci v vsakem sprintu znova pojavljajo.
Kdaj se trud splača — in kdaj je bolje pristopiti drugače
FastMM FullDebugMode je diagnostično orodje s stroški. Trud se posebej splača, kadar:
- Aplikacija teče dolgo (storitev, odjemalec za terminalski strežnik, sistem izmenskega dela, 24/7-procesi).
- oBDEluješ dejanske podatkovne tokove strank in v testih ne pokriješ vseh poti.
- stabilnost je pomembnejša od kratkoročne hitrosti uvedbe funkcionalnosti (tipično za procesno orientirane programske rešitve).
Če imaš nasprotno le majhnega namiznega pomočnika, ki po približno 30 sekundah konča, je iskanje puščanj pogosto drugotnega pomena. Enako velja, če gre za enkraten vrh porabe pomnilnika (npr. velik izvoz): pogosto ne gre za puščanje, temveč za strategijo streamanja in vprašanje vršne obremenitve na Heap-u.
Praktični zaključek: FullDebugMode ni stikalo, ampak proces
FastMM FullDebugMode vnaša strukturo v iskanje napak v pomnilniku: naredi alokacije vidne, zazna korupcijo Heap-a prej in zagotavlja stacktraces, s katerimi lahko odpravljaš vzrok namesto simptoma. Ključni vzvod ni orodje, temveč potek dela: reproducibilni scenariji, buildi primerni za diagnostiko, jasne pogodbe o ownershipu in regresija proti referenčni osnovi.
Če se zatakneš pri trdovratnem puščanju ali občasni napaki Heap-a in želiš to temo trajno stabilizirati v večjem Delphi-sistemu, se izplača kratek, urejen diagnostični setup s jasno sekvenco in analizabilnimi poročili. Če pri tem potrebuješ podporo pri analizi, build-profilih ali arhitekturnem refaktoriranju: kontaktiraj Net-Base Software GmbH.
Za to temo so pomembni tudi iskanje puščanj pomnilnika v Delphi in branje poročila o puščanju FastMM. Prispevek te vidike smiselno umešča in pokaže, na kaj je v vsakodnevnem delu treba paziti.
Prediskutirajte projekt ali modernizacijski načrt z Net-Base.
naslednji korak
Ko iz teme nastane resničen projekt, je treba arhitekturo, obstoječe sisteme in obratovanje zgodaj obravnavati skupaj.
Ne podpiramo le pri posameznih vprašanjih, ampak tudi takrat, ko iz izrezkov izvorne kode, legacy-tem ali idej za portale nastane zanesljiv podjetniški projekt.
- Obstoječe stanje, ciljno stanje in tehnična tveganja se ocenjujejo skupaj.
- REST, dostop do podatkov, portali in Rollout ne bodo prestavljeni v kasnejše faze.
- Že zgodaj vidite, katera pot je ekonomsko in operativno vzdržna.