@@ -28,6 +28,7 @@ namespace Microsoft.PowerShell.EditorServices.Services
28
28
/// </summary>
29
29
internal class AnalysisService : IDisposable
30
30
{
31
+ private readonly SemaphoreSlim _initializationSemaphore = new ( 1 , 1 ) ;
31
32
/// <summary>
32
33
/// Reliably generate an ID for a diagnostic record to track it.
33
34
/// </summary>
@@ -91,9 +92,6 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
91
92
private readonly int _analysisDelayMillis = 750 ;
92
93
93
94
private readonly ConcurrentDictionary < ScriptFile , CorrectionTableEntry > _mostRecentCorrectionsByFile = new ( ) ;
94
-
95
- private Lazy < PssaCmdletAnalysisEngine > _analysisEngineLazy ;
96
-
97
95
private CancellationTokenSource _diagnosticsCancellationTokenSource ;
98
96
99
97
private readonly string _pssaModulePath ;
@@ -112,14 +110,37 @@ public AnalysisService(
112
110
_languageServer = languageServer ;
113
111
_configurationService = configurationService ;
114
112
_workspaceService = workspaceService ;
115
- _analysisEngineLazy = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
116
113
_pssaModulePath = Path . Combine ( hostInfo . BundledModulePath , "PSScriptAnalyzer" ) ;
117
114
}
118
-
115
+ private PssaCmdletAnalysisEngine ? _analysisEngine ;
119
116
/// <summary>
120
117
/// The analysis engine to use for running script analysis.
121
118
/// </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
+ }
123
144
124
145
/// <summary>
125
146
/// Sets up a script analysis run, eventually returning the result.
@@ -215,7 +236,8 @@ public async Task<string> GetCommentHelpText(string functionText, string helpLoc
215
236
/// <returns>A thread-safe readonly dictionary of the code actions of the particular file.</returns>
216
237
public async Task < IReadOnlyDictionary < string , IEnumerable < MarkerCorrection > > > GetMostRecentCodeActionsForFileAsync ( DocumentUri uri )
217
238
{
218
- if ( ! _workspaceService . TryGetFile ( uri , out ScriptFile file )
239
+ ScriptFile ? file = await _workspaceService . TryGetFile ( uri ) . ConfigureAwait ( false ) ;
240
+ if ( file is null
219
241
|| ! _mostRecentCorrectionsByFile . TryGetValue ( file , out CorrectionTableEntry corrections ) )
220
242
{
221
243
return null ;
@@ -239,7 +261,7 @@ public async Task<IReadOnlyDictionary<string, IEnumerable<MarkerCorrection>>> Ge
239
261
/// </summary>
240
262
/// <param name="_">The sender of the configuration update event.</param>
241
263
/// <param name="settings">The new language server settings.</param>
242
- public void OnConfigurationUpdated ( object _ , LanguageServerSettings settings )
264
+ public async Task OnConfigurationUpdated ( LanguageServerSettings settings )
243
265
{
244
266
if ( settings . ScriptAnalysis . Enable )
245
267
{
@@ -249,24 +271,25 @@ public void OnConfigurationUpdated(object _, LanguageServerSettings settings)
249
271
250
272
private void EnsureEngineSettingsCurrent ( )
251
273
{
252
- if ( _analysisEngineLazy is null
274
+ if ( AnalysisEngine is null
253
275
|| ( _pssaSettingsFilePath is not null
254
276
&& ! File . Exists ( _pssaSettingsFilePath ) ) )
255
277
{
256
278
InitializeAnalysisEngineToCurrentSettings ( ) ;
257
279
}
258
280
}
259
281
260
- private void InitializeAnalysisEngineToCurrentSettings ( )
282
+ private async Task InitializeAnalysisEngineToCurrentSettings ( )
261
283
{
284
+
262
285
// We may be triggered after the lazy factory is set,
263
286
// but before it's been able to instantiate
264
- if ( _analysisEngineLazy is null )
287
+ if ( AnalysisEngine is null )
265
288
{
266
- _analysisEngineLazy = new Lazy < PssaCmdletAnalysisEngine > ( InstantiateAnalysisEngine ) ;
289
+ AnalysisEngine = await InstantiateAnalysisEngine ( ) . ConfigureAwait ( false ) ;
267
290
return ;
268
291
}
269
- else if ( ! _analysisEngineLazy . IsValueCreated )
292
+ else if ( IsValueCreated )
270
293
{
271
294
return ;
272
295
}
@@ -276,15 +299,16 @@ private void InitializeAnalysisEngineToCurrentSettings()
276
299
277
300
// Clear the open file markers and set the new engine factory
278
301
ClearOpenFileMarkers ( ) ;
279
- _analysisEngineLazy = new Lazy < PssaCmdletAnalysisEngine > ( ( ) => RecreateAnalysisEngine ( currentAnalysisEngine ) ) ;
302
+ AnalysisEngine = await RecreateAnalysisEngine ( currentAnalysisEngine ) . ConfigureAwait ( false ) ;
280
303
}
281
304
282
- internal PssaCmdletAnalysisEngine InstantiateAnalysisEngine ( )
305
+ internal async Task < PssaCmdletAnalysisEngine > InstantiateAnalysisEngine ( )
283
306
{
284
307
PssaCmdletAnalysisEngine . Builder pssaCmdletEngineBuilder = new ( _loggerFactory ) ;
285
308
286
309
// 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 ) )
288
312
{
289
313
_logger . LogInformation ( $ "Configuring PSScriptAnalyzer with rules at '{ settingsFilePath } '") ;
290
314
_pssaSettingsFilePath = settingsFilePath ;
@@ -299,9 +323,10 @@ internal PssaCmdletAnalysisEngine InstantiateAnalysisEngine()
299
323
return pssaCmdletEngineBuilder . Build ( _pssaModulePath ) ;
300
324
}
301
325
302
- private PssaCmdletAnalysisEngine RecreateAnalysisEngine ( PssaCmdletAnalysisEngine oldAnalysisEngine )
326
+ private async Task < PssaCmdletAnalysisEngine > RecreateAnalysisEngine ( PssaCmdletAnalysisEngine oldAnalysisEngine )
303
327
{
304
- if ( TryFindSettingsFile ( out string settingsFilePath ) )
328
+ string ? settingsFilePath = await TryFindSettingsFile ( ) . ConfigureAwait ( false ) ;
329
+ if ( ! string . IsNullOrEmpty ( settingsFilePath ) )
305
330
{
306
331
_logger . LogInformation ( $ "Recreating analysis engine with rules at '{ settingsFilePath } '") ;
307
332
_pssaSettingsFilePath = settingsFilePath ;
@@ -312,27 +337,27 @@ private PssaCmdletAnalysisEngine RecreateAnalysisEngine(PssaCmdletAnalysisEngine
312
337
return oldAnalysisEngine . RecreateWithRules ( s_defaultRules ) ;
313
338
}
314
339
315
- private bool TryFindSettingsFile ( out string settingsFilePath )
340
+ private async Task < string ? > TryFindSettingsFile ( )
316
341
{
317
342
string configuredPath = _configurationService ? . CurrentSettings . ScriptAnalysis . SettingsPath ;
318
-
343
+ string ? settingsFilePath ;
319
344
if ( string . IsNullOrEmpty ( configuredPath ) )
320
345
{
321
346
settingsFilePath = null ;
322
- return false ;
347
+ return settingsFilePath ;
323
348
}
324
349
325
- settingsFilePath = _workspaceService ? . ResolveWorkspacePath ( configuredPath ) ;
350
+ settingsFilePath = await ( _workspaceService ? . ResolveWorkspacePath ( configuredPath ) ) . ConfigureAwait ( false ) ;
326
351
327
352
if ( settingsFilePath is null
328
353
|| ! File . Exists ( settingsFilePath ) )
329
354
{
330
355
_logger . LogInformation ( $ "Unable to find PSSA settings file at '{ configuredPath } '. Loading default rules.") ;
331
356
settingsFilePath = null ;
332
- return false ;
357
+ return settingsFilePath ;
333
358
}
334
359
335
- return true ;
360
+ return settingsFilePath ;
336
361
}
337
362
338
363
private void ClearOpenFileMarkers ( )
@@ -467,19 +492,22 @@ private static Hashtable GetCommentHelpRuleSettings(string helpLocation, bool fo
467
492
468
493
#region IDisposable Support
469
494
private bool disposedValue ; // To detect redundant calls
495
+ private bool IsValueCreated ;
470
496
471
497
protected virtual void Dispose ( bool disposing )
472
498
{
473
499
if ( ! disposedValue )
474
500
{
475
501
if ( disposing )
476
502
{
477
- if ( _analysisEngineLazy ? . IsValueCreated == true )
503
+ if ( IsValueCreated )
478
504
{
479
- _analysisEngineLazy . Value . Dispose ( ) ;
505
+ AnalysisEngine . Dispose ( ) ;
480
506
}
507
+ _initializationSemaphore . Dispose ( ) ;
481
508
482
509
_diagnosticsCancellationTokenSource ? . Dispose ( ) ;
510
+ _analysisEngine ? . Dispose ( ) ;
483
511
}
484
512
485
513
disposedValue = true ;
0 commit comments