Skip to content

Commit 35fd46d

Browse files
authored
Fix bad context state after debugging (#1210)
Adds a lock around session state eventing. Whenever the lock is held by PowerShell executing a command, the debugger will not try to send events. But whenever the debugger aborting holds the lock, the command processor will wait. This is a workaround to a deeper problem in the threadsafety of the current session state and eventing model and should be fixed later.
1 parent 4f6cd32 commit 35fd46d

File tree

1 file changed

+45
-31
lines changed

1 file changed

+45
-31
lines changed

src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs

+45-31
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ namespace Microsoft.PowerShell.EditorServices.Services
2222
{
2323
using System.Diagnostics.CodeAnalysis;
2424
using System.Management.Automation;
25-
using System.Runtime.InteropServices;
2625
using Microsoft.PowerShell.EditorServices.Handlers;
2726
using Microsoft.PowerShell.EditorServices.Hosting;
28-
using Microsoft.PowerShell.EditorServices.Logging;
2927
using Microsoft.PowerShell.EditorServices.Services.PowerShellContext;
3028

3129
/// <summary>
@@ -68,6 +66,7 @@ static PowerShellContextService()
6866
#region Fields
6967

7068
private readonly SemaphoreSlim resumeRequestHandle = AsyncUtils.CreateSimpleLockingSemaphore();
69+
private readonly SemaphoreSlim sessionStateLock = AsyncUtils.CreateSimpleLockingSemaphore();
7170

7271
private readonly OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServer _languageServer;
7372
private readonly bool isPSReadLineEnabled;
@@ -745,6 +744,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
745744
// Don't change our SessionState for ReadLine.
746745
if (!executionOptions.IsReadLine)
747746
{
747+
await this.sessionStateLock.WaitAsync().ConfigureAwait(false);
748748
shell.InvocationStateChanged += PowerShell_InvocationStateChanged;
749749
}
750750

@@ -768,6 +768,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
768768
if (!executionOptions.IsReadLine)
769769
{
770770
shell.InvocationStateChanged -= PowerShell_InvocationStateChanged;
771+
this.sessionStateLock.Release();
771772
}
772773

773774
if (shell.HadErrors)
@@ -1204,45 +1205,58 @@ public void AbortExecution()
12041205
/// </param>
12051206
public void AbortExecution(bool shouldAbortDebugSession)
12061207
{
1207-
if (this.SessionState != PowerShellContextState.Aborting &&
1208-
this.SessionState != PowerShellContextState.Disposed)
1208+
if (this.SessionState == PowerShellContextState.Aborting
1209+
|| this.SessionState == PowerShellContextState.Disposed)
12091210
{
1210-
this.logger.LogTrace("Execution abort requested...");
1211+
this.logger.LogTrace(
1212+
string.Format(
1213+
$"Execution abort requested when already aborted (SessionState = {this.SessionState})"));
1214+
return;
1215+
}
12111216

1217+
this.logger.LogTrace("Execution abort requested...");
1218+
1219+
if (shouldAbortDebugSession)
1220+
{
1221+
this.ExitAllNestedPrompts();
1222+
}
1223+
1224+
if (this.PromptNest.IsInDebugger)
1225+
{
1226+
this.versionSpecificOperations.StopCommandInDebugger(this);
12121227
if (shouldAbortDebugSession)
12131228
{
1214-
this.ExitAllNestedPrompts();
1229+
this.ResumeDebugger(DebuggerResumeAction.Stop);
12151230
}
1231+
}
1232+
else
1233+
{
1234+
this.PromptNest.GetPowerShell(isReadLine: false).BeginStop(null, null);
1235+
}
12161236

1217-
if (this.PromptNest.IsInDebugger)
1237+
// TODO:
1238+
// This lock and state reset are a temporary fix at best.
1239+
// We need to investigate how the debugger should be interacting
1240+
// with PowerShell in this cancellation scenario.
1241+
//
1242+
// Currently we try to acquire a lock on the execution status changed event.
1243+
// If we can't, it's because a command is executing, so we shouldn't change the status.
1244+
// If we can, we own the status and should fire the event.
1245+
if (this.sessionStateLock.Wait(0))
1246+
{
1247+
try
12181248
{
1219-
if (shouldAbortDebugSession)
1220-
{
1221-
this.versionSpecificOperations.StopCommandInDebugger(this);
1222-
this.ResumeDebugger(DebuggerResumeAction.Stop);
1223-
}
1224-
else
1225-
{
1226-
this.versionSpecificOperations.StopCommandInDebugger(this);
1227-
}
1249+
this.SessionState = PowerShellContextState.Aborting;
1250+
this.OnExecutionStatusChanged(
1251+
ExecutionStatus.Aborted,
1252+
null,
1253+
false);
12281254
}
1229-
else
1255+
finally
12301256
{
1231-
this.PromptNest.GetPowerShell(isReadLine: false).BeginStop(null, null);
1257+
this.SessionState = PowerShellContextState.Ready;
1258+
this.sessionStateLock.Release();
12321259
}
1233-
1234-
this.SessionState = PowerShellContextState.Aborting;
1235-
1236-
this.OnExecutionStatusChanged(
1237-
ExecutionStatus.Aborted,
1238-
null,
1239-
false);
1240-
}
1241-
else
1242-
{
1243-
this.logger.LogTrace(
1244-
string.Format(
1245-
$"Execution abort requested when already aborted (SessionState = {this.SessionState})"));
12461260
}
12471261
}
12481262

0 commit comments

Comments
 (0)