Browse Source

Removed 'no-concurrent calls' guarantee for IParser. This avoids deadlocks when IParser implementations invoke methods on the main thread.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4718 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
4f2d79c75c
  1. 70
      src/Main/Base/Project/Src/Services/ParserService/ParserService.cs

70
src/Main/Base/Project/Src/Services/ParserService/ParserService.cs

@ -192,7 +192,7 @@ namespace ICSharpCode.SharpDevelop @@ -192,7 +192,7 @@ namespace ICSharpCode.SharpDevelop
}
/// <summary>
/// Retrieves the IParser instance that can parse the specified file.
/// Creates a new IParser instance that can parse the specified file.
/// This method is thread-safe.
/// </summary>
public static IParser CreateParser(string fileName)
@ -321,7 +321,7 @@ namespace ICSharpCode.SharpDevelop @@ -321,7 +321,7 @@ namespace ICSharpCode.SharpDevelop
sealed class FileEntry
{
readonly string fileName;
readonly IParser parser;
internal readonly IParser parser;
volatile ParseInformation parseInfo;
ITextBufferVersion bufferVersion;
ICompilationUnit[] oldUnits = emptyCompilationUnitArray;
@ -370,25 +370,22 @@ namespace ICSharpCode.SharpDevelop @@ -370,25 +370,22 @@ namespace ICSharpCode.SharpDevelop
}
ITextBufferVersion fileContentVersion = fileContent.Version;
List<IProjectContent> projectContents;
lock (this) {
// unfortunaly we need this big lock to ensure that project contents are updated correctly
// and never move backwards in time
if (disposed)
if (this.disposed)
return null;
if (fileContentVersion != null && bufferVersion != null && bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) {
if (bufferVersion.CompareAge(fileContentVersion) >= 0) {
if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) {
if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) {
// Special case: (necessary due to parentProjectContent optimization)
// Detect when a file belongs to multiple projects but the ParserService hasn't realized
// that, yet. In this case, do another parse run to detect all parent projects.
if (!(parentProjectContent != null && oldUnits.Length == 1 && oldUnits[0].ProjectContent != parentProjectContent)) {
return parseInfo;
if (!(parentProjectContent != null && this.oldUnits.Length == 1 && this.oldUnits[0].ProjectContent != parentProjectContent)) {
return this.parseInfo;
}
}
}
List<IProjectContent> projectContents;
if (parentProjectContent != null && (oldUnits.Length == 0 || (oldUnits.Length == 1 && oldUnits[0].ProjectContent == parentProjectContent))) {
// Optimization: if parentProjectContent is specified and doesn't conflict with what we already know,
// we will use it instead of doing an expensive GetProjectContents call.
@ -397,18 +394,38 @@ namespace ICSharpCode.SharpDevelop @@ -397,18 +394,38 @@ namespace ICSharpCode.SharpDevelop
} else {
projectContents = GetProjectContents(fileName);
}
// parse once for each project content that contains the file
ICompilationUnit[] newUnits = new ICompilationUnit[projectContents.Count];
ICompilationUnit resultUnit = null;
}
// We now leave the lock to do the actual parsing.
// This is done to allow IParser implementations to invoke methods on the main thread without
// risking deadlocks.
// parse once for each project content that contains the file
ICompilationUnit[] newUnits = new ICompilationUnit[projectContents.Count];
ICompilationUnit resultUnit = null;
for (int i = 0; i < newUnits.Length; i++) {
IProjectContent pc = projectContents[i];
newUnits[i] = parser.Parse(pc, fileName, fileContent);
if (i == 0 || pc == parentProjectContent)
resultUnit = newUnits[i];
}
lock (this) {
if (this.disposed)
return null;
// ensure we never go backwards in time (we need to repeat this check after we've reacquired the lock)
if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) {
if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) {
return this.parseInfo;
}
}
for (int i = 0; i < newUnits.Length; i++) {
IProjectContent pc = projectContents[i];
newUnits[i] = parser.Parse(pc, fileName, fileContent);
if (i == 0 || pc == parentProjectContent)
resultUnit = newUnits[i];
// update the compilation unit
ICompilationUnit oldUnit = oldUnits.FirstOrDefault(o => o.ProjectContent == pc);
pc.UpdateCompilationUnit(oldUnit, newUnits[i], fileName);
}
// remove all old units that don't exist anymore
foreach (ICompilationUnit oldUnit in oldUnits) {
if (!newUnits.Any(n => n.ProjectContent == oldUnit.ProjectContent))
@ -429,15 +446,13 @@ namespace ICSharpCode.SharpDevelop @@ -429,15 +446,13 @@ namespace ICSharpCode.SharpDevelop
lock (this) {
// by setting the disposed flag, we'll cause all running ParseFile() calls to return null and not
// call into the parser anymore, so we can do the remainder of the clean-up work outside the lock
disposed = true;
this.disposed = true;
oldUnits = this.oldUnits;
bufferVersion = null;
this.oldUnits = null;
this.bufferVersion = null;
}
foreach (ICompilationUnit oldUnit in oldUnits)
oldUnit.ProjectContent.RemoveCompilationUnit(oldUnit);
IDisposable d = this.parser as IDisposable;
if (d != null)
d.Dispose();
}
public Task<ParseInformation> BeginParse(ITextBuffer fileContent)
@ -661,6 +676,17 @@ namespace ICSharpCode.SharpDevelop @@ -661,6 +676,17 @@ namespace ICSharpCode.SharpDevelop
);
}
/// <summary>
/// Gets the parser instance that is responsible for the specified file.
/// Will create a new IParser instance on demand.
/// This method is thread-safe.
/// </summary>
public static IParser GetParser(string fileName)
{
return GetFileEntry(fileName, true).parser;
}
/// <summary>
/// Registers a compilation unit in the parser service.
/// Does not fire the OnParseInformationUpdated event, please use this for unit tests only!

Loading…
Cancel
Save