Skip to content

Name a suspected leak by what the runtime reported; tell a direct runtime what tracking does - #33

Merged
MelbourneDeveloper merged 5 commits into
mainfrom
fix/leak-offer-names-its-leak
Oct 29, 2026
Merged

MelbourneDeveloper merged 5 commits into
mainfrom
fix/leak-offer-names-its-leak

Conversation

@MelbourneDeveloper

@MelbourneDeveloper MelbourneDeveloper commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

TLDR

The autopilot's leak offer no longer reads "Suspected memory leak at :0" for a leak with a type and no source line. The tracking toast no longer tells a direct (.NET) runtime that each pause is captured. Two flaky tests are made deterministic: the server's mid-scan cancel, and Windows hot-line navigation.

Details

Both bugs showed up watching SharpLsp's memory autopilot e2e in a real VS Code window on Windows.

  • The leak offer ([PROF-UI-AUTOPILOT]).
    • The UI's decoration diff (toDiffResult) dropped the runtime's type_name, and the offer named every leak by basename(file):line.
    • A .NET byte[] grown by user code has a type and no line, so every local run of SharpLsp's money-flow e2e offered :0 (HIGH): 8 of 8 offers.
    • New memory-leak-offer.ts:
      • describeLeak names the type and the source line as far as they were reported, for example LoopLeak.Widget at Program.cs:4 or System.Byte[];
      • leakToOffer offers the most confident leak, and among equals one with a source line.
    • LeakActionOffer (the e2e seam) now records typeName and the message shown.
    • worstLeak is gone: the capture ledger's max confidence comes from leakToOffer.
  • The tracking toast ([PROF-MEMORY-MODE], [PROF-UI-AUTOPILOT]).
    • Both start toasts said "press Continue … each pause is captured". A direct runtime is never captured at a pause, so a .NET user waited for captures that never came.
    • New memory-guidance.ts, trackingGuidance(direct, intervalSecs):
      • a courier runtime keeps its words;
      • a direct runtime is told it is captured every N s while it runs, or to take a snapshot while it runs when interval capture is off. Either way, never while paused, and its last snapshot opens when it ends.
    • captureIntervalSeconds() is now the one reader of the interval settings, shared by the autopilot timer and the toast.
  • Spec. [PROF-UI-AUTOPILOT] now states both rules.
  • The stop toast ([PROF-MEMORY-MODE]).
    • Stopping before any snapshot said "Take a snapshot while paused", directly under the new start toast saying "never while paused".
    • stoppedWithoutSnapshot(direct) now tells a direct runtime to take one while the program runs.
  • Language names ([PROF-UI-HOST]).
    • "Run & profile" with no matching file open said "Open a csharp/fsharp file".
    • ProfilerHost now carries the required languageNames, and the message reads "Open a file in F# or C# to run and profile". The spec lists the new field.
  • Windows: the UI suite deleted a tracked fixture.
    • editor-path-key.test.ts junctions dotnet/fixtures/HotLeakApp into a temp dir. Its teardown's recursive rmSync followed the junction on Windows and deleted the fixture's Program.cs and HotLeakApp.csproj on every run.
    • The link is now unlinked first.
  • Flake: cancel_during_a_scan_stops_the_stream_with_the_standard_error.
    • It failed on this PR's first CI run.
    • It required a second partial after the barrier, but a cancel racing the first partial sometimes left none.
    • The scan is now held on the native read of its last document (HeldSource; waiting.json sorts after every scan-NNNN.json), so the cancel lands mid-scan by construction.
    • It now asserts that all 64 documents before the hold streamed exactly once, the held one never did, and the reply is -32800.
  • Windows: a measured hot line navigates to the actual runtime source.
    • The contract compared activeTextEditor.document.uri.fsPath to the runtime's file as raw strings. VS Code spells the drive c:, and a fixture built from C:\... reported C:.
    • The right file had opened at the right line, but the test failed in SharpLsp's Windows profiler chunk.
    • It now compares through editorPathKey, like every other overlay, and asserts the hot line's own file opened.

How Do The Automated Tests Prove It Works?

