Skip to content

Commit 42c028f

Browse files
committed
Converted WorkspaceService.GetFile, TryGetFile, and EnumeratePSFiles to async
Fixed object disposal issue on ScriptFile constructor overload
1 parent e3c0229 commit 42c028f

35 files changed

+467
-342
lines changed

src/PowerShellEditorServices/Extensions/Api/WorkspaceService.cs

+12-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Management.Automation.Language;
8+
using System.Threading.Tasks;
89

910
namespace Microsoft.PowerShell.EditorServices.Extensions.Services
1011
{
@@ -64,22 +65,22 @@ public interface IWorkspaceService
6465
/// </summary>
6566
/// <param name="fileUri">The absolute URI of the file to get.</param>
6667
/// <returns>A representation of the file.</returns>
67-
IEditorScriptFile GetFile(Uri fileUri);
68+
Task<IEditorScriptFile> GetFile(Uri fileUri);
6869

6970
/// <summary>
7071
/// Attempt to get a file within the workspace.
7172
/// </summary>
7273
/// <param name="fileUri">The absolute URI of the file to get.</param>
7374
/// <param name="file">The file, if it was found.</param>
7475
/// <returns>True if the file was found, false otherwise.</returns>
75-
bool TryGetFile(Uri fileUri, out IEditorScriptFile file);
76+
Task<IEditorScriptFile?> TryGetFile(Uri fileUri);
7677

7778
/// <summary>
7879
/// Get all the open files in the editor workspace.
7980
/// The result is not kept up to date as files are opened or closed.
8081
/// </summary>
8182
/// <returns>All open files in the editor workspace.</returns>
82-
IReadOnlyList<IEditorScriptFile> GetOpenedFiles();
83+
Task<IReadOnlyList<IEditorScriptFile>> GetOpenedFiles();
8384
}
8485

8586
internal class EditorScriptFile : IEditorScriptFile
@@ -124,30 +125,28 @@ internal WorkspaceService(
124125

125126
public IReadOnlyList<string> ExcludedFileGlobs { get; }
126127

127-
public IEditorScriptFile GetFile(Uri fileUri) => GetEditorFileFromScriptFile(_workspaceService.GetFile(fileUri));
128+
public async Task<IEditorScriptFile> GetFile(Uri fileUri) => await GetEditorFileFromScriptFile(await _workspaceService.GetFile(fileUri).ConfigureAwait(false)).ConfigureAwait(false);
128129

129-
public bool TryGetFile(Uri fileUri, out IEditorScriptFile file)
130+
public async Task<IEditorScriptFile?> TryGetFile(Uri fileUri)
130131
{
131-
if (!_workspaceService.TryGetFile(fileUri.LocalPath, out ScriptFile scriptFile))
132+
if (await _workspaceService.TryGetFile(fileUri.LocalPath).ConfigureAwait(false) is not ScriptFile scriptFile)
132133
{
133-
file = null;
134-
return false;
134+
return null;
135135
}
136136

137-
file = GetEditorFileFromScriptFile(scriptFile);
138-
return true;
137+
return await GetEditorFileFromScriptFile(scriptFile).ConfigureAwait(false);
139138
}
140139

141-
public IReadOnlyList<IEditorScriptFile> GetOpenedFiles()
140+
public async Task<IReadOnlyList<IEditorScriptFile>> GetOpenedFiles()
142141
{
143142
List<IEditorScriptFile> files = new();
144143
foreach (ScriptFile openedFile in _workspaceService.GetOpenedFiles())
145144
{
146-
files.Add(GetEditorFileFromScriptFile(openedFile));
145+
files.Add(await GetEditorFileFromScriptFile(openedFile).ConfigureAwait(false));
147146
}
148147
return files.AsReadOnly();
149148
}
150149

151-
private static IEditorScriptFile GetEditorFileFromScriptFile(ScriptFile file) => new EditorScriptFile(file);
150+
private static async Task<IEditorScriptFile> GetEditorFileFromScriptFile(ScriptFile file) => new EditorScriptFile(file);
152151
}
153152
}

src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs

+54-26
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace Microsoft.PowerShell.EditorServices.Services
2828
/// </summary>
2929
internal class AnalysisService : IDisposable
3030
{
31+
private readonly SemaphoreSlim _initializationSemaphore = new(1, 1);
3132
/// <summary>
3233
/// Reliably generate an ID for a diagnostic record to track it.
3334
/// </summary>
@@ -91,9 +92,6 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
9192
private readonly int _analysisDelayMillis = 750;
9293

9394
private readonly ConcurrentDictionary<ScriptFile, CorrectionTableEntry> _mostRecentCorrectionsByFile = new();
94-
95-
private Lazy<PssaCmdletAnalysisEngine> _analysisEngineLazy;
96-
9795
private CancellationTokenSource _diagnosticsCancellationTokenSource;
9896

9997
private readonly string _pssaModulePath;
@@ -112,14 +110,37 @@ public AnalysisService(
112110
_languageServer = languageServer;
113111
_configurationService = configurationService;
114112
_workspaceService = workspaceService;
115-
_analysisEngineLazy = new Lazy<PssaCmdletAnalysisEngine>(InstantiateAnalysisEngine);
116113
_pssaModulePath = Path.Combine(hostInfo.BundledModulePath, "PSScriptAnalyzer");
117114
}
118-
115+
private PssaCmdletAnalysisEngine? _analysisEngine;
119116
/// <summary>
120117
/// The analysis engine to use for running script analysis.
121118
/// </summary>
122-
internal PssaCmdletAnalysisEngine AnalysisEngine => _analysisEngineLazy?.Value;
119+
internal PssaCmdletAnalysisEngine? AnalysisEngine
120+
{
121+
get
122+
{
123+
_initializationSemaphore.Wait();
124+
try
125+
{
126+
if (_analysisEngine == null)
127+
{
128+
_analysisEngine = InstantiateAnalysisEngine().GetAwaiter().GetResult();
129+
IsValueCreated = true;
130+
}
131+
}
132+
finally
133+
{
134+
_initializationSemaphore.Release();
135+
}
136+
return _analysisEngine;
137+
}
138+
private set
139+
{
140+
IsValueCreated = true;
141+
_analysisEngine = value;
142+
}
143+
}
123144

124145
/// <summary>
125146
/// Sets up a script analysis run, eventually returning the result.
@@ -215,7 +236,8 @@ public async Task<string> GetCommentHelpText(string functionText, string helpLoc
215236
/// <returns>A thread-safe readonly dictionary of the code actions of the particular file.</returns>
216237
public async Task<IReadOnlyDictionary<string, IEnumerable<MarkerCorrection>>> GetMostRecentCodeActionsForFileAsync(DocumentUri uri)
217238
{
218-
if (!_workspaceService.TryGetFile(uri, out ScriptFile file)
239+
ScriptFile? file = await _workspaceService.TryGetFile(uri).ConfigureAwait(false);
240+
if (file is null
219241
|| !_mostRecentCorrectionsByFile.TryGetValue(file, out CorrectionTableEntry corrections))
220242
{
221243
return null;
@@ -239,7 +261,7 @@ public async Task<IReadOnlyDictionary<string, IEnumerable<MarkerCorrection>>> Ge
239261
/// </summary>
240262
/// <param name="_">The sender of the configuration update event.</param>
241263
/// <param name="settings">The new language server settings.</param>
242-
public void OnConfigurationUpdated(object _, LanguageServerSettings settings)
264+
public async Task OnConfigurationUpdated(LanguageServerSettings settings)
243265
{
244266
if (settings.ScriptAnalysis.Enable)
245267
{
@@ -249,24 +271,25 @@ public void OnConfigurationUpdated(object _, LanguageServerSettings settings)
249271

250272
private void EnsureEngineSettingsCurrent()
251273
{
252-
if (_analysisEngineLazy is null
274+
if (AnalysisEngine is null
253275
|| (_pssaSettingsFilePath is not null
254276
&& !File.Exists(_pssaSettingsFilePath)))
255277
{
256278
InitializeAnalysisEngineToCurrentSettings();
257279
}
258280
}
259281

260-
private void InitializeAnalysisEngineToCurrentSettings()
282+
private async Task InitializeAnalysisEngineToCurrentSettings()
261283
{
284+
262285
// We may be triggered after the lazy factory is set,
263286
// but before it's been able to instantiate
264-
if (_analysisEngineLazy is null)
287+
if (AnalysisEngine is null)
265288
{
266-
_analysisEngineLazy = new Lazy<PssaCmdletAnalysisEngine>(InstantiateAnalysisEngine);
289+
AnalysisEngine = await InstantiateAnalysisEngine().ConfigureAwait(false);
267290
return;
268291
}
269-
else if (!_analysisEngineLazy.IsValueCreated)
292+
else if (IsValueCreated)
270293
{
271294
return;
272295
}
@@ -276,15 +299,16 @@ private void InitializeAnalysisEngineToCurrentSettings()
276299

277300
// Clear the open file markers and set the new engine factory
278301
ClearOpenFileMarkers();
279-
_analysisEngineLazy = new Lazy<PssaCmdletAnalysisEngine>(() => RecreateAnalysisEngine(currentAnalysisEngine));
302+
AnalysisEngine = await RecreateAnalysisEngine(currentAnalysisEngine).ConfigureAwait(false);
280303
}
281304

282-
internal PssaCmdletAnalysisEngine InstantiateAnalysisEngine()
305+
internal async Task<PssaCmdletAnalysisEngine> InstantiateAnalysisEngine()
283306
{
284307
PssaCmdletAnalysisEngine.Builder pssaCmdletEngineBuilder = new(_loggerFactory);
285308

286309
// If there's a settings file use that
287-
if (TryFindSettingsFile(out string settingsFilePath))
310+
string? settingsFilePath = await TryFindSettingsFile().ConfigureAwait(false);
311+
if (!string.IsNullOrEmpty(settingsFilePath))
288312
{
289313
_logger.LogInformation($"Configuring PSScriptAnalyzer with rules at '{settingsFilePath}'");
290314
_pssaSettingsFilePath = settingsFilePath;
@@ -299,9 +323,10 @@ internal PssaCmdletAnalysisEngine InstantiateAnalysisEngine()
299323
return pssaCmdletEngineBuilder.Build(_pssaModulePath);
300324
}
301325

302-
private PssaCmdletAnalysisEngine RecreateAnalysisEngine(PssaCmdletAnalysisEngine oldAnalysisEngine)
326+
private async Task<PssaCmdletAnalysisEngine> RecreateAnalysisEngine(PssaCmdletAnalysisEngine oldAnalysisEngine)
303327
{
304-
if (TryFindSettingsFile(out string settingsFilePath))
328+
string? settingsFilePath = await TryFindSettingsFile().ConfigureAwait(false);
329+
if (!string.IsNullOrEmpty(settingsFilePath))
305330
{
306331
_logger.LogInformation($"Recreating analysis engine with rules at '{settingsFilePath}'");
307332
_pssaSettingsFilePath = settingsFilePath;
@@ -312,27 +337,27 @@ private PssaCmdletAnalysisEngine RecreateAnalysisEngine(PssaCmdletAnalysisEngine
312337
return oldAnalysisEngine.RecreateWithRules(s_defaultRules);
313338
}
314339

315-
private bool TryFindSettingsFile(out string settingsFilePath)
340+
private async Task<string?> TryFindSettingsFile()
316341
{
317342
string configuredPath = _configurationService?.CurrentSettings.ScriptAnalysis.SettingsPath;
318-
343+
string? settingsFilePath;
319344
if (string.IsNullOrEmpty(configuredPath))
320345
{
321346
settingsFilePath = null;
322-
return false;
347+
return settingsFilePath;
323348
}
324349

325-
settingsFilePath = _workspaceService?.ResolveWorkspacePath(configuredPath);
350+
settingsFilePath = await (_workspaceService?.ResolveWorkspacePath(configuredPath)).ConfigureAwait(false);
326351

327352
if (settingsFilePath is null
328353
|| !File.Exists(settingsFilePath))
329354
{
330355
_logger.LogInformation($"Unable to find PSSA settings file at '{configuredPath}'. Loading default rules.");
331356
settingsFilePath = null;
332-
return false;
357+
return settingsFilePath;
333358
}
334359

335-
return true;
360+
return settingsFilePath;
336361
}
337362

338363
private void ClearOpenFileMarkers()
@@ -467,19 +492,22 @@ private static Hashtable GetCommentHelpRuleSettings(string helpLocation, bool fo
467492

468493
#region IDisposable Support
469494
private bool disposedValue; // To detect redundant calls
495+
private bool IsValueCreated;
470496

471497
protected virtual void Dispose(bool disposing)
472498
{
473499
if (!disposedValue)
474500
{
475501
if (disposing)
476502
{
477-
if (_analysisEngineLazy?.IsValueCreated == true)
503+
if (IsValueCreated)
478504
{
479-
_analysisEngineLazy.Value.Dispose();
505+
AnalysisEngine.Dispose();
480506
}
507+
_initializationSemaphore.Dispose();
481508

482509
_diagnosticsCancellationTokenSource?.Dispose();
510+
_analysisEngine?.Dispose();
483511
}
484512

485513
disposedValue = true;

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public BreakpointHandlers(
4949

5050
public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request, CancellationToken cancellationToken)
5151
{
52-
if (!_workspaceService.TryGetFile(request.Source.Path, out ScriptFile scriptFile))
52+
ScriptFile scriptFile = await _workspaceService.TryGetFile(request.Source.Path).ConfigureAwait(false);
53+
if (scriptFile is null)
5354
{
5455
string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set.";
5556
IEnumerable<Breakpoint> srcBreakpoints = request.Breakpoints

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ internal async Task LaunchScriptAsync(string scriptToLaunch)
116116
}
117117
else // It's a URI to an untitled script, or a raw script.
118118
{
119-
bool isScriptFile = _workspaceService.TryGetFile(scriptToLaunch, out ScriptFile untitledScript);
120-
if (isScriptFile && BreakpointApiUtils.SupportsBreakpointApis(_runspaceContext.CurrentRunspace))
119+
ScriptFile untitledScript = await _workspaceService.TryGetFile(scriptToLaunch).ConfigureAwait(false);
120+
if (untitledScript is ScriptFile && BreakpointApiUtils.SupportsBreakpointApis(_runspaceContext.CurrentRunspace))
121121
{
122122
// Parse untitled files with their `Untitled:` URI as the filename which will
123123
// cache the URI and contents within the PowerShell parser. By doing this, we
@@ -149,7 +149,7 @@ internal async Task LaunchScriptAsync(string scriptToLaunch)
149149
command = PSCommandHelpers.BuildDotSourceCommandWithArguments(
150150
string.Concat(
151151
"{" + System.Environment.NewLine,
152-
isScriptFile ? untitledScript.Contents : scriptToLaunch,
152+
untitledScript is ScriptFile ? untitledScript.Contents : scriptToLaunch,
153153
System.Environment.NewLine + "}"),
154154
_debugStateService?.Arguments);
155155
}

src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ public GetCommentHelpHandler(
2727
public async Task<CommentHelpRequestResult> Handle(CommentHelpRequestParams request, CancellationToken cancellationToken)
2828
{
2929
CommentHelpRequestResult result = new();
30-
31-
if (!_workspaceService.TryGetFile(request.DocumentUri, out ScriptFile scriptFile))
30+
ScriptFile? scriptFile = await _workspaceService.TryGetFile(request.DocumentUri).ConfigureAwait(false);
31+
if (scriptFile is null)
3232
{
3333
return result;
3434
}

src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ public PsesInternalHost(
181181
_pipelineThread = new Thread(Run, maxStackSize)
182182
{
183183
Name = "PSES Pipeline Execution Thread",
184+
IsBackground = false
184185
};
185186

186187
if (VersionUtils.IsWindows)

src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs

+12-12
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,6 @@
33

44
#nullable enable
55

6-
using System;
7-
using System.Collections.Concurrent;
8-
using System.Collections.Generic;
9-
using System.Linq;
10-
using System.Management.Automation;
11-
using System.Management.Automation.Language;
12-
using System.Threading;
13-
using System.Threading.Tasks;
146
using Microsoft.Extensions.Logging;
157
using Microsoft.PowerShell.EditorServices.CodeLenses;
168
using Microsoft.PowerShell.EditorServices.Logging;
@@ -21,6 +13,14 @@
2113
using Microsoft.PowerShell.EditorServices.Services.Symbols;
2214
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
2315
using Microsoft.PowerShell.EditorServices.Utility;
16+
using System;
17+
using System.Collections.Concurrent;
18+
using System.Collections.Generic;
19+
using System.Linq;
20+
using System.Management.Automation;
21+
using System.Management.Automation.Language;
22+
using System.Threading;
23+
using System.Threading.Tasks;
2424

2525
namespace Microsoft.PowerShell.EditorServices.Services
2626
{
@@ -333,11 +333,11 @@ internal async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = d
333333
if (scanTask is null)
334334
{
335335
scanTask = Task.Run(
336-
() =>
336+
async () =>
337337
{
338-
foreach (string file in _workspaceService.EnumeratePSFiles())
338+
foreach (string file in await _workspaceService.EnumeratePSFiles(cancellationToken).ConfigureAwait(false))
339339
{
340-
if (_workspaceService.TryGetFile(file, out ScriptFile scriptFile))
340+
if ((await _workspaceService.TryGetFile(file).ConfigureAwait(false)) is ScriptFile scriptFile)
341341
{
342342
scriptFile.References.EnsureInitialized();
343343
}
@@ -457,7 +457,7 @@ internal async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = d
457457
return functionDefinitionAst as FunctionDefinitionAst;
458458
}
459459

460-
internal void OnConfigurationUpdated(object _, LanguageServerSettings e)
460+
internal async Task OnConfigurationUpdated(LanguageServerSettings e)
461461
{
462462
if (e.AnalyzeOpenDocumentsOnly)
463463
{

0 commit comments

Comments
 (0)