@@ -54,6 +54,10 @@ public ProjectEntry(IProject project, IUnresolvedFile unresolvedFile, ParseInfor
5454 List < ProjectEntry > entries = new List < ProjectEntry > { default ( ProjectEntry ) } ;
5555 ITextSourceVersion currentVersion ;
5656
57+ // Lock ordering: runningAsyncParseLock, rwLock, lock(parserService.fileEntryDict)
58+ // (to avoid deadlocks, the locks may only be acquired in this order)
59+ internal readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim ( LockRecursionPolicy . NoRecursion ) ;
60+
5761 public ParserServiceEntry ( ParserService parserService , FileName fileName )
5862 {
5963 this . parserService = parserService ;
@@ -68,6 +72,7 @@ IProject PrimaryProject {
6872
6973 int FindIndexForProject ( IProject parentProject )
7074 {
75+ Debug . Assert ( rwLock . IsReadLockHeld || rwLock . IsUpgradeableReadLockHeld || rwLock . IsWriteLockHeld ) ;
7176 if ( parentProject == null )
7277 return 0 ;
7378 for ( int i = 0 ; i < entries . Count ; i ++ ) {
@@ -81,7 +86,8 @@ int FindIndexForProject(IProject parentProject)
8186 public void AddOwnerProject ( IProject project , bool isLinkedFile )
8287 {
8388 Debug . Assert ( project != null ) ;
84- lock ( this ) {
89+ rwLock . EnterWriteLock ( ) ;
90+ try {
8591 if ( FindIndexForProject ( project ) >= 0 )
8692 throw new InvalidOperationException ( "The project alreadys owns the file" ) ;
8793 ProjectEntry newEntry = new ProjectEntry ( project , null , null ) ;
@@ -92,6 +98,8 @@ public void AddOwnerProject(IProject project, bool isLinkedFile)
9298 } else {
9399 entries . Insert ( 0 , newEntry ) ;
94100 }
101+ } finally {
102+ rwLock . ExitWriteLock ( ) ;
95103 }
96104 }
97105
@@ -100,7 +108,8 @@ public void RemoveOwnerProject(IProject project)
100108 Debug . Assert ( project != null ) ;
101109 ProjectEntry oldEntry ;
102110 bool removedLastOwner = false ;
103- lock ( this ) {
111+ rwLock . EnterWriteLock ( ) ;
112+ try {
104113 int index = FindIndexForProject ( project ) ;
105114 if ( index < 0 )
106115 throw new InvalidOperationException ( "The project does not own the file" ) ;
@@ -111,6 +120,8 @@ public void RemoveOwnerProject(IProject project)
111120 } else {
112121 entries . RemoveAt ( index ) ;
113122 }
123+ } finally {
124+ rwLock . ExitWriteLock ( ) ;
114125 }
115126 if ( oldEntry . UnresolvedFile != null ) {
116127 project . OnParseInformationUpdated ( new ParseInformationEventArgs ( project , oldEntry . UnresolvedFile , null ) ) ;
@@ -128,6 +139,7 @@ public void RemoveOwnerProject(IProject project)
128139 /// </summary>
129140 int CompareVersions ( ITextSourceVersion newVersion )
130141 {
142+ Debug . Assert ( rwLock . IsReadLockHeld || rwLock . IsUpgradeableReadLockHeld || rwLock . IsWriteLockHeld ) ;
131143 if ( currentVersion != null && newVersion != null && currentVersion . BelongsToSameDocumentAs ( newVersion ) )
132144 return currentVersion . CompareAge ( newVersion ) ;
133145 else
@@ -137,7 +149,8 @@ int CompareVersions(ITextSourceVersion newVersion)
137149 #region Expire Cache + GetExistingUnresolvedFile + GetCachedParseInformation
138150 public void ExpireCache ( )
139151 {
140- lock ( this ) {
152+ rwLock . EnterWriteLock ( ) ;
153+ try {
141154 if ( PrimaryProject == null ) {
142155 parserService . RemoveEntry ( this ) ;
143156 } else {
@@ -148,32 +161,40 @@ public void ExpireCache()
148161 }
149162 // force re-parse on next ParseFile() call even if unchanged
150163 this . currentVersion = null ;
164+ } finally {
165+ rwLock . ExitWriteLock ( ) ;
151166 }
152167 }
153168
154169 public IUnresolvedFile GetExistingUnresolvedFile ( ITextSourceVersion version , IProject parentProject )
155170 {
156- lock ( this ) {
171+ rwLock . EnterReadLock ( ) ;
172+ try {
157173 if ( version != null && CompareVersions ( version ) != 0 ) {
158174 return null ;
159175 }
160176 int index = FindIndexForProject ( parentProject ) ;
161177 if ( index < 0 )
162178 return null ;
163179 return entries [ index ] . UnresolvedFile ;
180+ } finally {
181+ rwLock . ExitReadLock ( ) ;
164182 }
165183 }
166184
167185 public ParseInformation GetCachedParseInformation ( ITextSourceVersion version , IProject parentProject )
168186 {
169- lock ( this ) {
187+ rwLock . EnterReadLock ( ) ;
188+ try {
170189 if ( version != null && CompareVersions ( version ) != 0 ) {
171190 return null ;
172191 }
173192 int index = FindIndexForProject ( parentProject ) ;
174193 if ( index < 0 )
175194 return null ;
176195 return entries [ index ] . CachedParseInformation ;
196+ } finally {
197+ rwLock . ExitReadLock ( ) ;
177198 }
178199 }
179200 #endregion
@@ -218,7 +239,8 @@ ProjectEntry DoParse(ITextSource fileContent, IProject parentProject, bool fullP
218239 }
219240
220241 ProjectEntry result ;
221- lock ( this ) {
242+ rwLock . EnterUpgradeableReadLock ( ) ;
243+ try {
222244 int index = FindIndexForProject ( parentProject ) ;
223245 int versionComparison = CompareVersions ( fileContent . Version ) ;
224246 if ( versionComparison > 0 || index < 0 ) {
@@ -253,24 +275,32 @@ ProjectEntry DoParse(ITextSource fileContent, IProject parentProject, bool fullP
253275 }
254276
255277 // Only if all parse runs succeeded, register the parse information.
256- currentVersion = fileContent . Version ;
257- for ( int i = 0 ; i < entries . Count ; i ++ ) {
258- if ( fullParseInformationRequested || ( entries [ i ] . CachedParseInformation != null && results [ i ] . NewParseInformation . IsFullParseInformation ) )
259- entries [ i ] = new ProjectEntry ( entries [ i ] . Project , results [ i ] . NewUnresolvedFile , results [ i ] . NewParseInformation ) ;
260- else
261- entries [ i ] = new ProjectEntry ( entries [ i ] . Project , results [ i ] . NewUnresolvedFile , null ) ;
262- if ( entries [ i ] . Project != null )
263- entries [ i ] . Project . OnParseInformationUpdated ( results [ i ] ) ;
264- parserService . RaiseParseInformationUpdated ( results [ i ] ) ;
278+ rwLock . EnterWriteLock ( ) ;
279+ try {
280+ currentVersion = fileContent . Version ;
281+ for ( int i = 0 ; i < entries . Count ; i ++ ) {
282+ if ( fullParseInformationRequested || ( entries [ i ] . CachedParseInformation != null && results [ i ] . NewParseInformation . IsFullParseInformation ) )
283+ entries [ i ] = new ProjectEntry ( entries [ i ] . Project , results [ i ] . NewUnresolvedFile , results [ i ] . NewParseInformation ) ;
284+ else
285+ entries [ i ] = new ProjectEntry ( entries [ i ] . Project , results [ i ] . NewUnresolvedFile , null ) ;
286+ if ( entries [ i ] . Project != null )
287+ entries [ i ] . Project . OnParseInformationUpdated ( results [ i ] ) ;
288+ parserService . RaiseParseInformationUpdated ( results [ i ] ) ;
289+ }
290+ result = entries [ index ] ;
291+ } finally {
292+ rwLock . ExitWriteLock ( ) ;
265293 }
266- result = entries [ index ] ;
267- } // exit lock
294+ } finally {
295+ rwLock . ExitUpgradeableReadLock ( ) ;
296+ }
268297 parserService . RegisterForCacheExpiry ( this ) ;
269298 return result ;
270299 }
271300
272301 ParseInformation ParseWithExceptionHandling ( ITextSource fileContent , bool fullParseInformationRequested , IProject project , CancellationToken cancellationToken )
273302 {
303+ Debug . Assert ( rwLock . IsUpgradeableReadLockHeld && ! rwLock . IsWriteLockHeld ) ;
274304 #if DEBUG
275305 if ( Debugger . IsAttached )
276306 return parser . Parse ( fileName , fileContent , fullParseInformationRequested , project , cancellationToken ) ;
@@ -285,6 +315,8 @@ ParseInformation ParseWithExceptionHandling(ITextSource fileContent, bool fullPa
285315 #endregion
286316
287317 #region ParseAsync
318+ /// <summary>lock object for protecting the runningAsyncParse* fields</summary>
319+ object runningAsyncParseLock = new object ( ) ;
288320 Task < ProjectEntry > runningAsyncParseTask ;
289321 ITextSourceVersion runningAsyncParseFileContentVersion ;
290322 IProject runningAsyncParseProject ;
@@ -324,19 +356,24 @@ Task<ProjectEntry> DoParseAsync(ITextSource fileContent, IProject parentProject,
324356 }
325357 }
326358 Task < ProjectEntry > task ;
327- lock ( this ) {
359+ lock ( runningAsyncParseLock ) {
328360 if ( fileContent != null ) {
329361 // Optimization:
330362 // don't start a background task if fileContent was specified and up-to-date parse info is available
331- int index = FindIndexForProject ( parentProject ) ;
332- int versionComparison = CompareVersions ( fileContent . Version ) ;
333- if ( versionComparison == 0 && index >= 0 ) {
334- // Ensure we have parse info for the specified project (entry.UnresolvedFile is null for newly registered projects)
335- // If full parse info is requested, ensure we have full parse info.
336- if ( entries [ index ] . UnresolvedFile != null && ! ( requestFullParseInformation && entries [ index ] . CachedParseInformation == null ) ) {
337- // We already have the requested version parsed, just return it:
338- return Task . FromResult ( entries [ index ] ) ;
363+ rwLock . EnterReadLock ( ) ;
364+ try {
365+ int index = FindIndexForProject ( parentProject ) ;
366+ int versionComparison = CompareVersions ( fileContent . Version ) ;
367+ if ( versionComparison == 0 && index >= 0 ) {
368+ // Ensure we have parse info for the specified project (entry.UnresolvedFile is null for newly registered projects)
369+ // If full parse info is requested, ensure we have full parse info.
370+ if ( entries [ index ] . UnresolvedFile != null && ! ( requestFullParseInformation && entries [ index ] . CachedParseInformation == null ) ) {
371+ // We already have the requested version parsed, just return it:
372+ return Task . FromResult ( entries [ index ] ) ;
373+ }
339374 }
375+ } finally {
376+ rwLock . ExitReadLock ( ) ;
340377 }
341378 // Optimization:
342379 // if an equivalent task is already running, return that one instead
@@ -356,7 +393,7 @@ Task<ProjectEntry> DoParseAsync(ITextSource fileContent, IProject parentProject,
356393 }
357394 return DoParse ( fileContent , parentProject , requestFullParseInformation , cancellationToken ) ;
358395 } finally {
359- lock ( this ) {
396+ lock ( runningAsyncParseLock ) {
360397 runningAsyncParseTask = null ;
361398 runningAsyncParseFileContentVersion = null ;
362399 runningAsyncParseProject = null ;
@@ -383,7 +420,8 @@ public void RegisterUnresolvedFile(IProject project, IUnresolvedFile unresolvedF
383420 throw new ArgumentNullException ( "unresolvedFile" ) ;
384421 FreezableHelper . Freeze ( unresolvedFile ) ;
385422 var newParseInfo = new ParseInformation ( unresolvedFile , null , false ) ;
386- lock ( this ) {
423+ rwLock . EnterWriteLock ( ) ;
424+ try {
387425 int index = FindIndexForProject ( project ) ;
388426 if ( index >= 0 ) {
389427 currentVersion = null ;
@@ -392,6 +430,8 @@ public void RegisterUnresolvedFile(IProject project, IUnresolvedFile unresolvedF
392430 project . OnParseInformationUpdated ( args ) ;
393431 parserService . RaiseParseInformationUpdated ( args ) ;
394432 }
433+ } finally {
434+ rwLock . ExitWriteLock ( ) ;
395435 }
396436 }
397437 }
0 commit comments