From c229eef00f2acea35f788688251c6711f4812fe4 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 3 Oct 2022 11:51:34 -0700 Subject: [PATCH] WIP: Add cancellation support to formatter --- .../Services/Analysis/AnalysisService.cs | 5 +-- .../Analysis/PssaCmdletAnalysisEngine.cs | 31 ++++++++++++++++--- .../Handlers/FormattingHandlers.cs | 6 ++-- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs index 5b0d81875..8f69e7e45 100644 --- a/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs @@ -178,11 +178,12 @@ public void StartScriptDiagnostics(ScriptFile[] filesToAnalyze) /// The script to format. /// The settings to use with the formatter. /// Optionally, the range that should be formatted. + /// The token used to cancel the task. /// The text of the formatted PowerShell script. - public Task FormatAsync(string scriptFileContents, Hashtable formatSettings, int[] formatRange = null) + public Task FormatAsync(string scriptFileContents, Hashtable formatSettings, int[] formatRange, CancellationToken cancellationToken) { EnsureEngineSettingsCurrent(); - return AnalysisEngine.FormatAsync(scriptFileContents, formatSettings, formatRange); + return AnalysisEngine.FormatAsync(scriptFileContents, formatSettings, formatRange, cancellationToken); } /// diff --git a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs index 371950bc0..92510d3e1 100644 --- a/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs +++ b/src/PowerShellEditorServices/Services/Analysis/PssaCmdletAnalysisEngine.cs @@ -7,6 +7,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Services.TextDocument; @@ -127,8 +128,9 @@ private PssaCmdletAnalysisEngine( /// The full text of a script. /// The formatter settings to use. /// A possible range over which to run the formatter. + /// The token used to cancel the task. /// Formatted script as string - public async Task FormatAsync(string scriptDefinition, Hashtable formatSettings, int[] rangeList) + public async Task FormatAsync(string scriptDefinition, Hashtable formatSettings, int[] rangeList, CancellationToken cancellationToken) { // We cannot use Range type therefore this workaround of using -1 default value. // Invoke-Formatter throws a ParameterBinderValidationException if the ScriptDefinition is an empty string. @@ -148,7 +150,7 @@ public async Task FormatAsync(string scriptDefinition, Hashtable formatS psCommand.AddParameter("Range", rangeList); } - PowerShellResult result = await InvokePowerShellAsync(psCommand).ConfigureAwait(false); + PowerShellResult result = await InvokePowerShellAsync(psCommand, cancellationToken).ConfigureAwait(false); if (result is null) { @@ -156,6 +158,12 @@ public async Task FormatAsync(string scriptDefinition, Hashtable formatS return null; } + if (cancellationToken.IsCancellationRequested) + { + _logger.LogWarning("Formatting request canceled"); + return null; + } + if (result.HasErrors) { StringBuilder errorBuilder = new StringBuilder().Append(s_indentJoin); @@ -257,7 +265,7 @@ public void Dispose() => private async Task GetSemanticMarkersFromCommandAsync(PSCommand command) { - PowerShellResult result = await InvokePowerShellAsync(command).ConfigureAwait(false); + PowerShellResult result = await InvokePowerShellAsync(command, CancellationToken.None).ConfigureAwait(false); IReadOnlyCollection diagnosticResults = result?.Output ?? s_emptyDiagnosticResult; _logger.LogDebug(string.Format("Found {0} violations", diagnosticResults.Count)); @@ -274,11 +282,24 @@ private async Task GetSemanticMarkersFromCommandAsync(PSComm } // TODO: Deduplicate this logic and cleanup using lessons learned from pipeline rewrite. - private Task InvokePowerShellAsync(PSCommand command) => Task.Run(() => InvokePowerShell(command)); + private async Task InvokePowerShellAsync(PSCommand command, CancellationToken cancellationToken) + { + try + { + return await Task.Run(() => InvokePowerShell( + command, + cancellationToken), cancellationToken).ConfigureAwait(false); + } + catch (TaskCanceledException) + { + return await Task.FromCanceled(cancellationToken).ConfigureAwait(false); + } + } - private PowerShellResult InvokePowerShell(PSCommand command) + private PowerShellResult InvokePowerShell(PSCommand command, CancellationToken cancellationToken = default) { using PowerShell pwsh = PowerShell.Create(RunspaceMode.NewRunspace); + using CancellationTokenRegistration registration = cancellationToken.Register(() => pwsh.Stop()); pwsh.RunspacePool = _analysisRunspacePool; pwsh.Commands = command; PowerShellResult result = null; diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs index 5b72f25cd..dd48a5bbc 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/FormattingHandlers.cs @@ -68,7 +68,8 @@ public override async Task Handle(DocumentFormattingParams re formattedScript = await _analysisService.FormatAsync( scriptFile.Contents, pssaSettings, - null).ConfigureAwait(false); + null, + cancellationToken).ConfigureAwait(false); if (formattedScript is null) { @@ -154,7 +155,8 @@ public override async Task Handle(DocumentRangeFormattingPara formattedScript = await _analysisService.FormatAsync( scriptFile.Contents, pssaSettings, - rangeList).ConfigureAwait(false); + rangeList, + cancellationToken).ConfigureAwait(false); if (formattedScript is null) {