Skip to content

Commit bdb8343

Browse files
Make a lot more things cancellable
- Cancellation tokens are now propagated to all code lens providers. - Provider code that was heavy in synchronous code has some cancellation checks and `Task.Yield` calls to help keep thread pool threads more flexible.
1 parent 591c77e commit bdb8343

File tree

7 files changed

+65
-25
lines changed

7 files changed

+65
-25
lines changed

src/PowerShellEditorServices/Services/CodeLens/ICodeLensProvider.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Threading;
45
using System.Threading.Tasks;
56
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
67
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
@@ -25,8 +26,9 @@ internal interface ICodeLensProvider
2526
/// <param name="scriptFile">
2627
/// The document for which CodeLenses should be provided.
2728
/// </param>
29+
/// <param name="cancellationToken"></param>
2830
/// <returns>An array of CodeLenses.</returns>
29-
CodeLens[] ProvideCodeLenses(ScriptFile scriptFile);
31+
CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken);
3032

3133
/// <summary>
3234
/// Resolves a CodeLens that was created without a Command.
@@ -37,11 +39,13 @@ internal interface ICodeLensProvider
3739
/// <param name="scriptFile">
3840
/// The ScriptFile to resolve it in (sometimes unused).
3941
/// </param>
42+
/// <param name="cancellationToken"></param>
4043
/// <returns>
4144
/// A Task which returns the resolved CodeLens when completed.
4245
/// </returns>
4346
Task<CodeLens> ResolveCodeLens(
4447
CodeLens codeLens,
45-
ScriptFile scriptFile);
48+
ScriptFile scriptFile,
49+
CancellationToken cancellationToken);
4650
}
4751
}

src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33
using System;
44
using System.Collections.Generic;
5+
using System.Threading;
56
using System.Threading.Tasks;
67
using Microsoft.PowerShell.EditorServices.Services;
78
using Microsoft.PowerShell.EditorServices.Services.Symbols;
@@ -99,8 +100,9 @@ private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, Scri
99100
/// Get all Pester CodeLenses for a given script file.
100101
/// </summary>
101102
/// <param name="scriptFile">The script file to get Pester CodeLenses for.</param>
103+
/// <param name="cancellationToken"></param>
102104
/// <returns>All Pester CodeLenses for the given script file.</returns>
103-
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
105+
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
104106
{
105107
// Don't return anything if codelens setting is disabled
106108
if (!_configurationService.CurrentSettings.Pester.CodeLens)
@@ -116,6 +118,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
116118
continue;
117119
}
118120

121+
cancellationToken.ThrowIfCancellationRequested();
119122
if (_configurationService.CurrentSettings.Pester.UseLegacyCodeLens
120123
&& pesterSymbol.Command != PesterCommandType.Describe)
121124
{
@@ -133,8 +136,9 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
133136
/// </summary>
134137
/// <param name="codeLens">The code lens to resolve.</param>
135138
/// <param name="scriptFile">The script file.</param>
139+
/// <param name="cancellationToken"></param>
136140
/// <returns>The given CodeLens, wrapped in a task.</returns>
137-
public Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile) =>
141+
public Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile, CancellationToken cancellationToken) =>
138142
// This provider has no specific behavior for
139143
// resolving CodeLenses.
140144
Task.FromResult(codeLens);

src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs

+21-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Text;
7+
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.PowerShell.EditorServices.Services;
910
using Microsoft.PowerShell.EditorServices.Services.Symbols;
@@ -53,12 +54,14 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ
5354
/// Get all reference code lenses for a given script file.
5455
/// </summary>
5556
/// <param name="scriptFile">The PowerShell script file to get code lenses for.</param>
57+
/// <param name="cancellationToken"></param>
5658
/// <returns>An array of CodeLenses describing all functions in the given script file.</returns>
57-
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
59+
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
5860
{
5961
List<CodeLens> acc = new();
6062
foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile))
6163
{
64+
cancellationToken.ThrowIfCancellationRequested();
6265
if (sym.SymbolType == SymbolType.Function)
6366
{
6467
acc.Add(new CodeLens
@@ -68,7 +71,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
6871
Uri = scriptFile.DocumentUri,
6972
ProviderId = nameof(ReferencesCodeLensProvider)
7073
}, LspSerializer.Instance.JsonSerializer),
71-
Range = sym.ScriptRegion.ToRange()
74+
Range = sym.ScriptRegion.ToRange(),
7275
});
7376
}
7477
}
@@ -81,8 +84,12 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
8184
/// </summary>
8285
/// <param name="codeLens">The old code lens to get updated references for.</param>
8386
/// <param name="scriptFile"></param>
87+
/// <param name="cancellationToken"></param>
8488
/// <returns>A new code lens object describing the same data as the old one but with updated references.</returns>
85-
public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile scriptFile)
89+
public async Task<CodeLens> ResolveCodeLens(
90+
CodeLens codeLens,
91+
ScriptFile scriptFile,
92+
CancellationToken cancellationToken)
8693
{
8794
ScriptFile[] references = _workspaceService.ExpandScriptReferences(
8895
scriptFile);
@@ -93,9 +100,10 @@ public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile script
93100
codeLens.Range.Start.Character + 1);
94101

