Skip to content

Decouple ProjectJsonToPackageReferenceMigrator and VSSolutionManager during solution load, to fix the migrator infinite loop project fails migrating or the project is only project in the solution - #6794

Merged
nkolev92 merged 5 commits into
devfrom
dev-nkolev92-migrator
Sep 23, 2025

Conversation

@nkolev92

@nkolev92 nkolev92 commented Sep 22, 2025 •

Copy link
Copy Markdown
Member

Bug

Fixes: NuGet/Home#14553

Description

VSSolutionManager requires that it's initialized before calls on it can be made.

EnsureNuGetAndVsProjectAdapterCacheAsync for example adds all projects there.
That same method calls the migrator whenever needed.
There are 2 places, but 2 method calls:
GetVsProjectAdapterAsync
GetNuGetProjectAsync
ReloadProjectAsync which calls GetVsProjectAdapterAsync.

GetVsProjectAdapterAsync and GetNuGetProjectAsync are VSSolutionManager methods and as you might've guessed, they call EnsureInitializeAsync which calls EnsureNuGetAndVsProjectAdapterCacheAsync which is where the loop occurs.
You quickly get how this becomes a problem.

This is because the migrator was written for a fully loaded solution in mind.

Fortunately, when we call the migrator at solution load, we actually have project adapter and NuGetProject already, so we can reuse though, so I simply added a method that'll be called during initialization instead of the generic one that'd rely on ISolutionManager.
This is a quick fix that gets rid of that dependency.

The last part is ReloadProjectAsync. I needed to do some history investigation there, the idea being it's something that we'd call whenever a project was partially migrated, like say we write 1 PackageReference and not another.
Unfortunately that doesn't work. Reloading would require doing some other clean-up that frankly should probably be done within the migrator logic itself.
This method has been there for a while though, and failures to migrate projects like this aren't really happening. It's something that requires project-system APIs to fail and these are APIs commonly used for legacy PR projects, so it's very unlikely. Either way, this current implementaiton wasn't helping and given that the migration rarely fails, I've just deleted that method to remove the loop.

This isn't the ideal solution.
Ideally we'd avoid this dance but that's probably a bigger refactoring not worth attempting with a few days left.

PR Checklist

  • Meaningful title, helpful description and a linked NuGet/Home issue
  • Added tests
  • Link to an issue or pull request to update docs if this PR changes settings, environment variables, new feature, etc.
Details

NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.EnsureNuGetAndVsProjectAdapterCacheAsync.AnonymousMethod__114_0() Line 787 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.NuGetLockService.ExecuteNuGetOperationAsync.AnonymousMethod__0() Line 102 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.NuGetLockService.ExecuteNuGetOperationAsync.AnonymousMethod__2() Line 93 C#
[External Code]
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.NuGetLockService.ExecuteNuGetOperationAsync(System.Func<System.Threading.Tasks.Task> action, System.Threading.CancellationToken token) Line 91 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.NuGetLockService.ExecuteNuGetOperationAsync(System.Func<System.Threading.Tasks.Task> action, System.Threading.CancellationToken token) Line 100 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.EnsureNuGetAndVsProjectAdapterCacheAsync() Line 783 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.OnSolutionExistsAndFullyLoadedAsync() Line 575 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.EnsureInitializeAsync.AnonymousMethod__118_0() Line 937 C#
[External Code]
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.EnsureInitializeAsync() Line 922 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.GetNuGetProjectSafeNameAsync(NuGet.ProjectManagement.NuGetProject nuGetProject) Line 309 C#
NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.UpgradeProjectToPackageReferenceAsync(NuGet.ProjectManagement.NuGetProject oldProject) Line 1123 C#
NuGet.VisualStudio.Implementation.dll!NuGet.VisualStudio.Implementation.Extensibility.VsProjectJsonToPackageReferenceMigrator.MigrateProjectToPackageRefAsync(NuGet.VisualStudio.IVsProjectAdapter projectAdapter, NuGet.ProjectManagement.NuGetProject nuGetProject) Line 113 C#
[Resuming Async Method]
[External Code]
[Async Call Stack]
[Async] NuGet.VisualStudio.Implementation.dll!NuGet.VisualStudio.Implementation.Extensibility.VsProjectJsonToPackageReferenceMigrator.MigrateProjectJsonToPackageReferenceAsync(NuGet.ProjectManagement.NuGetProject nuGetProject, NuGet.VisualStudio.IVsProjectAdapter projectAdapter) Line 80 C#
[Async] NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.ExecuteUpgradeProjectJsonNuGetProjectCommandAsync(NuGet.ProjectManagement.NuGetProject nuGetProject, NuGet.VisualStudio.IVsProjectAdapter vsProjectAdapter) Line 762 C#
[Async] NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.MigrateProjectJsonAsync(NuGet.ProjectManagement.Projects.ProjectJsonNuGetProject projectJsonNuGetProject, Microsoft.VisualStudio.Shell.Interop.IVsHierarchy hierarchy, NuGet.VisualStudio.IVsProjectAdapter vsProjectAdapter) Line 831 C#
[Async] NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.VSSolutionManager.EnsureNuGetAndVsProjectAdapterCacheAsync.AnonymousMethod__114_0() Line 801 C#
[Async] NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.NuGetLockService.ExecuteNuGetOperationAsync.AnonymousMethod__0() Line 102 C#
[Async] NuGet.PackageManagement.VisualStudio.dll!NuGet.PackageManagement.VisualStudio.NuGetLockService.ExecuteNuGetOperationAsync.AnonymousMethod__1() Line 71 C#
[Async] [Awaited by multiple tasks/threads]

@nkolev92
nkolev92 marked this pull request as ready for review September 23, 2025 00:59
@nkolev92
nkolev92 requested a review from a team as a code owner September 23, 2025 00:59
@nkolev92 nkolev92 changed the title Fix migrator infinite loop when a project fails migrating Decouple ProjectJsonToPackageReferenceMigrator and VSSolutionManager during solution load, to fix the migrator infinite loop project fails migrating or the project is only project in the solution Sep 23, 2025
@nkolev92

Copy link
Copy Markdown
Member Author

fwiw, NuGet/Home#12044 is probably something we can hit when we have project.json nuget projects even during successful migration.

Easy to check, put a breakpoint in VSSolutionManager.EnsureInitializeAsync and see us subscribe to the same events twice.

donnie-msft
donnie-msft previously approved these changes Sep 23, 2025
Comment thread src/NuGet.Clients/NuGet.VisualStudio.Implementation/GlobalSuppressions.cs Outdated
@nkolev92
nkolev92 enabled auto-merge (squash) September 23, 2025 17:28
@nkolev92
nkolev92 merged commit 9fa1873 into dev Sep 23, 2025
17 of 18 checks passed
@nkolev92
nkolev92 deleted the dev-nkolev92-migrator branch September 23, 2025 19:50
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.

VS crashes when the only project in the solution is a project.json project

3 participants