Skip to content

Commit 9ce4762

Browse files
Exit debugger stop early if cause is PSE (#1818)
If the `ErrorActionPreference` is set to `Break` and the user hits the Stop button in the UI, then we need to exit early on reentrance of OnDebuggerStop. Currently we deadlock until they hit stop again.
1 parent a66d9d8 commit 9ce4762

File tree

1 file changed

+46
-0
lines changed

1 file changed

+46
-0
lines changed

src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

+46
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Globalization;
77
using System.IO;
88
using System.Management.Automation.Host;
9+
using System.Reflection;
910
using System.Text;
1011
using System.Threading;
1112
using System.Threading.Tasks;
@@ -39,6 +40,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
3940
private static string CommandsModulePath => Path.GetFullPath(Path.Combine(
4041
s_bundledModulePath, "PowerShellEditorServices", "Commands", "PowerShellEditorServices.Commands.psd1"));
4142

43+
private static readonly PropertyInfo s_scriptDebuggerTriggerObjectProperty;
44+
4245
private readonly ILoggerFactory _loggerFactory;
4346

4447
private readonly ILogger _logger;
@@ -89,6 +92,21 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
8992

9093
private bool _resettingRunspace;
9194

95+
static PsesInternalHost()
96+
{
97+
Type scriptDebuggerType = typeof(PSObject).Assembly
98+
.GetType("System.Management.Automation.ScriptDebugger");
99+
100+
if (scriptDebuggerType is null)
101+
{
102+
return;
103+
}
104+
105+
s_scriptDebuggerTriggerObjectProperty = scriptDebuggerType.GetProperty(
106+
"TriggerObject",
107+
BindingFlags.Instance | BindingFlags.NonPublic);
108+
}
109+
92110
public PsesInternalHost(
93111
ILoggerFactory loggerFactory,
94112
ILanguageServerFacade languageServer,
@@ -1142,6 +1160,34 @@ internal void WaitForExternalDebuggerStops()
11421160

11431161
private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs)
11441162
{
1163+
// If ErrorActionPreference is set to Break, any engine exception is going to trigger a
1164+
// pipeline stop. Technically this is the same behavior as a standalone PowerShell
1165+
// process, but we use pipeline stops with greater frequency due to features like run
1166+
// selection and terminating the debugger. Without this, if the "Stop" button is pressed
1167+
// then we hit this repeatedly.
1168+
//
1169+
// This info is publically accessible via `PSDebugContext` but we'd need to access it
1170+
// via a script. At this point in the call I'd prefer this to be as light as possible so
1171+
// we can escape ASAP but we may want to consider switching to that at some point.
1172+
if (!Runspace.RunspaceIsRemote && s_scriptDebuggerTriggerObjectProperty is not null)
1173+
{
1174+
object triggerObject = null;
1175+
try
1176+
{
1177+
triggerObject = s_scriptDebuggerTriggerObjectProperty.GetValue(Runspace.Debugger);
1178+
}
1179+
catch
1180+
{
1181+
// Ignore all exceptions. There shouldn't be any, but as this is implementation
1182+
// detail that is subject to change it's best to be overly cautious.
1183+
}
1184+
1185+
if (triggerObject is PipelineStoppedException pse)
1186+
{
1187+
throw pse;
1188+
}
1189+
}
1190+
11451191
// The debugger has officially started. We use this to later check if we should stop it.
11461192
DebugContext.IsActive = true;
11471193

0 commit comments

Comments
 (0)