src/test/suite/memory-leak-offer.test.ts has 9 tests:

  • a diff keeps the type the runtime named… pushes a wire diff of three leaks through toMemoryIngestResult and toDiffResult: type-only, located, and neither. It asserts each leak's file, line, type and confidence. With the mapping line reverted it fails on typeName: '' vs 'System.Byte[]' and 'LoopLeak.Widget'.

  • Naming:

    • a type-only leak is named System.Byte[] and contains no :;
    • a located one is LoopLeak.Widget at Program.cs:4, or Program.cs:4 without a type;
    • a file without a line, or a line without a file, is not a location.
  • Choice:

    • equal confidence prefers the located leak in either order, and keeps the first of true equals;
    • higher confidence beats a source line, including DEFINITE over HIGH;
    • an empty list offers nothing.
  • Toast:

    • a direct runtime is never told "Continue" at intervals off, 1 and 30;
    • it is told its interval when on, and to snapshot when off;
    • a courier runtime keeps "Press Continue to auto-capture each pause".
  • stopped before any snapshot…: a direct runtime is told to snapshot while it runs, never while paused, and a courier one keeps its words.

  • host-language-names.test.ts: two names are joined in words in the host's order, one name stands alone, and no / or language ID leaks through.

  • removing the link leaves the project it linked to in place fails without the unlink: the fixture is gone. It passes with it.

The full UI suite passes (189) and npm run lint is clean.

  • Cancel test. dispatcher:: passed 20 of 20 runs on Windows after the change.
  • Hot-line navigation.
    • SharpLsp's profiler-cpu-e2e on Windows failed before the change: the editor was at c:\...\ProfileTarget\Program.cs line 251, and the test timed out on the raw compare.
    • It passes 9 of 9 with the change. SharpLsp's money-flow e2e will assert the offer it shows once this lands.

…time what tracking does

The autopilot's leak offer named every leak by file:line, and the diff the
UI reads dropped the runtime's type name. A .NET byte[] grown by user code
has a type and no line, so SharpLsp offered "Suspected memory leak at :0
(HIGH)" on every run. The offer now names the type and the source line as
far as they were reported, and among equally confident leaks it offers one
with a source line.

Both toasts that open tracking told the user to press Continue so each
pause is captured. A direct runtime is never captured at a pause
([PROF-MEMORY-MODE]), so a .NET user waited for captures that never came.
The toast now says what the runtime's mode does: a direct runtime is
captured on the interval while it runs (or snapshotted by hand), never
while paused, and its last snapshot opens when it ends.
…s the editor does

cancel_during_a_scan_stops_the_stream_with_the_standard_error failed on CI
(lspkit #33, run 40838382102): it required a second partial to stream
after the first, but a cancel racing the first partial sometimes left
none. The scan is now held on the native read of its last document, which
sorts after every scan document, so the cancel lands mid-scan by
construction. The test now asserts every one of the 64 documents before
the hold streamed exactly once, the held one never did, and the reply is
the standard cancellation error.

The CPU capture contract's hot-line navigation compared the opened
editor's fsPath to the runtime's file as raw strings. On Windows the
editor spells the drive in lower case, so a fixture built from an
upper-case path failed although the right file opened at the right line
(SharpLsp's Windows profiler chunk, locally). It now compares through
editorPathKey and asserts the hot line's own file opened.
…nguages in messages

Watching SharpLsp's Windows profiler suite turned up two more messages
that told a .NET user something untrue or unreadable:

- Stopping tracking before any snapshot said "Take a snapshot while
  paused, or let the program finish". A direct runtime refuses a snapshot
  while paused ([PROF-MEMORY-MODE]), and the start toast just above it now
  says "never while paused". stoppedWithoutSnapshot now says, for a direct
  runtime, to take one while the program runs.
- "Run & profile" with no file of the host's languages open said "Open a
  csharp/fsharp file": VS Code language IDs. A host now also supplies
  languageNames ([PROF-UI-HOST]), and the message reads "Open a file in
  F# or C# to run and profile."
…re survives

The editor-path-key test junctions dotnet/fixtures/HotLeakApp into a temp
directory. On Windows its teardown's recursive rmSync followed the
junction and deleted the real fixture's Program.cs and HotLeakApp.csproj
on every UI-suite run. The link itself is now unlinked first, and a new
test removes a link the same way and asserts the linked project's files
remain; it fails without the unlink.
… copied-PID toast

Revealing a .NET process row with no entry file said 'no resolvable
script file', Python-era wording under a command titled 'Reveal Entry in
Editor'; 'Copied PID' was the one profiler toast without the host's
name.
@MelbourneDeveloper
MelbourneDeveloper merged commit 741e806 into main Oct 29, 2026
3 checks passed
@MelbourneDeveloper
MelbourneDeveloper deleted the fix/leak-offer-names-its-leak branch October 29, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant