Skip to content

Commit 3c27ec3

Browse files
Merge pull request #1532 from PowerShell/andschwa/debugger
Make `ExecuteCommandAsync` cancellable
2 parents e29979c + 6a19daf commit 3c27ec3

14 files changed

+69
-60
lines changed

src/PowerShellEditorServices/Server/PsesDebugServer.cs

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public async Task StartAsync()
137137
public void Dispose()
138138
{
139139
_powerShellContextService.IsDebugServerActive = false;
140+
// TODO: If the debugger has stopped, should we clear the breakpoints?
140141
_debugAdapterServer.Dispose();
141142
_inputStream.Dispose();
142143
_outputStream.Dispose();

src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string esc
135135
IEnumerable<Breakpoint> setBreakpoints =
136136
await _powerShellContextService.ExecuteCommandAsync<Breakpoint>(psCommand).ConfigureAwait(false);
137137
configuredBreakpoints.AddRange(
138-
setBreakpoints.Select(BreakpointDetails.Create));
138+
setBreakpoints.Select((breakpoint) => BreakpointDetails.Create(breakpoint))
139+
);
139140
}
140141

141142
return configuredBreakpoints;

src/PowerShellEditorServices/Services/DebugAdapter/DebugEventHandlerService.cs

+21-32
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ e.OriginalEvent.Breakpoints[0] is CommandBreakpoint
8181
new StoppedEvent
8282
{
8383
ThreadId = 1,
84+
AllThreadsStopped = true,
8485
Reason = debuggerStoppedReason
8586
});
8687
}
@@ -117,59 +118,47 @@ private void PowerShellContext_DebuggerResumed(object sender, DebuggerResumeActi
117118
_debugAdapterServer.SendNotification(EventNames.Continued,
118119
new ContinuedEvent
119120
{
120-
AllThreadsContinued = true,
121-
ThreadId = 1
121+
ThreadId = 1,
122+
AllThreadsContinued = true
122123
});
123124
}
124125

125126
private void DebugService_BreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
126127
{
127-
string reason = "changed";
128-
128+
// Don't send breakpoint update notifications when setting
129+
// breakpoints on behalf of the client.
129130
if (_debugStateService.IsSetBreakpointInProgress)
130131
{
131-
// Don't send breakpoint update notifications when setting
132-
// breakpoints on behalf of the client.
133132
return;
134133
}
135134

136-
switch (e.UpdateType)
137-
{
138-
case BreakpointUpdateType.Set:
139-
reason = "new";
140-
break;
141-
142-
case BreakpointUpdateType.Removed:
143-
reason = "removed";
144-
break;
145-
}
146-
147-
var breakpoint = new OmniSharp.Extensions.DebugAdapter.Protocol.Models.Breakpoint
148-
{
149-
Verified = e.UpdateType != BreakpointUpdateType.Disabled
150-
};
151-
152135
if (e.Breakpoint is LineBreakpoint)
153136
{
154-
breakpoint = LspDebugUtils.CreateBreakpoint(BreakpointDetails.Create(e.Breakpoint));
137+
var breakpoint = LspDebugUtils.CreateBreakpoint(
138+
BreakpointDetails.Create(e.Breakpoint, e.UpdateType)
139+
);
140+
141+
string reason = (e.UpdateType) switch {
142+
BreakpointUpdateType.Set => BreakpointEventReason.New,
143+
BreakpointUpdateType.Removed => BreakpointEventReason.Removed,
144+
BreakpointUpdateType.Enabled => BreakpointEventReason.Changed,
145+
BreakpointUpdateType.Disabled => BreakpointEventReason.Changed,
146+
_ => "InvalidBreakpointUpdateTypeEnum"
147+
};
148+
149+
_debugAdapterServer.SendNotification(
150+
EventNames.Breakpoint,
151+
new BreakpointEvent { Breakpoint = breakpoint, Reason = reason }
152+
);
155153
}
156154
else if (e.Breakpoint is CommandBreakpoint)
157155
{
158156
_logger.LogTrace("Function breakpoint updated event is not supported yet");
159-
return;
160157
}
161158
else
162159
{
163160
_logger.LogError($"Unrecognized breakpoint type {e.Breakpoint.GetType().FullName}");
164-
return;
165161
}
166-
167-
_debugAdapterServer.SendNotification(EventNames.Breakpoint,
168-
new BreakpointEvent
169-
{
170-
Reason = reason,
171-
Breakpoint = breakpoint
172-
});
173162
}
174163

