Skip to content

Commit 9c28f42

Browse files
authored
Fix LaunchProfilesDebugLaunchProvider async completion handling (#9826)
This change resolves a potential deadlock issue by using IProjectFaultHandlerService.Forget to call OnAfterLaunchAsync The pattern ensures that post-launch async work completes while avoiding deadlocks that could occur when ExecuteSynchronously blocks on async work that needs UI thread access. Signed-off-by: Phil Henning <[email protected]>
1 parent 24ef57c commit 9c28f42

File tree

2 files changed

+30
-15
lines changed

2 files changed

+30
-15
lines changed

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Debug/LaunchProfilesDebugLaunchProvider.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@ internal class LaunchProfilesDebugLaunchProvider : DebugLaunchProviderBase, IDep
2424
private readonly ILaunchSettingsProvider _launchSettingsProvider;
2525
private IDebugProfileLaunchTargetsProvider? _lastLaunchProvider;
2626
private readonly IProjectThreadingService _threadingService;
27+
private readonly IProjectFaultHandlerService _projectFaultHandlerService;
2728

2829
[ImportingConstructor]
2930
public LaunchProfilesDebugLaunchProvider(
3031
ConfiguredProject configuredProject,
3132
ILaunchSettingsProvider launchSettingsProvider,
3233
IVsService<IVsDebuggerLaunchAsync> vsDebuggerService,
34+
IProjectFaultHandlerService projectFaultHandlerService,
3335
[Import(AllowDefault = true)] IProjectThreadingService? threadingService = null)
3436
: base(configuredProject)
3537
{
3638
_launchSettingsProvider = launchSettingsProvider;
3739
_vsDebuggerService = vsDebuggerService;
40+
_projectFaultHandlerService = projectFaultHandlerService;
3841
_threadingService = threadingService ?? ThreadingService;
3942

4043
LaunchTargetsProviders = new OrderPrecedenceImportCollection<IDebugProfileLaunchTargetsProvider>(projectCapabilityCheckProvider: configuredProject.UnconfiguredProject);
@@ -135,11 +138,12 @@ private async Task<IReadOnlyList<IDebugLaunchSettings>> QueryDebugTargetsInterna
135138
}
136139

137140
private sealed class LaunchCompleteCallback(
138-
IProjectThreadingService threadingService,
139141
DebugLaunchOptions launchOptions,
140142
IDebugProfileLaunchTargetsProvider? targetsProvider,
141143
ILaunchProfile activeProfile,
142-
IReadOnlyList<IDebugLaunchSettings> debugLaunchSettings)
144+
IReadOnlyList<IDebugLaunchSettings> debugLaunchSettings,
145+
IProjectFaultHandlerService projectFaultHandlerService,
146+
UnconfiguredProject? unconfiguredProject)
143147
: IVsDebuggerLaunchCompletionCallback, IVsDebugProcessNotify1800
144148
{
145149
private readonly List<IVsLaunchedProcess> _launchedProcesses = new();
@@ -164,16 +168,22 @@ public void OnComplete(int hr, uint debugTargetCount, VsDebugTargetProcessInfo[]
164168
processInfoArray.Length == 1 &&
165169
vsLaunchedProcess is not null)
166170
{
167-
threadingService.ExecuteSynchronously(() => targetsProvider5.OnAfterLaunchAsync(launchOptions, activeProfile, debugLaunchSettings[0], vsLaunchedProcess, processInfoArray[0]));
171+
projectFaultHandlerService.Forget(
172+
targetsProvider5.OnAfterLaunchAsync(launchOptions, activeProfile, debugLaunchSettings[0], vsLaunchedProcess, processInfoArray[0]),
173+
unconfiguredProject);
168174
}
169175
else
170176
{
171-
threadingService.ExecuteSynchronously(() => targetsProvider4.OnAfterLaunchAsync(launchOptions, activeProfile, processInfoArray));
177+
projectFaultHandlerService.Forget(
178+
targetsProvider4.OnAfterLaunchAsync(launchOptions, activeProfile, processInfoArray),
179+
unconfiguredProject);
172180
}
173181
}
174182
else if (targetsProvider is not null)
175183
{
176-
threadingService.ExecuteSynchronously(() => targetsProvider.OnAfterLaunchAsync(launchOptions, activeProfile));
184+
projectFaultHandlerService.Forget(
185+
targetsProvider.OnAfterLaunchAsync(launchOptions, activeProfile),
186+
unconfiguredProject);
177187
}
178188
}
179189

