@@ -29,7 +29,6 @@ internal abstract class LanguageServerProjectLoader
29
29
{
30
30
private readonly AsyncBatchingWorkQueue < ProjectToLoad > _projectsToReload ;
31
31
32
- protected readonly ProjectSystemProjectFactory ProjectFactory ;
33
32
private readonly ProjectTargetFrameworkManager _targetFrameworkManager ;
34
33
private readonly ProjectSystemHostInfo _projectSystemHostInfo ;
35
34
private readonly IFileChangeWatcher _fileChangeWatcher ;
@@ -66,11 +65,15 @@ private ProjectLoadState() { }
66
65
/// Represents a project which has not yet had a design-time build performed for it,
67
66
/// and which has an associated "primordial project" in the workspace.
68
67
/// </summary>
68
+ /// <param name="PrimordialProjectFactory">
69
+ /// The project factory for the workspace that the primordial project lives within. This
70
+ /// factory was not used to create the project, but still needs to be used during removal to avoid locking issues.
71
+ /// </param>
69
72
/// <param name="PrimordialProjectId">
70
73
/// ID of the project which LSP uses to fulfill requests until the first design-time build is complete.
71
74
/// The project with this ID is removed from the workspace when unloading or when transitioning to <see cref="LoadedTargets"/> state.
72
75
/// </param>
73
- public sealed record Primordial ( ProjectId PrimordialProjectId ) : ProjectLoadState ;
76
+ public sealed record Primordial ( ProjectSystemProjectFactory PrimordialProjectFactory , ProjectId PrimordialProjectId ) : ProjectLoadState ;
74
77
75
78
/// <summary>
76
79
/// Represents a project for which we have loaded zero or more targets.
@@ -83,7 +86,6 @@ public sealed record LoadedTargets(ImmutableArray<LoadedProject> LoadedProjectTa
83
86
}
84
87
85
88
protected LanguageServerProjectLoader (
86
- ProjectSystemProjectFactory projectFactory ,
87
89
ProjectTargetFrameworkManager targetFrameworkManager ,
88
90
ProjectSystemHostInfo projectSystemHostInfo ,
89
91
IFileChangeWatcher fileChangeWatcher ,
@@ -94,7 +96,6 @@ protected LanguageServerProjectLoader(
94
96
ServerConfigurationFactory serverConfigurationFactory ,
95
97
IBinLogPathProvider binLogPathProvider )
96
98
{
97
- ProjectFactory = projectFactory ;
98
99
_targetFrameworkManager = targetFrameworkManager ;
99
100
_projectSystemHostInfo = projectSystemHostInfo ;
100
101
_fileChangeWatcher = fileChangeWatcher ;
@@ -103,7 +104,6 @@ protected LanguageServerProjectLoader(
103
104
_logger = loggerFactory . CreateLogger ( nameof ( LanguageServerProjectLoader ) ) ;
104
105
_projectLoadTelemetryReporter = projectLoadTelemetry ;
105
106
_binLogPathProvider = binLogPathProvider ;
106
- var workspace = projectFactory . Workspace ;
107
107
var razorDesignTimePath = serverConfigurationFactory . ServerConfiguration ? . RazorDesignTimePath ;
108
108
109
109
AdditionalProperties = razorDesignTimePath is null
@@ -174,7 +174,7 @@ private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList<ProjectToLoad
174
174
}
175
175
}
176
176
177
- protected sealed record RemoteProjectLoadResult ( RemoteProjectFile ProjectFile , bool HasAllInformation , BuildHostProcessKind Preferred , BuildHostProcessKind Actual ) ;
177
+ protected sealed record RemoteProjectLoadResult ( RemoteProjectFile ProjectFile , ProjectSystemProjectFactory ProjectFactory , bool HasAllInformation , BuildHostProcessKind Preferred , BuildHostProcessKind Actual ) ;
178
178
179
179
/// <summary>Loads a project in the MSBuild host.</summary>
180
180
/// <remarks>Caller needs to catch exceptions to avoid bringing down the project loader queue.</remarks>
@@ -209,7 +209,7 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
209
209
return false ;
210
210
}
211
211
212
- ( RemoteProjectFile remoteProjectFile , bool hasAllInformation , BuildHostProcessKind preferredBuildHostKind , BuildHostProcessKind actualBuildHostKind ) = remoteProjectLoadResult ;
212
+ ( RemoteProjectFile remoteProjectFile , ProjectSystemProjectFactory projectFactory , bool hasAllInformation , BuildHostProcessKind preferredBuildHostKind , BuildHostProcessKind actualBuildHostKind ) = remoteProjectLoadResult ;
213
213
if ( preferredBuildHostKind != actualBuildHostKind )
214
214
preferredBuildHostKindThatWeDidNotGet = preferredBuildHostKind ;
215
215
@@ -226,7 +226,7 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
226
226
// The out-of-proc build host supports more languages than we may actually have Workspace binaries for, so ensure we can actually process that
227
227
// language in-process.
228
228
var projectLanguage = loadedProjectInfos . FirstOrDefault ( ) ? . Language ;
229
- if ( projectLanguage != null && ProjectFactory . Workspace . Services . GetLanguageService < ICommandLineParserService > ( projectLanguage ) == null )
229
+ if ( projectLanguage != null && projectFactory . Workspace . Services . GetLanguageService < ICommandLineParserService > ( projectLanguage ) == null )
230
230
{
231
231
return false ;
232
232
}
@@ -246,7 +246,7 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
246
246
var newProjectTargetsBuilder = ArrayBuilder < LoadedProject > . GetInstance ( loadedProjectInfos . Length ) ;
247
247
foreach ( var loadedProjectInfo in loadedProjectInfos )
248
248
{
249
- var ( target , targetAlreadyExists ) = await GetOrCreateProjectTargetAsync ( previousProjectTargets , loadedProjectInfo ) ;
249
+ var ( target , targetAlreadyExists ) = await GetOrCreateProjectTargetAsync ( previousProjectTargets , projectFactory , loadedProjectInfo ) ;
250
250
newProjectTargetsBuilder . Add ( target ) ;
251
251
252
252
var ( targetTelemetryInfo , targetNeedsRestore ) = await target . UpdateWithNewProjectInfoAsync ( loadedProjectInfo , hasAllInformation , _logger ) ;
@@ -272,15 +272,19 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
272
272
await _projectLoadTelemetryReporter . ReportProjectLoadTelemetryAsync ( telemetryInfos , projectToLoad , cancellationToken ) ;
273
273
}
274
274
275
- if ( currentLoadState is ProjectLoadState . Primordial ( var projectId ) )
275
+ if ( currentLoadState is ProjectLoadState . Primordial ( var primordialProjectFactory , var projectId ) )
276
276
{
277
277
// Remove the primordial project now that the design-time build pass is finished. This ensures that
278
278
// we have the new project in place before we remove the primordial project; otherwise for
279
279
// Miscellaneous Files we could have a case where we'd get another request to create a project
280
280
// for the project we're currently processing.
281
- await ProjectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) , cancellationToken ) ;
281
+ await primordialProjectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) , cancellationToken ) ;
282
282
}
283
283
284
+ // At this point we expect that all the loaded projects are now in the project factory returned, and any previous ones have been removed.
285
+ // this is a Debug.Assert() because if this expectation fails, the user's probably still in a state where things will work just fine;
286
+ // throwing here would mean we don't remember the LoadedProjects we created, and the next update will create more and things will get really broken.
287
+ Debug . Assert ( newProjectTargets . All ( target => target . ProjectFactory == projectFactory ) ) ;
284
288
_loadedProjects [ projectPath ] = new ProjectLoadState . LoadedTargets ( newProjectTargets ) ;
285
289
}
286
290
@@ -306,9 +310,9 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
306
310
return false ;
307
311
}
308
312
309
- async Task < ( LoadedProject , bool alreadyExists ) > GetOrCreateProjectTargetAsync ( ImmutableArray < LoadedProject > previousProjectTargets , ProjectFileInfo loadedProjectInfo )
313
+ async Task < ( LoadedProject , bool alreadyExists ) > GetOrCreateProjectTargetAsync ( ImmutableArray < LoadedProject > previousProjectTargets , ProjectSystemProjectFactory projectFactory , ProjectFileInfo loadedProjectInfo )
310
314
{
311
- var existingProject = previousProjectTargets . FirstOrDefault ( p => p . GetTargetFramework ( ) == loadedProjectInfo . TargetFramework ) ;
315
+ var existingProject = previousProjectTargets . FirstOrDefault ( p => p . GetTargetFramework ( ) == loadedProjectInfo . TargetFramework && p . ProjectFactory == projectFactory ) ;
312
316
if ( existingProject != null )
313
317
{
314
318
return ( existingProject , alreadyExists : true ) ;
@@ -324,13 +328,13 @@ private async Task<bool> ReloadProjectAsync(ProjectToLoad projectToLoad, ToastEr
324
328
CompilationOutputAssemblyFilePath = loadedProjectInfo . IntermediateOutputFilePath ,
325
329
} ;
326
330
327
- var projectSystemProject = await ProjectFactory . CreateAndAddToWorkspaceAsync (
331
+ var projectSystemProject = await projectFactory . CreateAndAddToWorkspaceAsync (
328
332
projectSystemName ,
329
333
loadedProjectInfo . Language ,
330
334
projectCreationInfo ,
331
335
_projectSystemHostInfo ) ;
332
336
333
- var loadedProject = new LoadedProject ( projectSystemProject , ProjectFactory . Workspace . Services . SolutionServices , _fileChangeWatcher , _targetFrameworkManager ) ;
337
+ var loadedProject = new LoadedProject ( projectSystemProject , projectFactory , _fileChangeWatcher , _targetFrameworkManager ) ;
334
338
loadedProject . NeedsReload += ( _ , _ ) => _projectsToReload . AddWork ( projectToLoad with { ReportTelemetry = false } ) ;
335
339
return ( loadedProject , alreadyExists : false ) ;
336
340
}
@@ -358,14 +362,22 @@ async Task LogDiagnosticsAsync(ImmutableArray<DiagnosticLogItem> diagnosticLogIt
358
362
}
359
363
}
360
364
365
+ protected async ValueTask < bool > IsProjectLoadedAsync ( string projectPath , CancellationToken cancellationToken )
366
+ {
367
+ using ( await _gate . DisposableWaitAsync ( cancellationToken ) )
368
+ {
369
+ return _loadedProjects . ContainsKey ( projectPath ) ;
370
+ }
371
+ }
372
+
361
373
/// <summary>
362
374
/// Begins loading a project with an associated primordial project. Must not be called for a project which has already begun loading.
363
375
/// </summary>
364
376
/// <param name="doDesignTimeBuild">
365
377
/// If <see langword="true"/>, initiates a design-time build now, and starts file watchers to repeat the design-time build on relevant changes.
366
378
/// If <see langword="false"/>, only tracks the primordial project.
367
379
/// </param>
368
- protected async ValueTask BeginLoadingProjectWithPrimordialAsync ( string projectPath , ProjectId primordialProjectId , bool doDesignTimeBuild )
380
+ protected async ValueTask BeginLoadingProjectWithPrimordialAsync ( string projectPath , ProjectSystemProjectFactory primordialProjectFactory , ProjectId primordialProjectId , bool doDesignTimeBuild )
369
381
{
370
382
using ( await _gate . DisposableWaitAsync ( CancellationToken . None ) )
371
383
{
@@ -377,7 +389,7 @@ protected async ValueTask BeginLoadingProjectWithPrimordialAsync(string projectP
377
389
Contract . Fail ( $ "Cannot begin loading project '{ projectPath } ' because it has already begun loading.") ;
378
390
}
379
391
380
- _loadedProjects . Add ( projectPath , new ProjectLoadState . Primordial ( primordialProjectId ) ) ;
392
+ _loadedProjects . Add ( projectPath , new ProjectLoadState . Primordial ( primordialProjectFactory , primordialProjectId ) ) ;
381
393
if ( doDesignTimeBuild )
382
394
{
383
395
_projectsToReload . AddWork ( new ProjectToLoad ( projectPath , ProjectGuid : null , ReportTelemetry : true ) ) ;
@@ -416,9 +428,9 @@ protected async ValueTask UnloadProjectAsync(string projectPath)
416
428
return ;
417
429
}
418
430
419
- if ( loadState is ProjectLoadState . Primordial ( var projectId ) )
431
+ if ( loadState is ProjectLoadState . Primordial ( var projectFactory , var projectId ) )
420
432
{
421
- await ProjectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) ) ;
433
+ await projectFactory . ApplyChangeToWorkspaceAsync ( workspace => workspace . OnProjectRemoved ( projectId ) ) ;
422
434
}
423
435
else if ( loadState is ProjectLoadState . LoadedTargets ( var existingProjects ) )
424
436
{
0 commit comments