Reduce blocking time in UpdateSolution_QueryDelayBuildAction#7454
Reduce blocking time in UpdateSolution_QueryDelayBuildAction#7454Erarndt wants to merge 2 commits into
Conversation
|
Let's update the PR title |
Updated :) |
donnie-msft
left a comment
There was a problem hiding this comment.
Is there evidence that this is resolving the issue you described, or is it just something to try and observe its impact?
Had a few questions inline...
| // explicit switch to a background thread, awaits inside the lambda (e.g. | ||
| // GetComponentModelAsync once the service is cached) can complete synchronously and | ||
| // run MEF composition on the caller's thread before pDelayTask is assigned. | ||
| pDelayTask = NuGetUIThreadHelper.JoinableTaskFactory.RunAsyncAsVsTask( |
There was a problem hiding this comment.
Existing code was using the SolutionRestoreWorker.Value.JoinableTaskFactory.
What is the justification for this change?
There was a problem hiding this comment.
I think in the new code SolutionRestoreWorker is manually imported in line 148 instead of using componentModel.DefaultCompositionService.SatisfyImportsOnce(this);
There was a problem hiding this comment.
Yes, there's additional overhead with SatisfyImportsOnce() compared to manually importing such as needing to reflect over the type to find import attributes.
| // Resolve imports directly from the export provider rather than calling | ||
| // SatisfyImportsOnce(this), which reflects over this type, validates | ||
| // composability of the import graph, and is significantly more expensive. | ||
| // The [Import] attributes above are kept for documentation only. |
There was a problem hiding this comment.
Would these imports still apply if SatisfyImportsOnce was called again?
There was a problem hiding this comment.
Could you clarify? Taking a guess at what you might mean, yes, they would still apply. If SatisfyImportsOnce() runs after the manual assignment, it can reassign the properties, but it would be with the same instance. The same thing is true for the reverse order of execution.
There was a problem hiding this comment.
I'm not sure what "for documentation only" means in this case.
There was a problem hiding this comment.
The intent is to signal that they're satisfied via MEF. I can change it to a comment or something. Whatever works best.
I'm not understanding some of the listed items. Such as:
I assume this means VS Options pages, such as PackageSourceMapping. There are no MEF imports in this page, but it does rely on |
| componentModel.DefaultCompositionService.SatisfyImportsOnce(this); | ||
| await TaskScheduler.Default.SwitchTo(alwaysYield: true); | ||
|
|
||
| if (!_isMEFInitialized) |
There was a problem hiding this comment.
now that this is used async, is there a race condition concern here? TBF the wors that could happen is we write these values below multiple times.
There was a problem hiding this comment.
The query and write to the property can happen multiple times in some narrow cases. It should be pretty unlikely in the current implementation though since it would require the user to trigger a build, cancel it, and then quickly try to build again. If there's a concern, I can update things to be more resilient.
I do not have a way to reproduce the issue locally, so I can't directly validate the fix. I had to use telemetry to diagnose the issue in the wild. There are multiple instrumentation points, and the issue I'm attempting to address in this PR is the synchronous blocking in The change to the import flow isn't required to stop the synchronous blocking, but telemetry indicates that this code is rather slow (taking over 17s in a few instances). This was my attempt at speeding things up. |
It's entirely possible that list is inaccurate. That was a quick attempt to see what might have changed to make this path slower recently. This seems like a relatively new issue, so I took a quick look. I can't reproduce this locally, so I can't profile. |
| if (!_isMEFInitialized) | ||
| { | ||
| ThreadHelper.JoinableTaskFactory.Run(async () => | ||
| // Force a yield before any work so this method returns immediately. Without the |
There was a problem hiding this comment.
I think this PR may have caused a regression.
30+ tests timed out.
These tests aren't perfect, a bunch of have flakiness, https://github.com/NuGet/Client.Engineering/issues/3827, but shouldn't be 30.
I re-ran the tests;
https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=14300232&view=results
Bug
Fixes:
Description
Users are reporting that there is an occasional lengthy delay before build starts. Some early telemetry shows that it can take over 10s just to construct the
IVsTaskthat gets returned. This isn't the time to finish the restore that the task initiates.The code now unconditionally yields as a first step to return to the caller immediately. Additionally, switching to explicit
GetExport()calls avoid some of the cost associated withSatisfyImportsOnce(). There might be a few innacuracies here, but it looks like there were some recent exports/services that were added that could explain why the MEF construction is showing up now:IVulnerabilitiesNotificationService)PR Checklist