Repository navigation
Conversation
tools/lsp-mem/lifecycle_leaks.py drives one tsrs process through N cycles that create and release a program (watch rebuilds, language-server edits, open/close and tsconfig reloads, API snapshots, updates and builds), samples its resident memory after each cycle and fits bytes per cycle. --poison checks crashes, answers and the region log's balance; --split divides the difference of two TSRS_MEM_SPLIT exit reports by the extra cycles into what stays used in the thread arenas and live in the heap; --vmmap attributes the growth on macOS. The --lsp and --api servers now print the exit split. notes/lifecycle-leaks.md records the runs at 357d1d3: six scenarios are steady; createSnapshot in the API and a tsconfig change in the language server keep every loaded config (about 50 KiB in a thread arena plus its heap members), because the config load happens outside any region.
Merging this PR will improve performance by 7.6%1 warning
Performance Changes
Tip Curious why performance improved? Comment Comparing Footnotes
|
Performance change vs mainWorkflow run · main Clef gate: Running benchmarks because the decision was unavailable (ValueError). Release builds, default checker count;
Negative means the PR used less; positive means it used more. Deltas are medians of paired PR/main ratios for wall time and instructions. Shared CI machines and parallel scheduling add noise to small changes. |
#677 moves the config load into a region; measured with the tool, the command line's heap members and the config text are freed, but about 41 KiB per full program build stays in a thread arena and is not the config load. The note said the whole figure was the load.
Every FSEvents client on the measuring machine saw file events about 13 s late (fseventsd at 99.7% CPU, 44 GiB resident); tsrs's own path from the callback to "File change detected" and the rebuild is under 30 ms.
The 41 KiB per full build is mostly the file loader's worker-side resolutions: the language server and the API never set ProgramOptions::regions, so every full build left them in thread arenas.
a tool that cycles the servers through program lifecycles and says what grows per cycle. it found the two leaks #677 and #678 fix
why
#646 asks for observed leaks across repeated build and lsp cycles. lsp_mem.py did one long editing session. nothing cycled programs (rebuilds, snapshots, reloads, builds) or separated kept data from memory the allocator just holds
what changed
tools/lsp-mem/lifecycle_leaks.py: 8 scenarios (watch, watch-root, lsp-edit, lsp-open-close, lsp-reload, api-snapshot, api-update, api-build). rss after every cycle with a slope fit.--poisonfor crashes, wrong answers and region balance.--splitdiffs the servers'TSRS_MEM_SPLITexit reports between a short and a long run, which gives bytes per cycle that stay used in thread arenas vs live heap vs merely resident. thats the number to trust--lspand--apiservers print the exit memory split like the compile path does (a few lines in main.rs)notes/lifecycle-leaks.md: method, tables, the two leaks with their code paths, a draft issue (not filed)what it found
six scenarios are steady. api-snapshot and lsp-reload grow forever, roughly 45-50 KiB of thread arena per full build. two causes, each with a fix PR: every tsconfig load lands in the thread arena outside any region (#677), and the lsp and api never set loader regions so every full build leaves the workers' resolutions in thread arenas for the life of the process (#678). 0 wrong answers in 2,520 normal and 1,260 poison cycles
tradeoffs
rss slopes are noisy, use
--split.--vmmapis macos only. synthetic 40 module project, real tsconfigs keep more per load. the watch runs are short (60 / 30 cycles) because my machine's fseventsd was delivering events ~13s late to every client, tsrs's own path is under 30ms--apiserver for every call;dispose()doesn't free it #655