Skip to content
This repository was archived by the owner on Oct 16, 2020. It is now read-only.

Commit 382c621

Browse files
committed
Use ReaderWriterLock in ParserServiceEntry to prevent the parser thread from blocking the UI thread.
1 parent 4b4518d commit 382c621

2 files changed

Lines changed: 70 additions & 30 deletions

File tree

src/Main/SharpDevelop/Parser/ParserService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public void ClearParseInformation(FileName fileName)
174174

175175
internal void RemoveEntry(ParserServiceEntry entry)
176176
{
177-
Debug.Assert(Monitor.IsEntered(entry));
177+
Debug.Assert(entry.rwLock.IsWriteLockHeld);
178178
lock (fileEntryDict) {
179179
ParserServiceEntry entryAtKey;
180180
if (fileEntryDict.TryGetValue(entry.fileName, out entryAtKey)) {
@@ -190,7 +190,7 @@ internal void RemoveEntry(ParserServiceEntry entry)
190190
internal void RegisterForCacheExpiry(ParserServiceEntry entry)
191191
{
192192
// This method should not be called within any locks
193-
Debug.Assert(!Monitor.IsEntered(entry));
193+
Debug.Assert(!(entry.rwLock.IsReadLockHeld || entry.rwLock.IsUpgradeableReadLockHeld || entry.rwLock.IsWriteLockHeld));
194194
ParserServiceEntry expiredItem = null;
195195
lock (cacheExpiryQueue) {
196196
cacheExpiryQueue.Remove(entry); // remove entry from queue if it's already enqueued

src/Main/SharpDevelop/Parser/ParserServiceEntry.cs

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)