95102
List<SymbolReference> referencesResult = await _symbolsService.FindReferencesOfSymbol(
96-
foundSymbol,
97-
references,
98-
_workspaceService).ConfigureAwait(false);
103+
foundSymbol,
104+
references,
105+
_workspaceService,
106+
cancellationToken).ConfigureAwait(false);
99107

100108
Location[] referenceLocations;
101109
if (referencesResult == null)
@@ -107,6 +115,10 @@ public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile script
107115
List<Location> acc = new();
108116
foreach (SymbolReference foundReference in referencesResult)
109117
{
118+
// This async method is pretty dense with synchronous code
119+
// so it's helpful to add some yields.
120+
await Task.Yield();
121+
cancellationToken.ThrowIfCancellationRequested();
110122
if (IsReferenceDefinition(foundSymbol, foundReference))
111123
{
112124
continue;
@@ -140,9 +152,9 @@ public async Task<CodeLens> ResolveCodeLens(CodeLens codeLens, ScriptFile script
140152
Title = GetReferenceCountHeader(referenceLocations.Length),
141153
Arguments = JArray.FromObject(new object[]
142154
{
143-
scriptFile.DocumentUri,
144-
codeLens.Range.Start,
145-
referenceLocations
155+
scriptFile.DocumentUri,
156+
codeLens.Range.Start,
157+
referenceLocations
146158
},
147159
LspSerializer.Instance.JsonSerializer)
148160
}

src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Management.Automation.Language;
1212
using System.Runtime.InteropServices;
1313
using System.Text.RegularExpressions;
14+
using System.Threading;
1415
using System.Threading.Tasks;
1516
using Microsoft.Extensions.Logging;
1617
using Microsoft.PowerShell.EditorServices.CodeLenses;
@@ -160,18 +161,25 @@ public static SymbolReference FindSymbolAtLocation(
160161
/// <param name="foundSymbol">The symbol to find all references for</param>
161162
/// <param name="referencedFiles">An array of scriptFiles too search for references in</param>
162163
/// <param name="workspace">The workspace that will be searched for symbols</param>
164+
/// <param name="cancellationToken"></param>
163165
/// <returns>FindReferencesResult</returns>
164166
public async Task<List<SymbolReference>> FindReferencesOfSymbol(
165167
SymbolReference foundSymbol,
166168
ScriptFile[] referencedFiles,
167-
WorkspaceService workspace)
169+
WorkspaceService workspace,
170+
CancellationToken cancellationToken = default)
168171
{
169172
if (foundSymbol == null)
170173
{
171174
return null;
172175
}
173176

174-
(Dictionary<string, List<string>> cmdletToAliases, Dictionary<string, string> aliasToCmdlets) = await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false);
177+
CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync(
178+
_executionService,
179+
cancellationToken).ConfigureAwait(false);
180+
181+
Dictionary<string, List<string>> cmdletToAliases = aliases.CmdletToAliases;
182+
Dictionary<string, string> aliasToCmdlets = aliases.AliasToCmdlets;
175183

176184
// We want to look for references first in referenced files, hence we use ordered dictionary
177185
// TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic
@@ -188,6 +196,10 @@ public async Task<List<SymbolReference>> FindReferencesOfSymbol(
188196
{
189197
if (!fileMap.Contains(filePath))
190198
{
199+
// This async method is pretty dense with synchronous code
200+
// so it's helpful to add some yields.
201+
await Task.Yield();
202+
cancellationToken.ThrowIfCancellationRequested();
191203
if (!workspace.TryGetFile(filePath, out ScriptFile scriptFile))
192204
{
193205
// If we can't access the file for some reason, just ignore it
@@ -223,6 +235,9 @@ public async Task<List<SymbolReference>> FindReferencesOfSymbol(
223235
reference.FilePath = file.FilePath;
224236
symbolReferences.Add(reference);
225237
}
238+
239+
await Task.Yield();
240+
cancellationToken.ThrowIfCancellationRequested();
226241
}
227242

228243
return symbolReferences;

src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeLensHandlers.cs

+7-6
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ public PsesCodeLensHandlers(ILoggerFactory factory, SymbolsService symbolsServic
4141
public override Task<CodeLensContainer> Handle(CodeLensParams request, CancellationToken cancellationToken)
4242
{
4343
ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri);
44-
CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile);
44+
CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile, cancellationToken);
4545
return Task.FromResult(new CodeLensContainer(codeLensResults));
4646
}
4747

48-
public override Task<CodeLens> Handle(CodeLens request, CancellationToken cancellationToken)
48+
public override async Task<CodeLens> Handle(CodeLens request, CancellationToken cancellationToken)
4949
{
5050
// TODO: Catch deserializtion exception on bad object
5151
CodeLensData codeLensData = request.Data.ToObject<CodeLensData>();
@@ -55,18 +55,19 @@ public override Task<CodeLens> Handle(CodeLens request, CancellationToken cancel
5555
.FirstOrDefault(provider => provider.ProviderId.Equals(codeLensData.ProviderId, StringComparison.Ordinal));
5656

5757
ScriptFile scriptFile = _workspaceService.GetFile(codeLensData.Uri);
58-
59-
return originalProvider.ResolveCodeLens(request, scriptFile);
58+
return await originalProvider.ResolveCodeLens(request, scriptFile, cancellationToken)
59+
.ConfigureAwait(false);
6060
}
6161

6262
/// <summary>
6363
/// Get all the CodeLenses for a given script file.
6464
/// </summary>
6565
/// <param name="scriptFile">The PowerShell script file to get CodeLenses for.</param>
66+
/// <param name="cancellationToken"></param>
6667
/// <returns>All generated CodeLenses for the given script file.</returns>
67-
private CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
68+
private CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
6869
{
69-
return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile))
70+
return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile, cancellationToken))
7071
.SelectMany(codeLens => codeLens)
7172
.ToArray();
7273
}

