De la tema din revistă la practica în proiecte
Pagini relevante de servicii și pagini tehnice pentru articol
Când o Delphi-aplicație se „umflă” lent în producție, iese sporadic cu Access Violations sau devine brusc instabilă după câteva zile de rulare, de regulă nu e vorba de un singur bug, ci de un tipar: memoria este alocată, dar nu eliberată curat — sau este eliberată prea devreme și folosită ulterior. Exact aici FastMM FullDebugMode valorează aur. Nu ca stare permanentă, ci ca unealtă de diagnostic țintită, care transformă „undeva în heap ceva e stricat” într-o cauză urmărită și reproductibilă.
Problema: FullDebugMode produce mult output, reduce performanța și conduce ușor la interpretări greșite. Un Leak-Report nu arată automat locul în care „este eroarea”. Iar un Stacktrace e atât de valoros cât de bună este rezolvarea simbolurilor (fișier MAP, informații de debug, inlining). În această contribuție parcurg cazul tipic, explic abordarea corectă și capcanele — astfel încât la final să nu găsești doar scurgerile, ci să le elimini durabil.
Când FastMM FullDebugMode este cu adevărat util
FastMM este în versiunile moderne de Delphi frecvent deja memory manager-ul implicit sau este integrat în multe proiecte. FullDebugMode este însă o configurare specială: marchează blocurile de memorie cu tipare suplimentare de verificare, colectează stacktrace-uri ale alocărilor și verifică mai agresiv corupția heap-ului (adică datele de administrare ale heap-ului corupte, p.ex. din cauza buffer-overrun-urilor).
Folosesc FullDebugMode în mod țintit când apare unul dintre aceste scenarii:
- Leak reproducibil: consumul de memorie crește în timpul unui test per operațiune (de ex. per Request, per Import, per acțiune UI).
- AV-uri sporadice: în special cele care apar „uneori aici, alteori acolo” în aceeași regiune (clasic: Use-after-free).
- Corupție de Heap: mesaje precum „Invalid pointer operation”, „Access violation in ntdll” sau crash-uri la închidere/finalizare.
- Căutare de regresii: după refactoring, actualizare de bibliotecă sau schimbare de compilator apare brusc instabilitate nouă.
FullDebugMode nu este potrivit ca „să-l activăm în toate build-urile”. Suprasarcina este mare, timing-ul se modifică și tocmai race-condition-urile pot dispărea sau se pot muta. Pentru funcționare permanentă e mai potrivit un monitoring restrâns (de ex. working set-ul procesului, Private Bytes, contoare per operațiune) — FullDebugMode este bisturiul, nu senzorul de puls.
Principiu de bază: Leak-Report e simptom, Stacktrace e urme
Un Leak-Report îți arată întâi: aceste blocuri sunt încă alocate la terminarea programului. Asta e problematic doar dacă aceste blocuri ar fi trebuit de fapt eliberate. Există „leaks” legitime: singletoane globale, cache-uri, OS-handles cu durata de viață a procesului sau biblioteci third-party care intenționat nu finalizează. Aceste cazuri trebuie cunoscute, dar nu corectate orbeste.
Stacktrace-ul din raport arată locul în care blocul a fost alocat. De cele mai multe ori acesta nu e locul în care ai „uitat să eliberezi”. Realitatea frecventă în sisteme mature:
- Alocare în stratul UI sau Service, eliberarea ar trebui să se întâmple într-un strat mai profund (ownership neclar).
- Alocare într-o Factory, ownership-ul este predat caller-ului — dar caller-ul crede că e „owned”.
- Obiectele sunt ținute în colecții (liste, dictionaries), dar modelul de ownership nu este consecvent.
- Un traseu de excepție sare peste cleanup pentru că lipsește try/finally sau începe prea târziu.
Fluxul curat este deci: reproduzieren → isolieren → Stacktrace auflösen → Ownership-Fehler finden → Fix mit Regressionstest. FastMM îți livrează urmele, dar tu trebuie să le traduci în arhitectură și cicluri de viață.
FastMM FullDebugMode sauber aktivieren (ohne Nebenwirkungen zu übersehen)
FullDebugMode este în practică activat prin opțiunile FastMM și o configurație FastMM corespunzătoare. Decisiv nu este atât „cum se numește exact fișierul include”, cât ce face configurația și în ce condiții de build o folosești.
Empfohlene Rahmenbedingungen für den Debug-Build
- Debug DCUs und Debug-Infos: Stacktrace-urile sunt utile doar dacă pot fi rezolvate la unitate/linie/adresă reale. Asigură-te că se generează informații de debug și că este disponibil un fișier MAP.
- Optimierung bewusst wählen: Pentru lizibilitatea stacktrace-urilor, un build neoptimizat este de obicei mai bun. Inlining-ul și optimizările agresive pot „estompa“ cadrele din stivă.
- Gleiche Laufzeitbedingungen: Folosește, pe cât posibil, aceleași date, aceeași configurație, aceleași drepturi. Multe scurgeri de memorie depind de date (de ex. formate rare, căi speciale).
- 64-bit vs. 32-bit trennen: Comportamentul memoriei, alinierea și bibliotecile terțe sunt diferite. Debugează pe platforma țintă pe care apare problema.
Un aspect pe care adminii și liderii tehnici îl subestimează adesea: FullDebugMode poate modifica și timing-ul. Dacă ai threading în joc, condițiile de concurență pot apărea diferit. De aceea e util să ai paralel și o rulare fără FullDebugMode care doar confirmă reproducerea. FullDebugMode este apoi pasul pentru diagnostic.
Vorsicht bei „ReportMemoryLeaksOnShutdown“
Delphi kann über ReportMemoryLeaksOnShutdown Leaks beim Programmende melden. Das ist praktisch, aber in komplexen Anwendungen (Services, Plug-in Host, lange Laufzeiten) kann es trügen: Beim Shutdown laufen Finalization-Abschnitte, Threads stoppen, Caches werden weggeräumt. Ein Leak, das in der Mitte der Laufzeit kritisch ist, kann bis zum Ende wieder verschwinden – oder umgekehrt: ein scheinbarer Leak entsteht erst im Shutdown, weil noch Hintergrundarbeit läuft.
Für praxistaugliches Leak-Hunting ist daher wichtiger: Leak pro Vorgang messen (z. B. nach 100 Requests), nicht nur beim Beenden. FastMM kann dabei helfen, aber der Testaufbau muss das abbilden.
Der typische Randfall: Leak-Report zeigt „irgendein Objekt“, aber die Ursache ist Ownership
Un clasic în aplicațiile enterprise: un proces de import creează pentru fiecare înregistrare obiecte auxiliare (de ex. StringLists, JSON-Parser, liste temporare). Pe Happy Path sunt eliberate corect. În cazuri rare (skip din cauza validării, excepție, exit prematur) un obiect rămâne blocat. După 10.000 de înregistrări devine vizibil.
FastMM FullDebugMode ajută aici, pentru că arată locul alocării. Dar „Fix“-ul nu este un „free la locul alocării“. Fix-ul este un robust Ownership-Pattern:
- Cine creează un obiect nu este automat owner.
- Ownership trebuie să fie clar în contractul API (parametrii/return, documentație, convenții de denumire).
- Colecțiile trebuie să fie neechivoce: owning vs. non-owning. Formele mixte se răzbună.
- Căile de excepție necesită blocuri try/finally plasate timpuriu.
Dacă din stacktrace vezi doar „TStringList.Create“, informația nu e inutilă – dar îți spune doar: aici se creează ceva. Întrebarea este: unde ar trebui să se termine? Și aici gândirea de arhitectură ajută mai mult decât acrobatiile de debugger.
Citirea corectă a stacktrace-urilor: Ce poți deduce cu adevărat
Un stacktrace generat de FastMM este, în general, o listă de adrese de revenire care – cu simboluri de debug – se pot mapa la unități, proceduri și ideal la numere de linie. Când citești asta, trei lucruri sunt decisive:
- Top-of-Stack nu este întotdeauna cauza erorii: frame-urile de sus sunt adesea ale memory manager-ului/RTL. Devine interesant acolo unde începe codul tău.
- Call-Chain în loc de linia izolată: linia e doar un punct. Lanțul îți arată ce cale a dus la alocare.
- Mai multe blocuri identice: dacă FastMM raportează mai multe leak-uri de aceeași dimensiune, este adesea un traseu recurent. Asta e bine: ai reproductibilitate.
Când lipsesc numerele de linie: MAP-Datei, Packages, Release-DCUs
Multe echipe se împiedică aici: FullDebugMode e activ, raportul de leak apare, dar în loc de Unit/linie vezi doar adrese sau simboluri criptice. Cauze tipice:
- Nu a fost generat fișier MAP sau informațiile de debug nu au fost create.
- Rulezi împotriva Release-DCUs sau DLL-urilor terțe fără simboluri.
- Aplicația folosește Runtime Packages: atunci părți din cod se află în BPL-uri, iar rezoluția simbolurilor trebuie configurată corespunzător.
- Optimizarea/Inlining a făcut stacktrace-ul mai greu de citit.
În practică asta înseamnă: pentru vânătoarea de leak-uri ai nevoie de un build care să fie deliberat „diagnosticabil“. Acesta este un obiectiv diferit de „cât mai rapid“. Lead-ii tehnici ar trebui să trateze asta ca un profil de build separat, pentru ca nu fiecare membru al echipei să modifice ad-hoc opțiunile proiectului.
Evaluarea frame-urilor: „Interessant“ este adesea o linie mai sus
Un exemplu din practică (fără cod concret al unui client): Stacktrace-ul îți arată ca prim frame în codul tău o rutină „LoadConfig”. Vezi acolo o creare de obiect. Adaugi un Free, leak-ul dispare – și dintr‑odată se produce un crash în altă parte din cauza unui Double Free. De ce? Pentru că „LoadConfig” plasează obiectul într‑un cache, iar un alt traseu de cod este deja owner și curăță mai târziu.
Interpretarea corectă ar fi fost: Stacktrace-ul îți spune unde apare blocul. Fixul se află adesea în definiție: Cine deține obiectul după return? Dacă nu răspunzi clar la această întrebare, schimbi doar tabloul erorii (Leak → AV).
Coruperea heap-ului vs. Leak: De ce FullDebugMode găsește adesea adevăratul vinovat
Multe „Leaks” sunt, în realitate, probleme secundare: un buffer-overrun suprascrie metadatele heap-ului, managerul de memorie nu poate elibera corect ulterior, iar la final vezi aparent leak-uri aleatorii sau operații cu pointer invalid. FullDebugMode e eficient aici pentru că folosește pattern-uri de verificare și face validări suplimentare la Free/Reuse.
Importantă este distincția:
- Leak: blocul a fost alocat și niciodată eliberat. Stabilitatea degradează în timp; crash‑ul nu este obligatoriu.
- Use-after-free: blocul este eliberat, dar apoi folosit în continuare. Conduce la AV-uri sporadice, greu de reprodus.
- Double Free: același bloc este eliberat de două ori. Poate crasha imediat sau mai târziu (dacă blocul a fost reutilizat între timp).
- Heap-Korruption: cineva scrie dincolo de marginile unui bloc. Simptomele apar adesea cu întârziere.
FullDebugMode este deosebit de valoros când simptomele apar cu întârziere. Validările adiționale fac erorile vizibile mai devreme – adesea chiar la punctul în care are loc accesul greșit, nu abia minute mai târziu la o operație Free oarecare.
Abordare în proiecte: vânătoare reproducibilă de Leak în loc de „debugging în ceață”
Dacă vrei să vânezi scurgeri de memorie, ai nevoie de un flux de lucru repetabil și partajabil în echipă. Lucrez de regulă cu un cadru de diagnostic fix:
1) Reproducere într‑un scenariu determinist
Definește o secvență de test care expune leak-ul în mod fiabil: „Pornește serviciul, procesează 500 de mesaje, oprește serviciul” sau „Deschide interfața X, execută acțiunea Y de 200 de ori”. E important să documentezi secvența cu parametrii (set de date, client/tenant, feature flags), astfel încât alții să o poată reproduce.
2) Minimizați: faceți leak‑ul vizibil pas cu pas
Dacă secvența durează 20 de minute, sparge‑o. Obiectivul: să poți compara „înainte” și „după” cât mai rapid. În aplicații mari, asta e adesea cel mai consumator de timp, nu corectarea propriu‑zisă.
3) Activează FullDebugMode și interpretează raportul
Abia acum intervine FastMM FullDebugMode. Colectează rapoartele, grupează după dimensiunea blocului/callstack și caută repetiții. Un singur bloc rămas poate fi un cache legitim. 10.000 de blocuri identice sunt aproape întotdeauna o scurgere reală.
4) Clarificarea ownership-ului și fix-ul în stratul potrivit
Remediază leak-urile acolo unde se definește ownership-ul: Factory, contractul API, Collection-Wrapper. „Schnell ein Free einbauen“ direct lângă Create este adesea locul greșit dacă obiectul este transmis mai departe.
5) Regression: aceeași Sequenz, același Build, același Report
Remedierea este bună doar când secvența rulează din nou și nu apar nici leak-uri, nici erori noi de memorie. Mai ales în cazul Use-after-free un „Leak weg“ nu este o dovadă, ci doar un nou simptom.
Capcane tipice în codul Delphi, pe care FastMM le face vizibile
Colecții și Ownership (liste, dicționare, interfețe)
Multe leak-uri nu provin din algoritmi complicați, ci din structuri de date uzuale. Două tipare clasice de eroare:
- O listă conține obiecte, dar nimeni nu știe cine le eliberează. Soluție: folosește o listă owning sau golește-o consecvent în finally.
- Un dicționar păstrează obiecte ca valori; la Remove valoarea nu este eliberată sau este uitată la Clear.
Sunt de asemenea problematice interfețele: numărarea referințelor (similară ARC) e comodă, dar operarea mixtă cu ownership-ul obiectelor poate genera leak-uri în prezența referințelor ciclice sau a evenimentelor. FullDebugMode îți arată adesea calea de alocare, dar cauza este un ciclu de referințe (A ține B prin interfață, B ține A prin callback).
Excepții și ieșiri timpurii
În sisteme software de business mature, excepțiile fac adesea parte din fluxul normal de control (de ex. validare, anulare, retry). Problema rar e excepția în sine, ci drumul din jur: un obiect este creat înainte de try/finally, apoi apare o excepție și cleanup-ul este sărit. FullDebugMode îți oferă stacktrace-ul alocării — și trebuie să verifici dacă există un parcurs de eliberare garantat executat.
Threads și durata de viață: „Eliberare în thread-ul greșit“
La VCL/FMX și servicii cu worker-threads apare un alt caz limită: un obiect este creat într-un thread, dar eliberat în UI-thread (sau invers), pentru că se trece „doar repede“ ceva prin Queue/Synchronize. Asta poate funcționa, dar poate duce la Use-after-free dacă producer-ul continuă lucrul în timp ce consumer-ul a eliberat deja.
FastMM FullDebugMode poate ajuta aici, deoarece detectează mai devreme erorile întârziate. Remedierea reală este însă un model curat de durată de viață: raporturi de proprietate clare, transfer doar prin date imuabile sau puncte clare de transfer al ownership-ului.
Cum să folosești rapoartele: filtrare, comparare, documentare
În echipe merită să tratezi rapoartele de leak nu doar ca „a le privi“, ci ca pe un artefact. Trei măsuri pragmatice care s-au dovedit eficiente:
- Baseline-Report: Un „stare cunoscută“ (de ex. versiunea curentă a produsului) este parcursă o dată cu FullDebugMode și salvată ca referință. Astfel recunoști imediat leak-urile noi.
- Comparare după Use-Case: Pentru fluxuri critice (Import, Export, API-Request, operațiuni în masă în UI) stabilești pentru fiecare o secvență scurtă care poate fi repetată regulat.
- Leak-uri „legitime“ documentate: Dacă un cache nu este finalizat intenționat, documentează asta. Altfel, peste șase luni cineva va urmări din nou aceleași intrări.
Aceasta nu este birocrație, ci o economisire de timp: vânătoarea de scurgeri devine altfel rapid un ciclu fără sfârșit, deoarece aceleași tipare apar din nou în fiecare sprint.
Când merită efortul – și când ar trebui să procedezi altfel
FastMM FullDebugMode este un instrument de diagnosticare care implică costuri. Efortul merită în special când:
- Aplicația rulează pe termen lung (serviciu, Terminalserver-Client, sistem pe ture, procese 24/7).
- Procesezi fluxuri reale de date ale clienților și nu acoperi toate căile în test.
- Stabilitatea este mai importantă decât viteza de livrare a funcționalităților pe termen scurt (tipic în soluții software apropiate de proces).
Dacă ai, în schimb, doar un mic helper de desktop care se încheie după 30 de secunde, vânătoarea de scurgeri este adesea secundară. De asemenea: dacă ai un vârf de memorie izolat (de ex. un export mare), este frecvent nu o scurgere, ci o problemă de strategie de streaming și de sarcină de vârf în heap.
Concluzie practică: FullDebugMode nu este un comutator, ci un proces
FastMM FullDebugMode aduce structură în căutarea erorilor de memorie: face alocările vizibile, detectează corupția heap-ului mai devreme și furnizează stacktraces care îți permit să remediezi cauza, nu simptomul. Pârghia decisivă nu este însă instrumentul, ci procedura: scenarii reproductibile, build-uri capabile de diagnostic, contracte clare de ownership și regresie față de o baseline.
Dacă ești blocat de o scurgere persistentă sau de erori sporadice de heap și vrei să stabilizezi subiectul în mod durabil într-un sistem Delphi mai mare, merită un setup de diagnostic scurt și curat, cu o secvență clară și rapoarte analizabile. Dacă ai nevoie de asistență pentru analiză, profile de build sau refactorizare arhitecturală: contact la Net-Base Software GmbH.
Pentru acest subiect sunt importante și Delphi Găsirea scurgerilor de memorie și Citirea Fastmm Leak Report. Articolul ordonează aceste aspecte într-un mod accesibil și arată ce contează în practică.
Discută un proiect sau o inițiativă de modernizare cu Net-Base.
Pasul următor
Dacă un subiect devine un proiect real, arhitectura, starea existentă și operarea ar trebui analizate împreună încă din faza incipientă.
Nu oferim sprijin doar pentru întrebări punctuale, ci și atunci când fragmente de cod sursă, probleme legacy sau idei de portal trebuie transformate într-un proiect robust la nivel de companie.
- Situația curentă, starea țintă și riscurile tehnice sunt evaluate împreună.
- REST, accesul la date, portalurile și implementarea nu sunt amânate pentru etape ulterioare.
- Veți vedea din timp care opțiune este viabilă din punct de vedere economic și operațional.