175164
#endregion

src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointDetails.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ internal static BreakpointDetails Create(
7777
/// PowerShell Breakpoint object.
7878
/// </summary>
7979
/// <param name="breakpoint">The Breakpoint instance from which details will be taken.</param>
80+
/// <param name="updateType">The BreakpointUpdateType to determine if the breakpoint is verified.</param>
8081
/// <returns>A new instance of the BreakpointDetails class.</returns>
81-
internal static BreakpointDetails Create(Breakpoint breakpoint)
82+
internal static BreakpointDetails Create(
83+
Breakpoint breakpoint,
84+
BreakpointUpdateType updateType = BreakpointUpdateType.Set)
8285
{
8386
Validate.IsNotNull("breakpoint", breakpoint);
8487

@@ -91,7 +94,7 @@ internal static BreakpointDetails Create(Breakpoint breakpoint)
9194
var breakpointDetails = new BreakpointDetails
9295
{
9396
Id = breakpoint.Id,
94-
Verified = true,
97+
Verified = updateType != BreakpointUpdateType.Disabled,
9598
Source = lineBreakpoint.Script,
9699
LineNumber = lineBreakpoint.Line,
97100
ColumnNumber = lineBreakpoint.Column,

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs

+7-5
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,13 @@ await _powerShellContextService.ExecuteScriptStringAsync(
303303
string debugRunspaceCmd;
304304
if (request.RunspaceName != null)
305305
{
306-
IEnumerable<int?> ids = await _powerShellContextService.ExecuteCommandAsync<int?>(new PSCommand()
307-
.AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace")
308-
.AddParameter("Name", request.RunspaceName)
309-
.AddCommand("Microsoft.PowerShell.Utility\\Select-Object")
310-
.AddParameter("ExpandProperty", "Id")).ConfigureAwait(false);
306+
IEnumerable<int?> ids = await _powerShellContextService.ExecuteCommandAsync<int?>(
307+
new PSCommand()
308+
.AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace")
309+
.AddParameter("Name", request.RunspaceName)
310+
.AddCommand("Microsoft.PowerShell.Utility\\Select-Object")
311+
.AddParameter("ExpandProperty", "Id"), cancellationToken: cancellationToken).ConfigureAwait(false);
312+
311313
foreach (var id in ids)
312314
{
313315
_debugStateService.RunspaceId = id;

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ThreadsHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public Task<ThreadsResponse> Handle(ThreadsArguments request, CancellationToken
2020
{
2121
// TODO: OmniSharp supports multithreaded debugging (where
2222
// multiple threads can be debugged at once), but we don't. This
23-
// means we always need to set AllThreadsStoppped and
23+
// means we always need to set AllThreadsStopped and
2424
// AllThreadsContinued in our events. But if we one day support
2525
// multithreaded debugging, we'd need a way to associate
2626
// debugged runspaces with .NET threads in a consistent way.

src/PowerShellEditorServices/Services/PowerShellContext/Console/ConsoleReadLine.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,8 @@ internal async Task<string> InvokeLegacyReadLineAsync(bool isCommandLine, Cancel
208208
command.AddParameter("CursorColumn", currentCursorIndex);
209209
command.AddParameter("Options", null);
210210

211-
var results = await this.powerShellContext
212-
.ExecuteCommandAsync<CommandCompletion>(command, sendOutputToHost: false, sendErrorToHost: false)
213-
.ConfigureAwait(false);
211+
var results = await this.powerShellContext.ExecuteCommandAsync<CommandCompletion>(
212+
command, sendOutputToHost: false, sendErrorToHost: false, cancellationToken).ConfigureAwait(false);
214213

215214
currentCompletion = results.FirstOrDefault();
216215
}
@@ -327,8 +326,8 @@ internal async Task<string> InvokeLegacyReadLineAsync(bool isCommandLine, Cancel
327326
PSCommand command = new PSCommand();
328327
command.AddCommand("Get-History");
329328

330-
currentHistory = await this.powerShellContext.ExecuteCommandAsync<PSObject>(command, sendOutputToHost: false, sendErrorToHost: false)
331-
.ConfigureAwait(false)
329+
currentHistory = await this.powerShellContext.ExecuteCommandAsync<PSObject>(
330+
command, sendOutputToHost: false, sendErrorToHost: false, cancellationToken).ConfigureAwait(false)
332331
as Collection<PSObject>;
333332

334333
if (currentHistory != null)

src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ExpandAliasHandler.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ function __Expand-Alias {
6969
.AddStatement()
7070
.AddCommand("__Expand-Alias")
7171
.AddArgument(request.Text);
72-
var result = await _powerShellContextService.ExecuteCommandAsync<string>(psCommand).ConfigureAwait(false);
72+
var result = await _powerShellContextService.ExecuteCommandAsync<string>(
73+
psCommand, cancellationToken: cancellationToken).ConfigureAwait(false);
7374

7475
return new ExpandAliasResult
7576
{

src/PowerShellEditorServices/Services/PowerShellContext/Handlers/GetCommandHandler.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public async Task<List<PSCommandMessage>> Handle(GetCommandParams request, Cance
5353
.AddCommand("Microsoft.PowerShell.Utility\\Sort-Object")
5454
.AddParameter("Property", "Name");
5555

56-
IEnumerable<CommandInfo> result = await _powerShellContextService.ExecuteCommandAsync<CommandInfo>(psCommand).ConfigureAwait(false);
56+
IEnumerable<CommandInfo> result = await _powerShellContextService.ExecuteCommandAsync<CommandInfo>(
57+
psCommand, cancellationToken: cancellationToken).ConfigureAwait(false);
5758

5859
var commandList = new List<PSCommandMessage>();
5960
if (result != null)

src/PowerShellEditorServices/Services/PowerShellContext/Handlers/PSHostProcessAndRunspaceHandlers.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public async Task<RunspaceResponse[]> Handle(GetRunspaceParams request, Cancella
8787
var psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace");
8888
var sb = new StringBuilder();
8989
// returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later.
90-
runspaces = await _powerShellContextService.ExecuteCommandAsync<PSObject>(psCommand, sb).ConfigureAwait(false);
90+
runspaces = await _powerShellContextService.ExecuteCommandAsync<PSObject>(
91+
psCommand, sb, cancellationToken: cancellationToken).ConfigureAwait(false);
9192
}
9293

9394
var runspaceResponses = new List<RunspaceResponse>();

src/PowerShellEditorServices/Services/PowerShellContext/Handlers/ShowHelpHandler.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ public async Task<Unit> Handle(ShowHelpParams request, CancellationToken cancell
7272

7373
// TODO: Rather than print the help in the console, we should send the string back
7474
// to VSCode to display in a help pop-up (or similar)
75-
await _powerShellContextService.ExecuteCommandAsync<PSObject>(checkHelpPSCommand, sendOutputToHost: true).ConfigureAwait(false);
75+
await _powerShellContextService.ExecuteCommandAsync<PSObject>(
76+
checkHelpPSCommand, sendOutputToHost: true, cancellationToken: cancellationToken).ConfigureAwait(false);
7677
return Unit.Value;
7778
}
7879
}

src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs

+16-8
Original file line numberDiff line numberDiff line change
@@ -519,9 +519,11 @@ public Task<RunspaceHandle> GetRunspaceHandleAsync(CancellationToken cancellatio
519519
public Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
520520
PSCommand psCommand,
521521
bool sendOutputToHost = false,
522-
bool sendErrorToHost = true)
522+
bool sendErrorToHost = true,
523+
CancellationToken cancellationToken = default)
523524
{
524-
return ExecuteCommandAsync<TResult>(psCommand, errorMessages: null, sendOutputToHost, sendErrorToHost);
525+
return this.ExecuteCommandAsync<TResult>(
526+
psCommand, errorMessages: null, sendOutputToHost, sendErrorToHost, cancellationToken: cancellationToken);
525527
}
526528

527529
/// <summary>
@@ -549,7 +551,8 @@ public Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
549551
StringBuilder errorMessages,
550552
bool sendOutputToHost = false,
551553
bool sendErrorToHost = true,
552-
bool addToHistory = false)
554+
bool addToHistory = false,
555+
CancellationToken cancellationToken = default)
553556
{
554557
return
555558
this.ExecuteCommandAsync<TResult>(
@@ -560,7 +563,8 @@ public Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
560563
WriteOutputToHost = sendOutputToHost,
561564
WriteErrorsToHost = sendErrorToHost,
562565
AddToHistory = addToHistory
563-
});
566+
},
567+
cancellationToken);
564568
}
565569

566570

@@ -581,7 +585,8 @@ public Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
581585
public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
582586
PSCommand psCommand,
583587
StringBuilder errorMessages,
584-
ExecutionOptions executionOptions)
588+
ExecutionOptions executionOptions,
589+
CancellationToken cancellationToken = default)
585590
{
586591
Validate.IsNotNull(nameof(psCommand), psCommand);
587592
Validate.IsNotNull(nameof(executionOptions), executionOptions);
@@ -759,8 +764,11 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
759764
return shell.Invoke<TResult>(null, invocationSettings);
760765
}
761766

762-
// May need a cancellation token here
763-
return await Task.Run<IEnumerable<TResult>>(() => shell.Invoke<TResult>(input: null, invocationSettings), CancellationToken.None).ConfigureAwait(false);
767+
// This is the primary reason that ExecuteCommandAsync takes a CancellationToken
768+
cancellationToken.Register(() => shell.Stop());
769+
return await Task.Run<IEnumerable<TResult>>(
770+
() => shell.Invoke<TResult>(input: null, invocationSettings), cancellationToken)
771+
.ConfigureAwait(false);
764772
}
765773
finally
766774
{
@@ -872,7 +880,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
872880
// will exist already so we need to create one and then use it
873881
if (runspaceHandle == null)
874882
{
875-
runspaceHandle = await this.GetRunspaceHandleAsync().ConfigureAwait(false);
883+
runspaceHandle = await this.GetRunspaceHandleAsync(cancellationToken).ConfigureAwait(false);
876884
}
877885

878886
sessionDetails = this.GetSessionDetailsInRunspace(runspaceHandle.Runspace);

src/PowerShellEditorServices/Services/PowerShellContext/Session/Host/EditorServicesPSHostUserInterface.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,8 @@ private async Task WritePromptStringToHostAsync(CancellationToken cancellationTo
731731

732732
cancellationToken.ThrowIfCancellationRequested();
733733
string promptString =
734-
(await this.powerShellContext.ExecuteCommandAsync<PSObject>(promptCommand, false, false).ConfigureAwait(false))
734+
(await this.powerShellContext.ExecuteCommandAsync<PSObject>(
735+
promptCommand, false, false, cancellationToken).ConfigureAwait(false))
735736
.Select(pso => pso.BaseObject)
736737
.OfType<string>()
737738
.FirstOrDefault() ?? "PS> ";

src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ public async Task<string> InvokeReadLineAsync(bool isCommandLine, CancellationTo
143143
IEnumerable<string> readLineResults = await _powerShellContext.ExecuteCommandAsync<string>(
144144
readLineCommand,
145145
errorMessages: null,
146-
s_psrlExecutionOptions).ConfigureAwait(false);
146+
s_psrlExecutionOptions,
147+
cancellationToken).ConfigureAwait(false);
147148

148149
string line = readLineResults.FirstOrDefault();
149150

0 commit comments

Comments
 (0)