src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public override async Task<LocationContainer> Handle(ReferenceParams request, Ca
4545
await _symbolsService.FindReferencesOfSymbol(
4646
foundSymbol,
4747
_workspaceService.ExpandScriptReferences(scriptFile),
48-
_workspaceService).ConfigureAwait(false);
48+
_workspaceService,
49+
cancellationToken).ConfigureAwait(false);
4950

5051
List<Location> locations = new();
5152

src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public PsesWorkspaceSymbolsHandler(ILoggerFactory loggerFactory, SymbolsService
3232

3333
protected override WorkspaceSymbolRegistrationOptions CreateRegistrationOptions(WorkspaceSymbolCapability capability, ClientCapabilities clientCapabilities) => new() { };
3434

35-
public override Task<Container<SymbolInformation>> Handle(WorkspaceSymbolParams request, CancellationToken cancellationToken)
35+
public override async Task<Container<SymbolInformation>> Handle(WorkspaceSymbolParams request, CancellationToken cancellationToken)
3636
{
3737
List<SymbolInformation> symbols = new();
3838

@@ -47,6 +47,10 @@ public override Task<Container<SymbolInformation>> Handle(WorkspaceSymbolParams
4747

4848
foreach (SymbolReference foundOccurrence in foundSymbols)
4949
{
50+
// This async method is pretty dense with synchronous code
51+
// so it's helpful to add some yields.
52+
await Task.Yield();
53+
cancellationToken.ThrowIfCancellationRequested();
5054
if (!IsQueryMatch(request.Query, foundOccurrence.SymbolName))
5155
{
5256
continue;
@@ -68,8 +72,7 @@ public override Task<Container<SymbolInformation>> Handle(WorkspaceSymbolParams
6872
}
6973
}
7074
_logger.LogWarning("Logging in a handler works now.");
71-
72-
return Task.FromResult(new Container<SymbolInformation>(symbols));
75+
return new Container<SymbolInformation>(symbols);
7376
}
7477

7578
#region private Methods

0 commit comments

Comments
 (0)