diff --git a/docs/plans/PROFILER-PLAN.md b/docs/plans/PROFILER-PLAN.md index bb779b20..5201b403 100644 --- a/docs/plans/PROFILER-PLAN.md +++ b/docs/plans/PROFILER-PLAN.md @@ -202,7 +202,9 @@ These are SharpLsp's shipped profiler features. Cutover is blocked until each ro - a hot function with no location reached the UI as file `""`, line 0 (lspkit#15); - the UI's Stop never sent `memory.stop`, so the server kept the session and its graphs and still answered steps (lspkit#15); - a timed CPU session never left `cpu.list` once its duration elapsed (lspkit#15); - - the host accepted a profiler command with no argument, two arguments or a non-object argument (`profiler/host.rs`). + - the host accepted a profiler command with no argument, two arguments or a non-object argument (`profiler/host.rs`); + - a sample in an F# `match`'s decision tree lost its file, so `Csv.parseLine` split into a located function and an unlocated twin holding 61% of its samples, and the Windows chunk failed on #406 (#408, lspkit#19); + - FsOrderLedger's burn tick took the process-wide gate around every `priceAll` call. EventPipe's sampler stops a thread at its next safe point, so the uncontended lock call drew 98% of the burn thread's samples, and `priceAll` sat at the 2% hot-function threshold. The joint CPU and memory soak failed two runs in three on macOS. The tick only reads, so it now runs outside the gate, and `priceAll` is on every burn sample (#408). - [ ] Assertions of the deleted suites with no replacement: counters webview and counter sessions, `openTrace`/`convertTrace` of a `.nettrace`, dump collection with cancellable progress, `inspectObject`, `diffSnapshots`/`detectLeaks` panels over dumps, graph browser interactions. Each maps to a parity gap above. ### Documentation and cutover diff --git a/lspkit b/lspkit index e6e41727..3cd58965 160000 --- a/lspkit +++ b/lspkit @@ -1 +1 @@ -Subproject commit e6e417275a76568d559ef26f9f68752f7a0f4454 +Subproject commit 3cd589656b0d35382bcc289e09b46ac7c3eee73b diff --git a/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Gate.fs b/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Gate.fs index edc9d63f..11403df3 100644 --- a/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Gate.fs +++ b/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Gate.fs @@ -1,10 +1,12 @@ // FsOrderLedger's single process-wide gate. Commands arrive on the stdin thread while the -// "workload" thread drips orders and burns CPU, so every mutation, every workload tick and every +// "workload" thread drips orders and burns CPU, so every mutation, every drip burst and every // reply snapshot runs inside this one lock: the counts a reply prints are then one consistent -// snapshot of the live heap, not a torn read. +// snapshot of the live heap, not a torn read. A burn tick only reads, so it stays outside: the +// sampler stops a thread at its next safe point, and a lock taken on every tick drew the samples +// of the loop it guarded, which left `priceAll` under the hot-function threshold. // Profiling fixture for [PROFILER-SHARED-LIBRARY-VSCODE] and lspkit's [PROF-TESTING]. module FsOrderLedger.Gate -/// The one process-wide lock. Hold it for a whole command, a whole drip burst, or one burn call. +/// The one process-wide lock. Hold it for a whole command or a whole drip burst. let sync = obj () diff --git a/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Program.fs b/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Program.fs index 13917397..b2171210 100644 --- a/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Program.fs +++ b/src/sharplsp/tests/fixtures/fsharp-profiling/FsOrderLedger/Program.fs @@ -31,8 +31,8 @@ let private dripTick () : unit = placeOne () |> ignore) Thread.Sleep 50 -/// One burn tick: the gate is held for exactly one `priceAll` call, never across ticks. -let private burnTick () : unit = lock Gate.sync (fun () -> Ledger.priceAll () |> ignore) +/// One burn tick: one `priceAll` call. It only reads, under the ledger's own lock, so it takes no gate. +let private burnTick () : unit = Ledger.priceAll () |> ignore let private workloadLoop () = while not (Volatile.Read(&stopping)) do