Memory, Allocations and Session Leaks
Both halves of the memory problem are fixed and measured:
- Retention. Fifty open/close rounds of the ticket detail form used to retain +76 MB and leave 50 handlers on a static event. They now retain +1.1 MB and leave 0.
- Allocation. The grid rows are projected into
TicketRowonce per search instead of being rebuilt on every redraw.
Evidence: MemoryEvidence.md · DisposalChecklist.md ·
SessionMemoryBudget.md
Run it
cd "LearnWisej-Samples/Performance and Profiling Course/Module 4/WisejPerfLab"
dotnet run -c Release -f net10.0 --urls http://localhost:5804
For the before reading, run Module 3 on 5803 and use the same three buttons there — that folder carries the same measuring instruments and the unfixed form.
What to click
| Action | What you should see |
|---|---|
| Memory snapshot A | snapshot A — heap 23.9 MB bus subscribers 0 forms created 0 / disposed 0 |
| Open and close the detail form ×50 | one Session/OpenCloseDetail rows=50 record |
| Snapshot B and compare | B - A: heap +1.1 MB subscribers 0 forms outstanding 0 — green status |
| the same three in Module 3 | B - A: heap +76.0 MB subscribers +50 forms outstanding 0 — red status |
| Tickets → Search tickets, then Redraw | 5,000 rows rebound in 1 ms (no query, no new rows) — the redraw allocates nothing |
| Tickets → double-click a row, then close the form by its X | forms disposed goes up by one and the subscriber count stays where it was — the X and the Close button take the same path |
| Repeat the ×50 round a second time | the same +1 MB, not +2 MB: a one-off cost does not grow, retention does |
The line worth staring at is forms created 50 / disposed 50, which is true before and after.
Disposal always ran. What changed is that nothing holds the disposed forms any more.
What changed since Module 3
Diagnostics/MemoryProbe.cs snapshots: heap after a collection, bus subscribers, forms created/disposed
Models/TicketRow.cs new — the row the grid binds, display strings built once
Forms/TicketDetailForm.cs + Dispose(bool): unsubscribe, stop and dispose the timer, null the
DataSource, dispose the image, drop the buffer, dispose components
+ the timer callback checks IsDisposed
+ static Created / DisposedCount counters for the readings
Forms/TicketDetailForm.Designer.cs its Dispose(bool) moved into the .cs (a partial class has one)
Pages/TicketGridPage.cs the search projects TicketRow once and keeps it; Redraw rebinds
MainPage.cs / .Designer.cs + the three snapshot buttons and the memory readout
Module 3 also carries MemoryProbe, the form counters and the three buttons — with the unfixed
form — so the before reading can be taken on the code as it stood. That is the first half of this module.
Known simplifications
MemoryProbereads the managed heap only (GC.GetTotalMemory). The Wisej.NET client state, the SQLite page cache and the Kestrel buffers live outside it. For a capacity model, use the process working set as well — seeSessionMemoryBudget.md.- The counters say that something is retained. Only Paths to Root in a Memory Usage snapshot says why; the doc walks through collecting it.