@@ -216,8 +226,8 @@ public async Task LaunchWithProfileAsync(DebugLaunchOptions launchOptions, ILaun
216226
{
217227
await targetProvider.OnBeforeLaunchAsync(launchOptions, profile);
218228
}
219-
220-
LaunchCompleteCallback callback = new(ThreadingService, launchOptions, targetProvider, profile, targets);
229+
230+
LaunchCompleteCallback callback = new(launchOptions, targetProvider, profile, targets, _projectFaultHandlerService, ConfiguredProject?.UnconfiguredProject);
221231

222232
if (targets.Count == 0)
223233
{
@@ -240,7 +250,6 @@ public async Task LaunchWithProfileAsync(DebugLaunchOptions launchOptions, ILaun
240250

241251
// The debugger needs to be called on the UI thread
242252
await _threadingService.SwitchToUIThread(cancellationToken);
243-
244253
shellDebugger.LaunchDebugTargetsAsync((uint)launchSettingsNative.Length, launchSettingsNative, callback);
245254
}
246255
finally

tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/Debug/LaunchProfilesDebugLaunchProviderTests.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,13 @@ public async Task LaunchWithProfileAsync_WhenNotProjectCommand_DoesNotCreateHotR
113113
var mockHotReloadOptionService = IHotReloadOptionServiceFactory.Create();
114114
var mockVsDebuggerService = Mock.Of<IVsDebuggerLaunchAsync>();
115115
var mockProjectThreadingService = IProjectThreadingServiceFactory.Create();
116+
var mockProjectFaultHandlerService = IProjectFaultHandlerServiceFactory.Create();
116117

117118
var provider = new LaunchProfilesDebugLaunchProvider(
118119
_configuredProjectMoq.Object,
119120
_launchSettingsProviderMoq.Object,
120121
IVsServiceFactory.Create(mockVsDebuggerService),
122+
mockProjectFaultHandlerService,
121123
mockProjectThreadingService);
122124

123125
provider.LaunchTargetsProviders.Add(_mockWebProvider.Object); // Use web provider instead
@@ -150,8 +152,9 @@ public async Task LaunchWithProfileAsync_WhenNotProjectCommand_DoesNotCreateHotR
150152
public void OnComplete_WhenHrIsErrorCode_ThrowsException()
151153
{
152154
// Arrange
153-
var mockProjectThreadingService = IProjectThreadingServiceFactory.Create();
154155
var mockTargetsProvider = Mock.Of<IDebugProfileLaunchTargetsProvider>();
156+
var mockProjectFaultHandlerService = IProjectFaultHandlerServiceFactory.Create();
157+
var mockUnconfiguredProject = UnconfiguredProjectFactory.Create();
155158
var profile = new LaunchProfile("TestProfile", "Project");
156159
var launchOptions = DebugLaunchOptions.NoDebug;
157160

@@ -162,11 +165,12 @@ public void OnComplete_WhenHrIsErrorCode_ThrowsException()
162165

163166
var callback = Activator.CreateInstance(
164167
launchCompleteCallbackType,
165-
mockProjectThreadingService,
166168
launchOptions,
167169
mockTargetsProvider,
168170
profile,
169-
(IReadOnlyList<IDebugLaunchSettings>)[]);
171+
(IReadOnlyList<IDebugLaunchSettings>)[],
172+
mockProjectFaultHandlerService,
173+
mockUnconfiguredProject);
170174

171175
// Act & Assert
172176
var onCompleteMethod = launchCompleteCallbackType.GetMethod("OnComplete");
@@ -186,8 +190,9 @@ public void OnComplete_WhenHrIsErrorCode_ThrowsException()
186190
public void OnComplete_WhenHrIsSuccess_DoesNotThrowException()
187191
{
188192
// Arrange
189-
var mockProjectThreadingService = IProjectThreadingServiceFactory.Create();
190193
var mockTargetsProvider = Mock.Of<IDebugProfileLaunchTargetsProvider>();
194+
var mockProjectFaultHandlerService = IProjectFaultHandlerServiceFactory.Create();
195+
var mockUnconfiguredProject = UnconfiguredProjectFactory.Create();
191196
var profile = new LaunchProfile("TestProfile", "Project");
192197
var launchOptions = DebugLaunchOptions.NoDebug;
193198

@@ -198,11 +203,12 @@ public void OnComplete_WhenHrIsSuccess_DoesNotThrowException()
198203

199204
var callback = Activator.CreateInstance(
200205
launchCompleteCallbackType,
201-
mockProjectThreadingService,
202206
launchOptions,
203207
mockTargetsProvider,
204208
profile,
205-
(IReadOnlyList<IDebugLaunchSettings>)[]);
209+
(IReadOnlyList<IDebugLaunchSettings>)[],
210+
mockProjectFaultHandlerService,
211+
mockUnconfiguredProject);
206212

207213
// Act & Assert
208214
var onCompleteMethod = launchCompleteCallbackType.GetMethod("OnComplete");
@@ -217,7 +223,7 @@ public void OnComplete_WhenHrIsSuccess_DoesNotThrowException()
217223

218224
private LaunchProfilesDebugLaunchProvider CreateInstance()
219225
{
220-
var provider = new LaunchProfilesDebugLaunchProvider(_configuredProjectMoq.Object, _launchSettingsProviderMoq.Object, vsDebuggerService: null!);
226+
var provider = new LaunchProfilesDebugLaunchProvider(_configuredProjectMoq.Object, _launchSettingsProviderMoq.Object, vsDebuggerService: null!, projectFaultHandlerService: null!);
221227

222228
provider.LaunchTargetsProviders.Add(_mockWebProvider.Object);
223229
provider.LaunchTargetsProviders.Add(_mockDockerProvider.Object);

0 commit comments

Comments
 (0)