No žurnāla tēmas līdz projektu praksei
Atbilstošas pakalpojumu un tehniskās lapas rakstam
Ja eine Delphi-lietotne ekspluatācijas laikā pakāpeniski „uzpūšas“, sporādiski izkrīt ar Access Violations vai pēc dažu dienu darbības kļūst negaidīti nestabila, aiz tā bieži nenolaižas viens konkrēts bugs, bet gan raksts: atmiņa tiek pieprasīta, bet netiek korekti atbrīvota — vai arī tā tiek atbrīvota par ātru un vēlāk izmantota. Tieši šeit ir FastMM FullDebugMode zelta vērtībā. Ne kā pastāvīgs režīms, bet kā mērķēts diagnostikas rīks, kas no „kaut kur heapā kaut kas salūzis“ padara atsekojamu iemeslu.
Trūkums: FullDebugMode ģenerē daudz izvades, samazina veiktspēju un ātri noved pie nepareizas interpretācijas. Leak-Report neuzrāda automātiski vietu, kur ir „kļūda“. Un Stacktrace ir labs tik labi, cik laba ir simbolu atrisināšana (MAP-fails, debug-informācija, inlining). Šajā rakstā es izskatu tipisku malējā gadījuma scenāriju, izskaidroju sakārtotu pieeju un kritiskos punktus — tā, lai beigās tu ne tikai atrastu noplūdes, bet arī ilgstoši tās novērstu.
Kad FastMM FullDebugMode tiešām lietderīgs
FastMM mūsdienu Delphi versijās bieži jau ir noklusējuma atmiņas pārvaldnieks vai tiek iekļauts daudzos projektos. Tomēr FullDebugMode ir īpaša konfigurācija: tas papildina atmiņas blokus ar papildu pārbaudes paraugiem, apkopo alokācijas Stacktraces un agresīvāk pārbauda Heap-korupciju (t.i., bojāti pārvaldības dati heapā, piemēram, buffer-overrun gadījumā).
Es izmantoju FullDebugMode mērķtiecīgi, ja pastāv kāda no šīm situācijām:
- Reproducējama noplūde: atmiņas patēriņš testa laikā pieaug uz vienu operāciju (piem., uz request, uz importu, uz UI darbību).
- Sporādiski AV: īpaši tie, kas notiek „reiz šeit, reiz tur“ tajā pašā apgabalā (klasiski: use-after-free).
- Heap-korupcija: ziņojumi kā „Invalid pointer operation“, „Access violation in ntdll“ vai avārijas iziešanas/finalizācijas brīdī.
- Regresijas meklēšana: pēc refaktorēšanas, bibliotēkas atjaunināšanas vai kompilatora maiņas pēkšņi parādās jauna nestabilitāte.
Nav jēgas FullDebugMode izmantot kā „mēs to ieslēgsim visos buildos“. Overhead ir liels, izpildes laiks mainās, un tieši race conditions var izzust vai pārvietoties. Ilgtermiņa darbībai vairāk piemērots viegls monitoring (piem., procesa Working Set, Private Bytes, skaitītāji uz operāciju) — FullDebugMode ir skalpelis, ne pulsa sensors.
Pamatprincip: Leak-Report ir simptoms, Stacktrace ir norāde
Leak-Report vispirms parāda: šie bloki programmbeigu brīdī joprojām ir allokēti. Tas ir problēma tikai tad, ja šie bloki faktiski vajadzēja tikt atbrīvoti. Ir leģitīmas „noplūdes“: globāli singletoni, keši, OS handle ar procesa dzīves ilgumu vai third-party bibliotēkas, kas apzināti neveic finalizāciju. Šādus gadījumus tu gribi zināt, bet ne akli „salabot“.
Stacktrace ziņojumā rāda vietu, kur bloks tika pieprasīts. Tas bieži nav vieta, kur tu esi „aizmirsis Free“. Bieža realitāte pieaugušās sistēmās:
- Allokācija UI vai servisa slānī, atbrīvošana būtu jāveic zemākā slānī (ownership nav skaidrs).
- Allokācija fabrikā (Factory), ownership tiek nodots callerim — bet caller domā, ka tas ir „owned“.
- Objekti tiek glabāti kolekcijās (listes, dictionaries), bet ownership-modelis nav konsekvents.
- Izņēmuma ceļš izlaiž cleanup, jo trūkst try/finally bloka vai tas sākas par vēlu.
Tāpēc skaidra darba gaita ir: reproducēt → izolēt → Stacktrace atšifrēt → atrast Ownership kļūdu → labojums ar regresijas testu. FastMM sniedz tev pēdas, bet tās ir jāpārvērš arhitektūrā un dzīves ciklos.
FastMM FullDebugMode rūpīga aktivēšana (nepalaidot garām blakusparādības)
FullDebugMode praksē tiek aktivēts caur FastMM‑opcijām un atbilstošu FastMM konfigurāciju. Izšķiroši nav tas, “kā tieši saucas include-fails”, bet ko konfigurācija rada un kādās būves‑nosacībās tu to izmanto.
Ieteicamie nosacījumi debug‑buildam
- Debug DCUs und Debug-Infos: Stacktraces ir noderīgi tikai tad, ja tos var atrisināt līdz īstajai vienībai/rindai/adresei. Pārliecinies, ka tiek ģenerēta debug‑informācija un ka MAP fails ir pieejams.
- Izvēlies optimizāciju apzināti: Stacktrace lasāmībai parasti labāks ir neoptimizēts build. Inlining un agresīva optimizācija var „izpludināt“ stackframe’us.
- Vienādi izpildes apstākļi: Izmanto pēc iespējas tos pašus datus, vienādu konfigurāciju, vienādas tiesības. Daudzas atmiņas noplūdes ir datu atkarīgas (piem., reti formāti, speciālas ceļas).
- Atsevišķi 64-bit un 32-bit: Atmiņas uzvedība, izlīdzināšana un trešo pušu bibliotēkas atšķiras. Debugē mērķplatformā, kur parādās problēma.
Viena lieta, ko administratori un tehniskie vadītāji bieži nenovērtē pietiekami: FullDebugMode var arī mainīt izpildes laika uzvedību. Ja izmanto threading, Race‑Conditions var izpausties citādi. Tāpēc ir jēga paralēli veikt arī vienu palaidi bez FullDebugMode, kas tikai apstiprina reproducēšanu. FullDebugMode ir tad diagnozes solis.
Uzmanība ar „ReportMemoryLeaksOnShutdown“
Delphi var ziņot par noplūdēm programmas beigās, izmantojot ReportMemoryLeaksOnShutdown. Tas ir ērti, taču sarežģītās lietojumprogrammās (servisi, Plug-in Host, ilgstoša darbība) tas var maldināt: izslēgšanas laikā tiek izpildīti finalizācijas bloki, threadi apstājas, keši tiek iztīrīti. Noplūde, kas vidū ir kritiska, līdz beigām var pazust — vai pretēji: it kā noplūde rodas tikai izslēgšanas laikā, jo vēl notiek fona darbs.
Praktiskai noplūžu meklēšanai tāpēc būtiskāk ir: mērīt noplūdi uz vienu operāciju (piem., pēc 100 pieprasījumiem), ne tikai pie izbeigšanas. FastMM var šajā palīdzēt, bet testa uzstādījumam tas ir jāatspoguļo.
Tipisks maluzņēmums: Leak‑ziņojums rāda „jebkuru objektu“, bet cēlonis ir Ownership
Klasisks piemērs uzņēmumu lietojumprogrammās: importēšanas process ģenerē katram ierakstam palīgobjektus (piem., StringLists, JSON-parserus, pagaidu sarakstus). Laimīgā ceļā tie tiek pareizi atbrīvoti. Retos gadījumos (validācijas dēļ izlaists ieraksts, izņēmums, agrīna izeja) objekts paliek bloķēts. Pēc 10 000 ierakstiem tas kļūst redzams.
FastMM FullDebugMode šeit palīdz, jo rāda alokācijas vietu. Taču “fix” nav “free tajā pašā vietā, kur notika alokācija”. Risinājums ir robusts Ownership-Pattern:
- Tas, kas objektu izveido, nav automātiski Owner.
- Ownership jābūt skaidrai API līgumā (Parameter/Return, dokumentācija, nosaukumu konvencijas).
- Kolekcijām jābūt viennozīmīgām: owning vs. non-owning. Jauktas pieejas noved pie problēmām.
- Izņēmumu ceļiem vajadzīgi agrie try/finally bloki.
Ja no stacktrace tu redzi tikai „TStringList.Create“, informācija nav bezvērtīga — bet tā tev tikai saka: šeit kaut kas tiek izveidots. Jautājums ir: kur tam vajadzētu beigties? Un šeit arhitektūras domāšana palīdz vairāk nekā debugger-akrobātika.
Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst
Stacktrace no FastMM parasti ir atgriešanās adresu saraksts, kas — ar debug simboliem — kartējas uz Units, procedūrām un, ideālā gadījumā, rindas numuriem. Lasot to, trīs lietas ir izšķirošas:
- Top-of-Stack nav vienmēr kļūda: augstākie freimi bieži ir Memory-Manager/RTL. Interesanti paliek tur, kur sākas tavs kods.
- Call-Chain statt Einzelzeile: viena rinda ir tikai punkts. Ķēde parāda, kurš ceļš noveda pie alokācijas.
- Mehrere identische Blöcke: ja FastMM ziņo par vairākām atmiņas noplūdēm vienāda izmēra, tas bieži norāda uz atkārtojošu ceļu. Tas ir labs signāls: tev ir reproducējamība.
Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs
Daudzas komandas uz šo paklūp: FullDebugMode ir ieslēgts, leak-ziņojums ienāk, bet vietā, lai redzētu Unit/Zeile, ir tikai adreses vai kriptiski simboli. Tipiskie cēloņi:
- Nav MAP-faila vai debug-informācija netika ģenerēta.
- Tiek izmantotas Release-DCUs vai trešo pušu DLL bez simboliem.
- Lietojumprogramma izmanto Runtime Packages: tad koda daļas atrodas BPL, un simbolu atrisināšanai tas jāpielepo.
- Optimizācija/Inlining ir padarījusi stacktrace grūtāk izlasāmu.
Praksē tas nozīmē: noplūžu medībām tev nepieciešams build, kas apzināti ir “diagnosefähig”. Tas ir cita veida mērķis nekā “möglichst schnell”. Tehniskajiem līderiem tas jāuztver kā atsevišķs build-profil, lai ne katrs komandas loceklis ad hoc mainītu projekta opcijas.
Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben
Praktisks piemērs no dzīves (bez konkrēta klienta koda): Der Stacktrace rāda pirmajā rāmī jūsu kodā rutīnu „LoadConfig“. Tur redzat objekta izveidi. Pievienojat Free, noplūde pazūd – un pēkšņi citur notiek Double Free. Kāpēc? Tāpēc, ka „LoadConfig“ ievieto objektu kešā, un cits koda ceļš jau ir objekta īpašnieks un vēlāk to atbrīvo.
Pareizā interpretācija būtu bijusi: Der Stacktrace rāda dir, kur bloks tiek izveidots. Labojums bieži slēpjas definīcijā: Kas pieder objektam pēc return? Ja šo jautājumu neizskaidro skaidri, tu tikai maini kļūdas simptomu (noplūde → AV).
Heap-Korruption vs. Leak: Warum FullDebugMode oft den echten Übeltäter findet
Daudzas „noplūdes“ patiesībā ir seku problēmas: bufera pārplūde (Buffer-Overrun) pārraksta heap metadatus, atmiņas pārvaldnieks vēlāk nevar tos tīri atbrīvot, un beigās tu redzi it kā nejaušas noplūdes vai Invalid Pointer Operations. FullDebugMode šeit ir efektīvs, jo tas strādā ar pārbaudes paraugiem un pie Free/Reuse veic papildus validācijas.
Svarīga ir atšķiršana:
- Noplūde: bloks tika alocēts un nekad netika atbrīvots. Stabilitāte laika gaitā pasliktinās, avārija nav obligāta.
- Use-after-free: bloks tiek atbrīvots, bet vēlāk joprojām tiek izmantots. Izsauc sporādiskas AV, kuras ir grūti reproducēt.
- Double Free: bloks tiek atbrīvots divreiz. Var uzreiz radīt avāriju vai tikai vēlāk (piemēram, kad bloks ir atkārtoti izmantots).
- Heap-Korruption: kāds raksta pāri bloka robežām. Simptomi bieži parādās ar laika nobīdi.
FullDebugMode ir īpaši vērtīgs, ja simptomi parādās ar laika nobīdi. Papildu validācija padara kļūdas redzamas agrāk – bieži tieši tur, kur notiek nepareizais piekļuves izmēģinājums, nevis minūtes vēlāk kādā nejaušā Free.
Vorgehen in Projekten: Reproduzierbare Leak-Jagd statt „Debugging im Nebel“
Ja vēlies medīt atmiņas noplūdes, tev nepieciešams process, kas ir atkārtojams un ko var koplietot komandā. Es parasti strādāju ar fiksētu diagnozes ietvaru:
1) Reproduktion in einem deterministischen Szenario
Definē testu secību, kas uzticami parāda noplūdi: „Startē Service, apstrādā 500 ziņojumus, stopē Service“ vai „Atver formu X, veic darbību Y 200 reizes“. Svarīgi dokumentēt secību ar parametriem (datu kopa, mandants, feature‑flagi), lai citi varētu to reproducēt.
2) Minimieren: Leak pro Schritt sichtbar machen
Ja secība ilgst 20 minūtes, sadali to. Mērķis ir iespējami ātri salīdzināt „pirms“ un „pēc“. Lielās lietojumprogrammās tas bieži ir pats laika patērētājs, nevis labojums.
3) FullDebugMode einschalten und Report interpretieren
Tagad spēlē iejaucas FastMM FullDebugMode. Savāc ziņojumus, grupē pēc bloka lieluma/Callstack un skaties pēc atkārtojumiem. Viens atlikušo bloks var būt leģitīms kešs. 10 000 identiski bloki gandrīz vienmēr norāda uz reālu noplūdi.
4) Ownership-Klārēšana un labojums atbilstošajā slānī
Novērs noplūdes tur, kur definēta Ownership: Factory, API-Vertrag, Collection-Wrapper. „Schnell ein Free einbauen“ tieši blakus Create bieži ir nepareizā vieta, ja objekts tiek nodots tālāk.
5) Regression: vienāda secība, vienāds Build, vienāds ziņojums
Labojums ir labs tikai tad, kad secība atkal darbojas un nerodas ne noplūdes, ne jaunas atmiņas kļūdas. Jo īpaši Use-after-free gadījumā „noplūde pazudusi“ nav pierādījums, bet tikai jauns simptoms.
Typische Fallstricke in Delphi-Code, die FastMM sichtbar macht
Collections und Ownership (Listen, Dictionaries, Interfaces)
Daudzas noplūdes neizriet no sarežģītiem algoritmiem, bet no ikdienišķām datu struktūrām. Divi klasiskie kļūdu veidi:
- Saraksts satur objektus, bet neviens nezina, kurš tos atbrīvo. Risinājums: izmantot owning Liste vai konsekventi atbrīvot finally blokā.
- Dictionary tur objektus kā Values; pie Remove Value netiek atbrīvots vai Clear to aizmirst.
Papildus sarežģīti ir Interfaces: referenču skaitīšana (līdzīgi ARC) ir ērta, bet jauktais režīms ar objektu Ownership var radīt noplūdes pie cikliskām referencēm vai notikumiem. FullDebugMode bieži rāda alokācijas ceļu, bet cēlonis ir referenču cikls (A tur B caur Interface, B tur A caur Callback).
Exceptions und frühe Exits
Attīstītās biznesa programmatūras sistēmās Exceptions bieži ir daļa no parastās vadības (piem., validācija, pārtraukšana, retry). Problēma reti ir pati Exception, bet izpildes ceļš apkārt tai: objekts tiek izveidots pirms try/finally, tad parādās Exception, un Cleanup tiek izlaists. FullDebugMode sniedz alokācijas Stacktrace — un tev jānoskaidro, vai pastāv garantēts izpildāms atbrīvošanas ceļš.
Threads und Lebenszeit: „Freigeben im falschen Thread“
VCL/FMX un servisos ar Worker-Threads rodas vēl viens malas gadījums: objekts tiek izveidots vienā Thread, bet atbrīvots UI-Thread (vai otrādi), jo ar Queue/Synchronize tiek „tikai ātri“ kaut kas pārnests. Tas var darboties, bet var arī izraisīt Use-after-free, ja Producer turpina darbu, kamēr Consumer jau atbrīvo.
FastMM FullDebugMode šeit var palīdzēt, jo tas agrāk atklāj laika nobīdes kļūdas. Taču patiesais labojums ir skaidrs dzīves ilguma modelis: noteiktas īpašumtiesības, nodošana tikai caur immutable datiem vai viennozīmīgi Ownership-transfer punkti.
Kā padarīt ziņojumus lietojamus: filtrēšana, salīdzināšana, dokumentēšana
Komandās ir vērts noplūdes ziņojumus ne tikai apskatīt, bet izturēties pret tiem kā pret artefaktu. Trīs pragmatiski pasākumi, kas sevi ir pierādījuši:
- Baseline-Report: „zināms stāvoklis“ (piem., pašreizējā produkta versija) tiek vienreiz palaists ar FullDebugMode un saglabāts kā atsauce. Tā tu uzreiz pamanīsi jaunas noplūdes.
- Vergleich nach Use-Case: kritiskiem darba plūsmām (Import, Export, API-Request, UI masveida operācija) nosaki īsu, regulāri atkārtojamu sekvenci.
- Dokumentierte „legitime Leaks“: ja kešs apzināti netiek finalizēts, dokumentē to. Citādi pēc sešiem mēnešiem kāds atkal meklēs tos pašus ierakstus.
Tā nav birokrātija, bet laika ietaupījums: noplūdes meklēšana citādi ātri kļūst par bezgalīgu cilpu, jo tie paši modeļi katrā sprintā atkārtojas.
Kad piepūle atmaksājas – un kad jārīkojas citādi
FastMM FullDebugMode ir diagnostikas rīks ar izmaksām. Piepūle īpaši atmaksājas, ja:
- lietotne darbojas ilgi (serviss, termināla servera klients, maiņu sistēma, 24/7 procesi).
- tu apstrādā reālas klientu datu plūsmas un neaptver visus ceļus testos.
- stabilitāte ir svarīgāka par īstermiņa funkciju piegādes ātrumu (tipiski ar procesiem saistītiem programmatūras risinājumiem).
Ja tev savukārt ir tikai neliels darbvirsmas palīgs, kas beidzas pēc 30 sekundēm, noplūdes meklēšana bieži vien ir sekundāra. Tāpat: ja rodas vienreizējs atmiņas pieauguma spikes (piem., liels eksports), bieži vien tas nav noplūde, bet jautājums par straumēšanas stratēģiju un virsotņu slodzi heapā.
Praktiskais secinājums: FullDebugMode nav slēdzis, bet process
FastMM FullDebugMode ievieš struktūru atmiņas kļūdu meklēšanā: tas padara alokācijas redzamas, agrāk atklāj heap korupciju un nodrošina izsaukumu steku izsekojumus (stacktraces), ar kuriem tu vari labot cēloni, nevis simptomu. Tomēr izšķirošais sviras punkts nav rīks, bet darba kārtība: reproducējami scenāriji, diagnostikai piemēroti buildi, skaidri īpašumtiesību līgumi un regresijas pārbaude pret bāzstāvokli.
Ja tu esi iestrēdzis pie noturīgas noplūdes vai sporādiskas heap kļūdas un vēlies šo jautājumu ilgtspējīgi stabilizēt lielākā Delphi-sistēmā, tad lietderīgs būs īss, kārtīgs diagnostikas uzstādījums ar skaidru secību un analizējamiem ziņojumiem. Ja tev nepieciešama atbalsta analīzē, build profilos vai arhitektūras refaktorēšanā: sazinies ar Net-Base Software GmbH.
Šajā tēmā svarīgas ir arī Delphi atmiņas noplūžu atrašana un Fastmm noplūdes atskaites lasīšana. Raksts saprotami sakārto šos aspektus un parāda, uz ko ikdienā jāpievērš uzmanība.
Nākamais solis
Ja no tēmas rodas reāls projekts, arhitektūru, esošo sistēmu un ekspluatāciju jāvērtē kopā jau agrīnā posmā.
Mēs atbalstām ne tikai atsevišķu jautājumu risināšanā, bet arī tad, kad no avota koda fragmentiem, mantojuma sistēmu jautājumiem vai portāla idejām jāizveido stabils uzņēmuma līmeņa projekts.
- Esošais stāvoklis, mērķa stāvoklis un tehniskie riski tiek kopīgi vērtēti.
- REST, datu piekļuve, portāli un Rollout netiek pārcelti uz vēlākām fāzēm.
- Jūs laikus redzat, kurš risinājums ir ekonomiski un darbības ziņā dzīvotspējīgs.