15
15
using OmniSharp . Extensions . LanguageServer . Protocol . Models ;
16
16
using OmniSharp . Extensions . LanguageServer . Protocol . Server ;
17
17
using System . Threading ;
18
+ using System . Collections . Concurrent ;
18
19
19
20
namespace Microsoft . PowerShell . EditorServices
20
21
{
21
22
/// <summary>
22
23
/// Provides a high-level service for performing semantic analysis
23
24
/// of PowerShell scripts.
24
25
/// </summary>
25
- public class AnalysisService : IDisposable
26
+ internal class AnalysisService : IDisposable
26
27
{
27
28
#region Static fields
28
29
@@ -54,9 +55,6 @@ public class AnalysisService : IDisposable
54
55
55
56
private static readonly string [ ] s_emptyGetRuleResult = new string [ 0 ] ;
56
57
57
- private Dictionary < string , Dictionary < string , MarkerCorrection > > codeActionsPerFile =
58
- new Dictionary < string , Dictionary < string , MarkerCorrection > > ( ) ;
59
-
60
58
private static CancellationTokenSource s_existingRequestCancellation ;
61
59
62
60
/// <summary>
@@ -96,8 +94,11 @@ public class AnalysisService : IDisposable
96
94
private PSModuleInfo _pssaModuleInfo ;
97
95
98
96
private readonly ILanguageServer _languageServer ;
97
+
99
98
private readonly ConfigurationService _configurationService ;
100
99
100
+ private readonly ConcurrentDictionary < string , ( SemaphoreSlim , Dictionary < string , MarkerCorrection > ) > _mostRecentCorrectionsByFile ;
101
+
101
102
#endregion // Private Fields
102
103
103
104
#region Properties
@@ -149,6 +150,7 @@ private AnalysisService(
149
150
_configurationService = configurationService ;
150
151
_logger = logger ;
151
152
_pssaModuleInfo = pssaModuleInfo ;
153
+ _mostRecentCorrectionsByFile = new ConcurrentDictionary < string , ( SemaphoreSlim , Dictionary < string , MarkerCorrection > ) > ( ) ;
152
154
}
153
155
154
156
#endregion // constructors
@@ -813,26 +815,57 @@ private void PublishScriptDiagnostics(
813
815
ScriptFile scriptFile ,
814
816
List < ScriptFileMarker > markers )
815
817
{
816
- List < Diagnostic > diagnostics = new List < Diagnostic > ( ) ;
818
+ var diagnostics = new List < Diagnostic > ( ) ;
817
819
818
- // Hold on to any corrections that may need to be applied later
819
- Dictionary < string , MarkerCorrection > fileCorrections =
820
- new Dictionary < string , MarkerCorrection > ( ) ;
820
+ // Create the entry for this file if it does not already exist
821
+ SemaphoreSlim fileLock ;
822
+ Dictionary < string , MarkerCorrection > fileCorrections ;
823
+ bool newEntryNeeded = false ;
824
+ if ( _mostRecentCorrectionsByFile . TryGetValue ( scriptFile . DocumentUri , out ( SemaphoreSlim , Dictionary < string , MarkerCorrection > ) fileCorrectionsEntry ) )
825
+ {
826
+ fileLock = fileCorrectionsEntry . Item1 ;
827
+ fileCorrections = fileCorrectionsEntry . Item2 ;
828
+ }
829
+ else
830
+ {
831
+ fileLock = new SemaphoreSlim ( initialCount : 1 , maxCount : 1 ) ;
832
+ fileCorrections = new Dictionary < string , MarkerCorrection > ( ) ;
833
+ newEntryNeeded = true ;
834
+ }
821
835
822
- foreach ( var marker in markers )
836
+ fileLock . Wait ( ) ;
837
+ try
823
838
{
824
- // Does the marker contain a correction?
825
- Diagnostic markerDiagnostic = GetDiagnosticFromMarker ( marker ) ;
826
- if ( marker . Correction != null )
839
+ if ( newEntryNeeded )
827
840
{
828
- string diagnosticId = GetUniqueIdFromDiagnostic ( markerDiagnostic ) ;
829
- fileCorrections [ diagnosticId ] = marker . Correction ;
841
+ // If we create a new entry, we should do it after acquiring the lock we just created
842
+ // to ensure a competing thread can never acquire it first and read invalid information from it
843
+ _mostRecentCorrectionsByFile [ scriptFile . DocumentUri ] = ( fileLock , fileCorrections ) ;
830
844
}
845
+ else
846
+ {
847
+ // Otherwise we need to clear the stale corrections
848
+ fileCorrections . Clear ( ) ;
849
+ }
850
+
851
+ foreach ( ScriptFileMarker marker in markers )
852
+ {
853
+ // Does the marker contain a correction?
854
+ Diagnostic markerDiagnostic = GetDiagnosticFromMarker ( marker ) ;
855
+ if ( marker . Correction != null )
856
+ {
857
+ string diagnosticId = GetUniqueIdFromDiagnostic ( markerDiagnostic ) ;
858
+ fileCorrections [ diagnosticId ] = marker . Correction ;
859
+ }
831
860
832
- diagnostics . Add ( markerDiagnostic ) ;
861
+ diagnostics . Add ( markerDiagnostic ) ;
862
+ }
863
+ }
864
+ finally
865
+ {
866
+ fileLock . Release ( ) ;
833
867
}
834
868
835
- codeActionsPerFile [ scriptFile . DocumentUri ] = fileCorrections ;
836
869
837
870
var uriBuilder = new UriBuilder ( )
838
871
{
@@ -850,17 +883,42 @@ private void PublishScriptDiagnostics(
850
883
} ) ;
851
884
}
852
885
886
+ public async Task < IReadOnlyDictionary < string , MarkerCorrection > > GetMostRecentCodeActionsForFileAsync ( string documentUri )
887
+ {
888
+ if ( ! _mostRecentCorrectionsByFile . TryGetValue ( documentUri , out ( SemaphoreSlim fileLock , Dictionary < string , MarkerCorrection > corrections ) fileCorrectionsEntry ) )
889
+ {
890
+ return null ;
891
+ }
892
+
893
+ await fileCorrectionsEntry . fileLock . WaitAsync ( ) ;
894
+ // We must copy the dictionary for thread safety
895
+ var corrections = new Dictionary < string , MarkerCorrection > ( fileCorrectionsEntry . corrections . Count ) ;
896
+ try
897
+ {
898
+ foreach ( KeyValuePair < string , MarkerCorrection > correction in fileCorrectionsEntry . corrections )
899
+ {
900
+ corrections . Add ( correction . Key , correction . Value ) ;
901
+ }
902
+
903
+ return corrections ;
904
+ }
905
+ finally
906
+ {
907
+ fileCorrectionsEntry . fileLock . Release ( ) ;
908
+ }
909
+ }
910
+
853
911
// Generate a unique id that is used as a key to look up the associated code action (code fix) when
854
912
// we receive and process the textDocument/codeAction message.
855
- private static string GetUniqueIdFromDiagnostic ( Diagnostic diagnostic )
913
+ internal static string GetUniqueIdFromDiagnostic ( Diagnostic diagnostic )
856
914
{
857
915
Position start = diagnostic . Range . Start ;
858
916
Position end = diagnostic . Range . End ;
859
917
860
918
var sb = new StringBuilder ( 256 )
861
919
. Append ( diagnostic . Source ?? "?" )
862
920
. Append ( "_" )
863
- . Append ( diagnostic . Code . ToString ( ) )
921
+ . Append ( diagnostic . Code . IsString ? diagnostic . Code . String : diagnostic . Code . Long . ToString ( ) )
864
922
. Append ( "_" )
865
923
. Append ( diagnostic . Severity ? . ToString ( ) ?? "?" )
866
924
. Append ( "_" )
0 commit comments