Browse Source

Use ReaderWriterLock in ParserServiceEntry to prevent the parser thread from blocking the UI thread.

pull/512/head
Daniel Grunwald 11 years ago
parent
commit
382c621383
  1. 4
      src/Main/SharpDevelop/Parser/ParserService.cs
  2. 96
      src/Main/SharpDevelop/Parser/ParserServiceEntry.cs

4
src/Main/SharpDevelop/Parser/ParserService.cs

@ -174,7 +174,7 @@ namespace ICSharpCode.SharpDevelop.Parser
internal void RemoveEntry(ParserServiceEntry entry) internal void RemoveEntry(ParserServiceEntry entry)
{ {
Debug.Assert(Monitor.IsEntered(entry)); Debug.Assert(entry.rwLock.IsWriteLockHeld);
lock (fileEntryDict) { lock (fileEntryDict) {
ParserServiceEntry entryAtKey; ParserServiceEntry entryAtKey;
if (fileEntryDict.TryGetValue(entry.fileName, out entryAtKey)) { if (fileEntryDict.TryGetValue(entry.fileName, out entryAtKey)) {
@ -190,7 +190,7 @@ namespace ICSharpCode.SharpDevelop.Parser
internal void RegisterForCacheExpiry(ParserServiceEntry entry) internal void RegisterForCacheExpiry(ParserServiceEntry entry)
{ {
// This method should not be called within any locks // This method should not be called within any locks
Debug.Assert(!Monitor.IsEntered(entry)); Debug.Assert(!(entry.rwLock.IsReadLockHeld || entry.rwLock.IsUpgradeableReadLockHeld || entry.rwLock.IsWriteLockHeld));
ParserServiceEntry expiredItem = null; ParserServiceEntry expiredItem = null;
lock (cacheExpiryQueue) { lock (cacheExpiryQueue) {
cacheExpiryQueue.Remove(entry); // remove entry from queue if it's already enqueued cacheExpiryQueue.Remove(entry); // remove entry from queue if it's already enqueued

96
src/Main/SharpDevelop/Parser/ParserServiceEntry.cs

@ -54,6 +54,10 @@ namespace ICSharpCode.SharpDevelop.Parser
List<ProjectEntry> entries = new List<ProjectEntry> { default(ProjectEntry) }; List<ProjectEntry> entries = new List<ProjectEntry> { default(ProjectEntry) };
ITextSourceVersion currentVersion; ITextSourceVersion currentVersion;
// Lock ordering: runningAsyncParseLock, rwLock, lock(parserService.fileEntryDict)
// (to avoid deadlocks, the locks may only be acquired in this order)
internal readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
public ParserServiceEntry(ParserService parserService, FileName fileName) public ParserServiceEntry(ParserService parserService, FileName fileName)
{ {
this.parserService = parserService; this.parserService = parserService;
@ -68,6 +72,7 @@ namespace ICSharpCode.SharpDevelop.Parser
int FindIndexForProject(IProject parentProject) int FindIndexForProject(IProject parentProject)
{ {
Debug.Assert(rwLock.IsReadLockHeld || rwLock.IsUpgradeableReadLockHeld || rwLock.IsWriteLockHeld);
if (parentProject == null) if (parentProject == null)
return 0; return 0;
for (int i = 0; i < entries.Count; i++) { for (int i = 0; i < entries.Count; i++) {
@ -81,7 +86,8 @@ namespace ICSharpCode.SharpDevelop.Parser
public void AddOwnerProject(IProject project, bool isLinkedFile) public void AddOwnerProject(IProject project, bool isLinkedFile)
{ {
Debug.Assert(project != null); Debug.Assert(project != null);
lock (this) { rwLock.EnterWriteLock();
try {
if (FindIndexForProject(project) >= 0) if (FindIndexForProject(project) >= 0)
throw new InvalidOperationException("The project alreadys owns the file"); throw new InvalidOperationException("The project alreadys owns the file");
ProjectEntry newEntry = new ProjectEntry(project, null, null); ProjectEntry newEntry = new ProjectEntry(project, null, null);
@ -92,6 +98,8 @@ namespace ICSharpCode.SharpDevelop.Parser
} else { } else {
entries.Insert(0, newEntry); entries.Insert(0, newEntry);
} }
} finally {
rwLock.ExitWriteLock();
} }
} }
@ -100,7 +108,8 @@ namespace ICSharpCode.SharpDevelop.Parser
Debug.Assert(project != null); Debug.Assert(project != null);
ProjectEntry oldEntry; ProjectEntry oldEntry;
bool removedLastOwner = false; bool removedLastOwner = false;
lock (this) { rwLock.EnterWriteLock();
try {
int index = FindIndexForProject(project); int index = FindIndexForProject(project);
if (index < 0) if (index < 0)
throw new InvalidOperationException("The project does not own the file"); throw new InvalidOperationException("The project does not own the file");
@ -111,6 +120,8 @@ namespace ICSharpCode.SharpDevelop.Parser
} else { } else {
entries.RemoveAt(index); entries.RemoveAt(index);
} }
} finally {
rwLock.ExitWriteLock();
} }
if (oldEntry.UnresolvedFile != null) { if (oldEntry.UnresolvedFile != null) {
project.OnParseInformationUpdated(new ParseInformationEventArgs(project, oldEntry.UnresolvedFile, null)); project.OnParseInformationUpdated(new ParseInformationEventArgs(project, oldEntry.UnresolvedFile, null));
@ -128,6 +139,7 @@ namespace ICSharpCode.SharpDevelop.Parser
/// </summary> /// </summary>
int CompareVersions(ITextSourceVersion newVersion) int CompareVersions(ITextSourceVersion newVersion)
{ {
Debug.Assert(rwLock.IsReadLockHeld || rwLock.IsUpgradeableReadLockHeld || rwLock.IsWriteLockHeld);
if (currentVersion != null && newVersion != null && currentVersion.BelongsToSameDocumentAs(newVersion)) if (currentVersion != null && newVersion != null && currentVersion.BelongsToSameDocumentAs(newVersion))
return currentVersion.CompareAge(newVersion); return currentVersion.CompareAge(newVersion);
else else
@ -137,7 +149,8 @@ namespace ICSharpCode.SharpDevelop.Parser
#region Expire Cache + GetExistingUnresolvedFile + GetCachedParseInformation #region Expire Cache + GetExistingUnresolvedFile + GetCachedParseInformation
public void ExpireCache() public void ExpireCache()
{ {
lock (this) { rwLock.EnterWriteLock();
try {
if (PrimaryProject == null) { if (PrimaryProject == null) {
parserService.RemoveEntry(this); parserService.RemoveEntry(this);
} else { } else {
@ -148,12 +161,15 @@ namespace ICSharpCode.SharpDevelop.Parser
} }
// force re-parse on next ParseFile() call even if unchanged // force re-parse on next ParseFile() call even if unchanged
this.currentVersion = null; this.currentVersion = null;
} finally {
rwLock.ExitWriteLock();
} }
} }
public IUnresolvedFile GetExistingUnresolvedFile(ITextSourceVersion version, IProject parentProject) public IUnresolvedFile GetExistingUnresolvedFile(ITextSourceVersion version, IProject parentProject)
{ {
lock (this) { rwLock.EnterReadLock();
try {
if (version != null && CompareVersions(version) != 0) { if (version != null && CompareVersions(version) != 0) {
return null; return null;
} }
@ -161,12 +177,15 @@ namespace ICSharpCode.SharpDevelop.Parser
if (index < 0) if (index < 0)
return null; return null;
return entries[index].UnresolvedFile; return entries[index].UnresolvedFile;
} finally {
rwLock.ExitReadLock();
} }
} }
public ParseInformation GetCachedParseInformation(ITextSourceVersion version, IProject parentProject) public ParseInformation GetCachedParseInformation(ITextSourceVersion version, IProject parentProject)
{ {
lock (this) { rwLock.EnterReadLock();
try {
if (version != null && CompareVersions(version) != 0) { if (version != null && CompareVersions(version) != 0) {
return null; return null;
} }
@ -174,6 +193,8 @@ namespace ICSharpCode.SharpDevelop.Parser
if (index < 0) if (index < 0)
return null; return null;
return entries[index].CachedParseInformation; return entries[index].CachedParseInformation;
} finally {
rwLock.ExitReadLock();
} }
} }
#endregion #endregion
@ -218,7 +239,8 @@ namespace ICSharpCode.SharpDevelop.Parser
} }
ProjectEntry result; ProjectEntry result;
lock (this) { rwLock.EnterUpgradeableReadLock();
try {
int index = FindIndexForProject(parentProject); int index = FindIndexForProject(parentProject);
int versionComparison = CompareVersions(fileContent.Version); int versionComparison = CompareVersions(fileContent.Version);
if (versionComparison > 0 || index < 0) { if (versionComparison > 0 || index < 0) {
@ -253,24 +275,32 @@ namespace ICSharpCode.SharpDevelop.Parser
} }
// Only if all parse runs succeeded, register the parse information. // Only if all parse runs succeeded, register the parse information.
currentVersion = fileContent.Version; rwLock.EnterWriteLock();
for (int i = 0; i < entries.Count; i++) { try {
if (fullParseInformationRequested || (entries[i].CachedParseInformation != null && results[i].NewParseInformation.IsFullParseInformation)) currentVersion = fileContent.Version;
entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, results[i].NewParseInformation); for (int i = 0; i < entries.Count; i++) {
else if (fullParseInformationRequested || (entries[i].CachedParseInformation != null && results[i].NewParseInformation.IsFullParseInformation))
entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, null); entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, results[i].NewParseInformation);
if (entries[i].Project != null) else
entries[i].Project.OnParseInformationUpdated(results[i]); entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, null);
parserService.RaiseParseInformationUpdated(results[i]); if (entries[i].Project != null)
entries[i].Project.OnParseInformationUpdated(results[i]);
parserService.RaiseParseInformationUpdated(results[i]);
}
result = entries[index];
} finally {
rwLock.ExitWriteLock();
} }
result = entries[index]; } finally {
} // exit lock rwLock.ExitUpgradeableReadLock();
}
parserService.RegisterForCacheExpiry(this); parserService.RegisterForCacheExpiry(this);
return result; return result;
} }
ParseInformation ParseWithExceptionHandling(ITextSource fileContent, bool fullParseInformationRequested, IProject project, CancellationToken cancellationToken) ParseInformation ParseWithExceptionHandling(ITextSource fileContent, bool fullParseInformationRequested, IProject project, CancellationToken cancellationToken)
{ {
Debug.Assert(rwLock.IsUpgradeableReadLockHeld && !rwLock.IsWriteLockHeld);
#if DEBUG #if DEBUG
if (Debugger.IsAttached) if (Debugger.IsAttached)
return parser.Parse(fileName, fileContent, fullParseInformationRequested, project, cancellationToken); return parser.Parse(fileName, fileContent, fullParseInformationRequested, project, cancellationToken);
@ -285,6 +315,8 @@ namespace ICSharpCode.SharpDevelop.Parser
#endregion #endregion
#region ParseAsync #region ParseAsync
/// <summary>lock object for protecting the runningAsyncParse* fields</summary>
object runningAsyncParseLock = new object();
Task<ProjectEntry> runningAsyncParseTask; Task<ProjectEntry> runningAsyncParseTask;
ITextSourceVersion runningAsyncParseFileContentVersion; ITextSourceVersion runningAsyncParseFileContentVersion;
IProject runningAsyncParseProject; IProject runningAsyncParseProject;
@ -324,19 +356,24 @@ namespace ICSharpCode.SharpDevelop.Parser
} }
} }
Task<ProjectEntry> task; Task<ProjectEntry> task;
lock (this) { lock (runningAsyncParseLock) {
if (fileContent != null) { if (fileContent != null) {
// Optimization: // Optimization:
// don't start a background task if fileContent was specified and up-to-date parse info is available // don't start a background task if fileContent was specified and up-to-date parse info is available
int index = FindIndexForProject(parentProject); rwLock.EnterReadLock();
int versionComparison = CompareVersions(fileContent.Version); try {
if (versionComparison == 0 && index >= 0) { int index = FindIndexForProject(parentProject);
// Ensure we have parse info for the specified project (entry.UnresolvedFile is null for newly registered projects) int versionComparison = CompareVersions(fileContent.Version);
// If full parse info is requested, ensure we have full parse info. if (versionComparison == 0 && index >= 0) {
if (entries[index].UnresolvedFile != null && !(requestFullParseInformation && entries[index].CachedParseInformation == null)) { // Ensure we have parse info for the specified project (entry.UnresolvedFile is null for newly registered projects)
// We already have the requested version parsed, just return it: // If full parse info is requested, ensure we have full parse info.
return Task.FromResult(entries[index]); if (entries[index].UnresolvedFile != null && !(requestFullParseInformation && entries[index].CachedParseInformation == null)) {
// We already have the requested version parsed, just return it:
return Task.FromResult(entries[index]);
}
} }
} finally {
rwLock.ExitReadLock();
} }
// Optimization: // Optimization:
// if an equivalent task is already running, return that one instead // if an equivalent task is already running, return that one instead
@ -356,7 +393,7 @@ namespace ICSharpCode.SharpDevelop.Parser
} }
return DoParse(fileContent, parentProject, requestFullParseInformation, cancellationToken); return DoParse(fileContent, parentProject, requestFullParseInformation, cancellationToken);
} finally { } finally {
lock (this) { lock (runningAsyncParseLock) {
runningAsyncParseTask = null; runningAsyncParseTask = null;
runningAsyncParseFileContentVersion = null; runningAsyncParseFileContentVersion = null;
runningAsyncParseProject = null; runningAsyncParseProject = null;
@ -383,7 +420,8 @@ namespace ICSharpCode.SharpDevelop.Parser
throw new ArgumentNullException("unresolvedFile"); throw new ArgumentNullException("unresolvedFile");
FreezableHelper.Freeze(unresolvedFile); FreezableHelper.Freeze(unresolvedFile);
var newParseInfo = new ParseInformation(unresolvedFile, null, false); var newParseInfo = new ParseInformation(unresolvedFile, null, false);
lock (this) { rwLock.EnterWriteLock();
try {
int index = FindIndexForProject(project); int index = FindIndexForProject(project);
if (index >= 0) { if (index >= 0) {
currentVersion = null; currentVersion = null;
@ -392,6 +430,8 @@ namespace ICSharpCode.SharpDevelop.Parser
project.OnParseInformationUpdated(args); project.OnParseInformationUpdated(args);
parserService.RaiseParseInformationUpdated(args); parserService.RaiseParseInformationUpdated(args);
} }
} finally {
rwLock.ExitWriteLock();
} }
} }
} }

Loading…
Cancel
Save