Skip to content

Await the language server's own async calls instead of blocking on them - #8

Merged
siegfriedpammer merged 1 commit into
mainfrom
fix-blocking-task-waits
Sep 3, 2026
Merged

Await the language server's own async calls instead of blocking on them#8
siegfriedpammer merged 1 commit into
mainfrom
fix-blocking-task-waits

Conversation

@christophwille

Copy link
Copy Markdown
Member

Audit of every .Result / GetAwaiter().GetResult() on a Task in src and tests (vendored tree-view code excluded).

Real problems (fixed)

  • src/Stampeded.RoslynLsp/RoslynLspServer.cs Overlay: called the workspace's GetDocumentTextAsync and read .Result from inside HandleNotificationAsync, which is already async. The method really does await Roslyn's GetTextAsync. Now OverlayAsync, awaited with the notification's cancellation token.
  • src/Stampeded.RoslynLsp/RoslynLspServer.cs Notify: blocked on SendAsync(...).GetAwaiter().GetResult(), waiting on the write semaphore and a stream write. Its only caller is ReportState, an Action subscribed to StateChanged, so it cannot await. Now NotifyAsync returns the send task and ReportState fires it with a faulted-continuation that writes to the log. Ordering survives: SemaphoreSlim.WaitAsync queues waiters FIFO and each call reaches the wait synchronously before yielding. Neither case deadlocked today (no synchronization context in a console server), but both parked a thread-pool thread while another response held the lock.

Not problems (left alone)

  • src/Stampeded/Documents/DiffDocumentView.axaml.cs and src/Stampeded/Documents/SideBySideDocumentView.axaml.cs: regions.Result is read only inside an IsCompletedSuccessfully check. Nothing blocks. This is the documented pattern: an in-process parser answers before it is asked, and the diff view awaits the language-server case separately.
  • src/Stampeded/Panes/TestsPaneViewModel.cs: row.Result is the TestResult record property on TestRow, not a Task.

Nit (fixed)

  • tests/Stampeded.Core.Tests/GeneratedSourcesTests.cs: four tests called DiffAsync(...).GetAwaiter().GetResult(). NUnit runs async Task tests natively, so they now await.

Build clean, GeneratedSourcesTests green.

🤖 Generated with Claude Code

The server handled a didOpen/didChange notification from an async method
that then blocked on the workspace's text lookup with .Result, and sent its
state notification by blocking on the write lock from an event handler. A
console server has no synchronization context, so neither deadlocked, but
each parked a thread-pool thread while another response held the lock.

The overlay is now awaited with the notification's token. The state report
stays fire-and-forget because an Action handler cannot await; the write
semaphore queues waiters in order, so states still arrive in the order they
were reached, and a failure goes to the log instead of vanishing.

The GeneratedSources tests blocked the same way for no reason NUnit needs.

Assisted-by: Claude:claude-fable-5-1:Claude Code
@siegfriedpammer
siegfriedpammer merged commit d7681f2 into main Sep 3, 2026
3 checks passed
@christophwille
christophwille deleted the fix-blocking-task-waits branch September 3, 2026 13:14
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.

2 participants