@@ -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 ;
0 commit comments