Await the language server's own async calls instead of blocking on them - #8
Merged
Merged
Conversation
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
approved these changes
Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Audit of every
.Result/GetAwaiter().GetResult()on a Task insrcandtests(vendored tree-view code excluded).Real problems (fixed)
src/Stampeded.RoslynLsp/RoslynLspServer.csOverlay: called the workspace'sGetDocumentTextAsyncand read.Resultfrom insideHandleNotificationAsync, which is already async. The method really does await Roslyn'sGetTextAsync. NowOverlayAsync, awaited with the notification's cancellation token.src/Stampeded.RoslynLsp/RoslynLspServer.csNotify: blocked onSendAsync(...).GetAwaiter().GetResult(), waiting on the write semaphore and a stream write. Its only caller isReportState, anActionsubscribed toStateChanged, so it cannot await. NowNotifyAsyncreturns the send task andReportStatefires it with a faulted-continuation that writes to the log. Ordering survives:SemaphoreSlim.WaitAsyncqueues 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.csandsrc/Stampeded/Documents/SideBySideDocumentView.axaml.cs:regions.Resultis read only inside anIsCompletedSuccessfullycheck. 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.Resultis theTestResultrecord property onTestRow, not a Task.Nit (fixed)
tests/Stampeded.Core.Tests/GeneratedSourcesTests.cs: four tests calledDiffAsync(...).GetAwaiter().GetResult(). NUnit runsasync Tasktests natively, so they now await.Build clean,
GeneratedSourcesTestsgreen.🤖 Generated with Claude Code