Skip to content

Commit 0ce31f4

Browse files
committed
fix: load configured large solutions promptly
1 parent 05326f0 commit 0ce31f4

9 files changed

Lines changed: 160 additions & 38 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ All notable changes to this project will be documented in this file.
1616
### Known issues
1717
- _None yet._
1818

19+
## 0.1.20
20+
21+
### Fixed
22+
- Configured `vbnet.workspace.solutionPath` and `vbnet.workspace.projectPaths` are now honored before broad workspace `.vbproj` discovery, avoiding large-repository scans before loading the selected solution.
23+
- Workspace loads started by `workspace/didChangeConfiguration` now run in the same cancelable background path as initial startup loads, so configuration notifications cannot block later LSP requests during a long solution load.
24+
- Open documents no longer publish standalone missing-reference diagnostics while the initial workspace load is still pending; diagnostics are deferred until the document can be associated with the loaded Roslyn workspace.
25+
- Deferred project loading now preserves the workspace root so enabling `loadProjectsOnStart` from configuration can start the workspace load later.
26+
27+
### Known issues
28+
- Very large solutions can still take time for Roslyn/MSBuildWorkspace to fully load; language features may be limited until the background workspace load completes.
29+
1930
## 0.1.19
2031

2132
### Fixed

‎src/VbNet.LanguageServer.Vb/Core/LanguageServer.vb‎

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Namespace Core
8686
''' <summary>
8787
''' Server version reported in initialize response.
8888
''' </summary>
89-
Public Const ServerVersion As String = "0.1.19"
89+
Public Const ServerVersion As String = "0.1.20"
9090

9191
Public Sub New(transport As ITransport, loggerFactory As ILoggerFactory)
9292
If transport Is Nothing Then
@@ -488,18 +488,20 @@ Namespace Core
488488
_workspaceManager.Initialize()
489489
ApplyInitializationOptions()
490490

491+
If _initializeParams?.RootUri IsNot Nothing Then
492+
_workspaceRootUri = _initializeParams.RootUri
493+
ElseIf _initializeParams?.WorkspaceFolders IsNot Nothing AndAlso _initializeParams.WorkspaceFolders.Length > 0 Then
494+
_workspaceRootUri = _initializeParams.WorkspaceFolders(0).Uri
495+
End If
496+
491497
If Not _loadProjectsOnStart Then
492498
_logger.LogInformation("Project load deferred (vbnet.loadProjectsOnStart = false).")
493499
_workspaceManager.SignalInitialLoadCompleted(False)
494500
Return Task.CompletedTask
495501
End If
496502

497-
If _initializeParams?.RootUri IsNot Nothing Then
498-
_workspaceRootUri = _initializeParams.RootUri
499-
StartInitialWorkspaceLoad(_initializeParams.RootUri)
500-
ElseIf _initializeParams?.WorkspaceFolders IsNot Nothing AndAlso _initializeParams.WorkspaceFolders.Length > 0 Then
501-
_workspaceRootUri = _initializeParams.WorkspaceFolders(0).Uri
502-
StartInitialWorkspaceLoad(_initializeParams.WorkspaceFolders(0).Uri)
503+
If _workspaceRootUri IsNot Nothing Then
504+
StartInitialWorkspaceLoad(_workspaceRootUri)
503505
Else
504506
_logger.LogWarning("No workspace root provided, operating in single-file mode")
505507
_workspaceManager.SignalInitialLoadCompleted(False)
@@ -509,20 +511,28 @@ Namespace Core
509511
End Function
510512

511513
Private Sub StartInitialWorkspaceLoad(rootUri As String)
514+
StartWorkspaceLoad(rootUri, "Initial workspace load", False)
515+
End Sub
516+
517+
Private Sub StartWorkspaceLoad(rootUri As String, operationName As String, resetWorkspace As Boolean)
512518
CancelWorkspaceLoad()
513519

514520
Dim loadCts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token)
515521
Dim loadTask = Task.Run(
516522
Async Function()
517523
Try
518-
_logger.LogInformation("Initial workspace load started in background")
524+
_logger.LogInformation("{Operation} started in background", operationName)
525+
If resetWorkspace Then
526+
Await _workspaceManager.ResetWorkspaceAsync(loadCts.Token).ConfigureAwait(False)
527+
End If
528+
519529
Await LoadWorkspaceAsync(rootUri, loadCts.Token).ConfigureAwait(False)
520-
_logger.LogInformation("Initial workspace load completed")
530+
_logger.LogInformation("{Operation} completed", operationName)
521531
Catch ex As OperationCanceledException When loadCts.IsCancellationRequested
522-
_logger.LogInformation("Initial workspace load cancelled")
532+
_logger.LogInformation("{Operation} cancelled", operationName)
523533
_workspaceManager.SignalInitialLoadCompleted(False)
524534
Catch ex As Exception
525-
_logger.LogError(ex, "Initial workspace load failed")
535+
_logger.LogError(ex, "{Operation} failed", operationName)
526536
_workspaceManager.SignalInitialLoadCompleted(False)
527537
End Try
528538
End Function)
@@ -538,6 +548,12 @@ Namespace Core
538548
End SyncLock
539549
End Sub
540550

551+
Private Function IsWorkspaceLoadRunning() As Boolean
552+
SyncLock _workspaceLoadGate
553+
Return _workspaceLoadTask IsNot Nothing AndAlso Not _workspaceLoadTask.IsCompleted
554+
End SyncLock
555+
End Function
556+
541557
Private Sub CancelWorkspaceLoad()
542558
Dim loadCts As CancellationTokenSource = Nothing
543559

@@ -574,18 +590,10 @@ Namespace Core
574590
Return
575591
End If
576592

577-
Dim projectSearchRoots = GetProjectSearchRoots(rootPath)
578-
Dim vbprojFiles = CollectVbProjFiles(
579-
projectSearchRoots,
580-
_workspaceExcludePaths,
581-
_workspaceMaxProjectResults,
582-
ct)
583-
584-
Await ReportNetFxSupportWarningsAsync(vbprojFiles, ct).ConfigureAwait(False)
585-
586593
If Not String.IsNullOrWhiteSpace(_workspaceSolutionPathOverride) Then
587594
Dim explicitSolutionPath = ResolvePath(_workspaceSolutionPathOverride, rootPath)
588595
If Not String.IsNullOrEmpty(explicitSolutionPath) Then
596+
_logger.LogInformation("Using configured solution path: {Path}", explicitSolutionPath)
589597
Dim explicitProjects = GetSolutionProjectPaths(explicitSolutionPath)
590598
Await ReportNetFxSupportWarningsAsync(explicitProjects, ct).ConfigureAwait(False)
591599
loadSucceeded = Await _workspaceManager.LoadSolutionAsync(explicitSolutionPath, ct).ConfigureAwait(False)
@@ -595,23 +603,19 @@ Namespace Core
595603

596604
If _workspaceProjectPathsOverride IsNot Nothing AndAlso _workspaceProjectPathsOverride.Length > 0 Then
597605
Dim anyLoaded = False
598-
Await ReportNetFxSupportWarningsAsync(_workspaceProjectPathsOverride, ct).ConfigureAwait(False)
599-
Dim loadedCount = 0
600-
For Each projectPath In _workspaceProjectPathsOverride
601-
If String.IsNullOrWhiteSpace(projectPath) OrElse Not projectPath.EndsWith(".vbproj", StringComparison.OrdinalIgnoreCase) Then
602-
Continue For
603-
End If
606+
Dim resolvedProjectPaths = _workspaceProjectPathsOverride _
607+
.Select(Function(projectPath) ResolvePath(projectPath, rootPath)) _
608+
.Where(Function(projectPath) Not String.IsNullOrWhiteSpace(projectPath) AndAlso projectPath.EndsWith(".vbproj", StringComparison.OrdinalIgnoreCase)) _
609+
.ToList()
604610

611+
Await ReportNetFxSupportWarningsAsync(resolvedProjectPaths, ct).ConfigureAwait(False)
612+
Dim loadedCount = 0
613+
For Each resolved In resolvedProjectPaths
605614
If _workspaceMaxProjectCount > 0 AndAlso loadedCount >= _workspaceMaxProjectCount Then
606615
_logger.LogWarning("Project load capped at {Max} projects (vbnet.maxProjectCount).", _workspaceMaxProjectCount)
607616
Exit For
608617
End If
609618

610-
Dim resolved = ResolvePath(projectPath, rootPath)
611-
If String.IsNullOrEmpty(resolved) Then
612-
Continue For
613-
End If
614-
615619
Dim loaded = Await _workspaceManager.LoadProjectAsync(resolved, ct).ConfigureAwait(False)
616620
anyLoaded = anyLoaded OrElse loaded
617621
If loaded Then
@@ -626,6 +630,7 @@ Namespace Core
626630
Return
627631
End If
628632

633+
Dim vbprojFiles As List(Of String) = Nothing
629634
If Not _ignoreSolutionFiles Then
630635
Dim solutionCandidates = FindSolutionCandidates(rootPath)
631636
If solutionCandidates.Count > 0 Then
@@ -645,6 +650,15 @@ Namespace Core
645650
End If
646651
End If
647652

653+
Dim projectSearchRoots = GetProjectSearchRoots(rootPath)
654+
vbprojFiles = CollectVbProjFiles(
655+
projectSearchRoots,
656+
_workspaceExcludePaths,
657+
_workspaceMaxProjectResults,
658+
ct)
659+
660+
Await ReportNetFxSupportWarningsAsync(vbprojFiles, ct).ConfigureAwait(False)
661+
648662
If _workspaceMaxProjectResults > 0 AndAlso vbprojFiles.Count >= _workspaceMaxProjectResults Then
649663
_logger.LogInformation("Project search capped at {Max} results", _workspaceMaxProjectResults)
650664
End If
@@ -890,13 +904,16 @@ Namespace Core
890904

891905
If _loadProjectsOnStart AndAlso needReload AndAlso _workspaceRootUri IsNot Nothing Then
892906
_logger.LogInformation("Workspace configuration changed; reloading workspace")
893-
Await _workspaceManager.ResetWorkspaceAsync(ct).ConfigureAwait(False)
894-
Await LoadWorkspaceAsync(_workspaceRootUri, ct).ConfigureAwait(False)
907+
StartWorkspaceLoad(_workspaceRootUri, "Workspace reload", True)
895908
End If
896909

897-
If _loadProjectsOnStart AndAlso Not _workspaceManager.IsLoaded AndAlso _workspaceRootUri IsNot Nothing Then
910+
If _loadProjectsOnStart AndAlso
911+
Not needReload AndAlso
912+
Not _workspaceManager.IsLoaded AndAlso
913+
Not IsWorkspaceLoadRunning() AndAlso
914+
_workspaceRootUri IsNot Nothing Then
898915
_logger.LogInformation("Project loading enabled after configuration change; loading workspace")
899-
Await LoadWorkspaceAsync(_workspaceRootUri, ct).ConfigureAwait(False)
916+
StartWorkspaceLoad(_workspaceRootUri, "Workspace load after configuration change", False)
900917
End If
901918
End Function
902919

‎src/VbNet.LanguageServer.Vb/Services/DiagnosticsService.vb‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ Namespace Services
180180
Public Async Function GetDiagnosticsAsync(uri As String, Optional cancellationToken As CancellationToken = Nothing) As Task(Of Protocol.Diagnostic())
181181
Dim document = _documentManager.GetRoslynDocument(uri)
182182
If document Is Nothing Then
183+
If _workspaceManager.IsInitialLoadPending Then
184+
_logger.LogTrace("No Roslyn document found for: {Uri}. Workspace is still loading; deferring diagnostics.", uri)
185+
Return Array.Empty(Of Protocol.Diagnostic)()
186+
End If
187+
183188
_logger.LogTrace("No Roslyn document found for: {Uri}. Falling back to standalone diagnostics.", uri)
184189
Return Await GetStandaloneDiagnosticsAsync(uri, cancellationToken).ConfigureAwait(False)
185190
End If

‎src/VbNet.LanguageServer.Vb/Workspace/WorkspaceManager.vb‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ Namespace Workspace
6868
End Get
6969
End Property
7070

71+
''' <summary>
72+
''' Gets whether the initial workspace load has started but not completed.
73+
''' </summary>
74+
Public ReadOnly Property IsInitialLoadPending As Boolean
75+
Get
76+
Return _workspace IsNot Nothing AndAlso Not _initialLoadTcs.Task.IsCompleted
77+
End Get
78+
End Property
79+
7180
''' <summary>
7281
''' Gets the path of the loaded solution, if any.
7382
''' </summary>

‎src/extension/CHANGELOG.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ All notable changes to this project will be documented in this file.
1616
### Known issues
1717
- _None yet._
1818

19+
## 0.1.20
20+
21+
### Fixed
22+
- Configured `vbnet.workspace.solutionPath` and `vbnet.workspace.projectPaths` are now honored before broad workspace `.vbproj` discovery, avoiding large-repository scans before loading the selected solution.
23+
- Workspace loads started by `workspace/didChangeConfiguration` now run in the same cancelable background path as initial startup loads, so configuration notifications cannot block later LSP requests during a long solution load.
24+
- Open documents no longer publish standalone missing-reference diagnostics while the initial workspace load is still pending; diagnostics are deferred until the document can be associated with the loaded Roslyn workspace.
25+
- Deferred project loading now preserves the workspace root so enabling `loadProjectsOnStart` from configuration can start the workspace load later.
26+
27+
### Known issues
28+
- Very large solutions can still take time for Roslyn/MSBuildWorkspace to fully load; language features may be limited until the background workspace load completes.
29+
1930
## 0.1.19
2031

2132
### Fixed

‎src/extension/package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/extension/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vbnet-language-support",
33
"publisher": "dnakode",
4-
"version": "0.1.19",
4+
"version": "0.1.20",
55
"description": "First-class VB.NET language support for VS Code",
66
"displayName": "VB.NET Language Support",
77
"icon": "images/vbnet-icon.png",

‎test/VbNet.LanguageServer.Tests.Vb/Integration/LanguageServerIntegrationTests.vb‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,50 @@ Namespace VbNet.LanguageServer.Tests.Integration
142142
End Using
143143
End Function
144144

145+
<Fact>
146+
Public Async Function DidChangeConfiguration_DoesNotBlockShutdownWhileWorkspaceLoads() As Task
147+
Dim transport As New QueuedTransport()
148+
Dim server As New LspServer(transport, NullLoggerFactory.Instance)
149+
Dim loadStarted As New TaskCompletionSource(Of Object)(TaskCreationOptions.RunContinuationsAsynchronously)
150+
151+
server.TestBeforeWorkspaceLoadAsync =
152+
Async Function(ct)
153+
loadStarted.TrySetResult(Nothing)
154+
Await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(False)
155+
End Function
156+
157+
Using runCts As New CancellationTokenSource()
158+
Dim runTask = server.RunAsync(runCts.Token)
159+
160+
Try
161+
Dim rootUri = New Uri(TestProjectsRoot & Path.DirectorySeparatorChar).AbsoluteUri
162+
Dim initializeMessage =
163+
"{""jsonrpc"":""2.0"",""id"":1,""method"":""initialize"",""params"":{""rootUri"":" &
164+
JsonSerializer.Serialize(rootUri) &
165+
",""clientInfo"":{""name"":""test"",""version"":""1.0""},""initializationOptions"":{""loadProjectsOnStart"":false}}}"
166+
167+
transport.EnqueueMessage(initializeMessage)
168+
Await WaitForResponseIdAsync(transport, 1).ConfigureAwait(False)
169+
170+
transport.EnqueueMessage("{""jsonrpc"":""2.0"",""method"":""initialized"",""params"":{}}")
171+
transport.EnqueueMessage("{""jsonrpc"":""2.0"",""method"":""workspace/didChangeConfiguration"",""params"":{""settings"":{""vbnet"":{""loadProjectsOnStart"":true}}}}")
172+
Await WaitWithTimeoutAsync(loadStarted.Task, TimeSpan.FromSeconds(3)).ConfigureAwait(False)
173+
174+
transport.EnqueueMessage("{""jsonrpc"":""2.0"",""id"":2,""method"":""shutdown""}")
175+
Dim shutdownResponse = Await WaitForResponseIdAsync(transport, 2).ConfigureAwait(False)
176+
Assert.Contains("""id"":2", shutdownResponse)
177+
178+
transport.EnqueueMessage("{""jsonrpc"":""2.0"",""method"":""exit""}")
179+
transport.Complete()
180+
Await WaitWithTimeoutAsync(runTask, TimeSpan.FromSeconds(3)).ConfigureAwait(False)
181+
Finally
182+
transport.Complete()
183+
runCts.Cancel()
184+
server.DisposeAsync().AsTask().GetAwaiter().GetResult()
185+
End Try
186+
End Using
187+
End Function
188+
145189
Private Shared Async Function WaitForResponseIdAsync(transport As QueuedTransport, id As Integer) As Task(Of String)
146190
Using timeoutCts As New CancellationTokenSource(TimeSpan.FromSeconds(3))
147191
While True

‎test/VbNet.LanguageServer.Tests.Vb/Services/DiagnosticsServiceTests.vb‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,31 @@ Namespace VbNet.LanguageServer.Tests.Services
105105
Assert.Equal(uri, published.Uri)
106106
End Function
107107

108+
<Fact>
109+
Public Async Function ComputeAndPublishDiagnosticsAsync_DefersStandaloneDiagnosticsWhileInitialLoadPending() As Task
110+
Dim uri = "file:///c:/test/module1.vb"
111+
112+
_workspaceManager.Initialize()
113+
_diagnosticsService.Enabled = False
114+
_documentManager.HandleDidOpen(New DidOpenTextDocumentParams With {
115+
.TextDocument = New TextDocumentItem With {
116+
.Uri = uri,
117+
.LanguageId = "vb",
118+
.Version = 1,
119+
.Text = "Imports MissingNamespace" & vbLf &
120+
"Module Module1" & vbLf &
121+
"End Module"
122+
}
123+
})
124+
125+
_diagnosticsService.Enabled = True
126+
Await _diagnosticsService.ComputeAndPublishDiagnosticsAsync(uri)
127+
128+
Assert.Single(_publishedDiagnostics)
129+
Assert.Equal(uri, _publishedDiagnostics(0).Uri)
130+
Assert.Empty(_publishedDiagnostics(0).Diagnostics)
131+
End Function
132+
108133
<Fact>
109134
Public Async Function ComputeAndPublishDiagnosticsAsync_DoesNothingWhenDisabled() As Task
110135
Dim uri = "file:///c:/test/module1.vb"

0 commit comments

Comments
 (0)