Skip to content

Commit 4303ce9

Browse files
committed
Improve cancellation handling and runspace cleanup
1 parent ad9595e commit 4303ce9

File tree

4 files changed

+88
-33
lines changed

4 files changed

+88
-33
lines changed

src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReplRunner.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ private async Task RunReplLoopAsync()
131131
}
132132
catch (Exception e)
133133
{
134-
// TODO: Do something here
134+
_executionService.EditorServicesHost.UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}");
135+
_logger.LogError(e, "An error occurred while running the REPL loop");
136+
break;
135137
}
136138
finally
137139
{
@@ -202,6 +204,8 @@ public void SetReplPop()
202204

203205
private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args)
204206
{
207+
// We don't want to terminate the process
208+
args.Cancel = true;
205209
CancelCurrentPrompt();
206210
}
207211

src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,12 @@ private IReadOnlyList<TResult> ExecuteNormally(CancellationToken cancellationTok
7373
try
7474
{
7575
result = _pwsh.InvokeCommand<TResult>(_psCommand);
76-
77-
if (_executionOptions.PropagateCancellationToCaller)
78-
{
79-
cancellationToken.ThrowIfCancellationRequested();
80-
}
76+
cancellationToken.ThrowIfCancellationRequested();
8177
}
82-
catch (PipelineStoppedException)
78+
catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException)
8379
{
8480
throw new OperationCanceledException();
8581
}
86-
catch (PSRemotingDataStructureException e)
87-
{
88-
string message = $"Pipeline stopped while executing command:{Environment.NewLine}{Environment.NewLine}{e}";
89-
Logger.LogError(message);
90-
throw new ExecutionCanceledException(message, e);
91-
}
9282
catch (RuntimeException e)
9383
{
9484
Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}");
@@ -139,22 +129,12 @@ private IReadOnlyList<TResult> ExecuteInDebugger(CancellationToken cancellationT
139129
try
140130
{
141131
debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection);
142-
143-
if (_executionOptions.PropagateCancellationToCaller)
144-
{
145-
cancellationToken.ThrowIfCancellationRequested();
146-
}
132+
cancellationToken.ThrowIfCancellationRequested();
147133
}
148-
catch (PipelineStoppedException)
134+
catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException)
149135
{
150136
throw new OperationCanceledException();
151137
}
152-
catch (PSRemotingDataStructureException e)
153-
{
154-
string message = $"Pipeline stopped while executing command:{Environment.NewLine}{Environment.NewLine}{e}";
155-
Logger.LogError(message);
156-
throw new ExecutionCanceledException(message, e);
157-
}
158138
catch (RuntimeException e)
159139
{
160140
Logger.LogWarning($"Runtime exception occurred while executing command:{Environment.NewLine}{Environment.NewLine}{e}");

src/PowerShellEditorServices/Services/PowerShell/PowerShellExecutionService.cs

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.Extensions.Logging;
2+
using Microsoft.PowerShell.EditorServices.Handlers;
23
using Microsoft.PowerShell.EditorServices.Hosting;
34
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console;
45
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
@@ -90,6 +91,8 @@ public static PowerShellExecutionService CreateAndStart(
9091

9192
private readonly ConcurrentStack<CancellationTokenSource> _commandCancellationStack;
9293

94+
private readonly ReaderWriterLockSlim _taskProcessingLock;
95+
9396
private bool _runIdleLoop;
9497

9598
private bool _isExiting;
@@ -114,6 +117,7 @@ private PowerShellExecutionService(
114117
_executionQueue = new BlockingCollection<ISynchronousTask>();
115118
_debuggingContext = new DebuggingContext();
116119
_psFrameStack = new Stack<PowerShellContextFrame>();
120+
_taskProcessingLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
117121
_hostName = hostName;
118122
_hostVersion = hostVersion;
119123
_languageMode = languageMode;
@@ -243,8 +247,6 @@ private void RunTopLevelConsumerLoop()
243247
{
244248
Initialize();
245249

246-
_consoleRepl.StartRepl();
247-
248250
var cancellationContext = LoopCancellationContext.EnterNew(
249251
this,
250252
CurrentPowerShellCancellationSource,
@@ -316,6 +318,8 @@ private void Initialize()
316318
ImportModule(module);
317319
}
318320
}
321+
322+
_consoleRepl.StartRepl();
319323
}
320324

321325
private void SetExecutionPolicy()
@@ -542,7 +546,6 @@ private SMA.PowerShell CreateNestedPowerShell()
542546
return pwsh;
543547
}
544548

545-
546549
private void PushNonInteractivePowerShell()
547550
{
548551
PushNestedPowerShell(PowerShellFrameType.NonInteractive);
@@ -606,12 +609,14 @@ private void AddRunspaceEventHandlers(Runspace runspace)
606609
{
607610
runspace.Debugger.DebuggerStop += OnDebuggerStopped;
608611
runspace.Debugger.BreakpointUpdated += OnBreakpointUpdated;
612+
runspace.StateChanged += OnRunspaceStateChanged;
609613
}
610614

611615
private void RemoveRunspaceEventHandlers(Runspace runspace)
612616
{
613617
runspace.Debugger.DebuggerStop -= OnDebuggerStopped;
614618
runspace.Debugger.BreakpointUpdated -= OnBreakpointUpdated;
619+
runspace.StateChanged -= OnRunspaceStateChanged;
615620
}
616621

617622
private void RunPowerShellLoop(PowerShellFrameType powerShellFrameType)
@@ -667,6 +672,54 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break
667672
BreakpointUpdated?.Invoke(this, breakpointUpdatedEventArgs);
668673
}
669674

675+
private void OnRunspaceStateChanged(object sender, RunspaceStateEventArgs runspaceStateEventArgs)
676+
{
677+
if (!runspaceStateEventArgs.RunspaceStateInfo.IsUsable())
678+
{
679+
PopOrReinitializeRunspace();
680+
}
681+
}
682+
683+
private void PopOrReinitializeRunspace()
684+
{
685+
_consoleRepl.SetReplPop();
686+
CancelCurrentTask();
687+
688+
RunspaceStateInfo oldRunspaceState = CurrentPowerShell.Runspace.RunspaceStateInfo;
689+
_taskProcessingLock.EnterWriteLock();
690+
try
691+
{
692+
while (_psFrameStack.Count > 0
693+
&& !_psFrameStack.Peek().PowerShell.Runspace.RunspaceStateInfo.IsUsable())
694+
{
695+
PopFrame();
696+
}
697+
698+
if (_psFrameStack.Count == 0)
699+
{
700+
// If our main runspace was corrupted,
701+
// we must re-initialize our state.
702+
// TODO: Use runspace.ResetRunspaceState() here instead
703+
Initialize();
704+
705+
_logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized."
706+
+ " Please report this issue in the PowerShell/vscode-PowerShell GitHub repository with these logs.");
707+
EditorServicesHost.UI.WriteErrorLine("The main runspace encountered an error and has been reinitialized. See the PowerShell extension logs for more details.");
708+
}
709+
else
710+
{
711+
_logger.LogError($"Current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was popped.");
712+
EditorServicesHost.UI.WriteErrorLine($"The current runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}'."
713+
+ " If this occurred when using Ctrl+C in a Windows PowerShell remoting session, this is expected behavior."
714+
+ " The session is now returning to the previous runspace.");
715+
}
716+
}
717+
finally
718+
{
719+
_taskProcessingLock.ExitWriteLock();
720+
}
721+
}
722+
670723
internal struct PowerShellRunspaceContext
671724
{
672725
private readonly PowerShellExecutionService _executionService;
@@ -783,24 +836,26 @@ public void Dispose()
783836
{
784837
public static TaskCancellationContext EnterNew(PowerShellExecutionService executionService, CancellationToken loopCancellationToken)
785838
{
839+
executionService._taskProcessingLock.EnterReadLock();
786840
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(loopCancellationToken);
787841
executionService._commandCancellationStack.Push(cancellationTokenSource);
788-
return new TaskCancellationContext(executionService._commandCancellationStack, cancellationTokenSource.Token);
842+
return new TaskCancellationContext(executionService, cancellationTokenSource.Token);
789843
}
790844

791-
private TaskCancellationContext(ConcurrentStack<CancellationTokenSource> commandCancellationStack, CancellationToken cancellationToken)
845+
private TaskCancellationContext(PowerShellExecutionService executionService, CancellationToken cancellationToken)
792846
{
793-
_commandCancellationStack = commandCancellationStack;
847+
_executionService = executionService;
794848
CancellationToken = cancellationToken;
795849
}
796850

797-
private readonly ConcurrentStack<CancellationTokenSource> _commandCancellationStack;
851+
private readonly PowerShellExecutionService _executionService;
798852

799853
public readonly CancellationToken CancellationToken;
800854

801855
public void Dispose()
802856
{
803-
if (_commandCancellationStack.TryPop(out CancellationTokenSource taskCancellation))
857+
_executionService._taskProcessingLock.ExitReadLock();
858+
if (_executionService._commandCancellationStack.TryPop(out CancellationTokenSource taskCancellation))
804859
{
805860
taskCancellation.Dispose();
806861
}

src/PowerShellEditorServices/Services/PowerShell/Utility/RunspaceExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,21 @@ public static string GetRemotePrompt(this Runspace runspace, string basePrompt)
4848
{
4949
return s_getRemotePromptFunc(runspace, basePrompt);
5050
}
51+
52+
public static bool IsUsable(this RunspaceStateInfo runspaceStateInfo)
53+
{
54+
switch (runspaceStateInfo.State)
55+
{
56+
case RunspaceState.Broken:
57+
case RunspaceState.Closed:
58+
case RunspaceState.Closing:
59+
case RunspaceState.Disconnecting:
60+
case RunspaceState.Disconnected:
61+
return false;
62+
63+
default:
64+
return true;
65+
}
66+
}
5167
}
5268
}

0 commit comments

Comments
 (0)