internal override ProjectRootElement Get( string projectFile, OpenProjectRootElement openProjectRootElement, bool isExplicitlyLoaded, bool?preserveFormatting) { // Should already have been canonicalized ErrorUtilities.VerifyThrowInternalRooted(projectFile); return(openProjectRootElement == null ? GetFromCache(projectFile) : GetFromOrAddToCache(projectFile, openProjectRootElement)); }
private ProjectRootElement GetFromOrAddToCache(string projectFile, OpenProjectRootElement openFunc) { return(_cache.GetOrAdd(projectFile, key => { ProjectRootElement rootElement = openFunc(key, this); ErrorUtilities.VerifyThrowInternalNull(rootElement, "projectRootElement"); ErrorUtilities.VerifyThrow(rootElement.FullPath.Equals(key, StringComparison.OrdinalIgnoreCase), "Got project back with incorrect path"); ErrorUtilities.VerifyThrow(_cache.TryGetValue(key, out _), "Open should have renamed into cache and boosted"); return rootElement; })); }
private ProjectRootElement GetFromOrAddToCache(string projectFile, OpenProjectRootElement loadFunc) { return(_cache.GetOrAdd(projectFile, key => { ProjectRootElement rootElement = loadFunc(key, this); ErrorUtilities.VerifyThrowInternalNull(rootElement, "projectRootElement"); ErrorUtilities.VerifyThrow( rootElement.FullPath.Equals(key, StringComparison.OrdinalIgnoreCase), "Got project back with incorrect path. Expected path: {0}, received path: {1}.", key, rootElement.FullPath ); AddEntry(rootElement); ErrorUtilities.VerifyThrow(_cache.TryGetValue(key, out _), "Project should have been added into cache and boosted"); return rootElement; })); }
internal abstract ProjectRootElement Get(string projectFile, OpenProjectRootElement loadProjectRootElement, bool isExplicitlyLoaded, bool?preserveFormatting);
/// <summary> /// Returns an existing ProjectRootElement for the specified file path, if any. /// If none exists, calls the provided delegate to load one, and adds that to the cache. /// The reason that it calls back to do this is so that the cache is locked between determining /// that the entry does not exist and adding the entry. /// /// If <see cref="_autoReloadFromDisk"/> was set to true, and the file on disk has changed since it was cached, /// it will be reloaded before being returned. /// /// Thread safe. /// </summary> /// <remarks> /// Never needs to consult the strong cache as well, since if the item is in there, it will /// not have left the weak cache. /// If item is found, boosts it to the top of the strong cache. /// </remarks> /// <param name="projectFile">The project file which contains the ProjectRootElement. Must be a full path.</param> /// <param name="openProjectRootElement">The delegate to use to load if necessary. May be null.</param> /// <param name="isExplicitlyLoaded"><code>true</code> if the project is explicitly loaded, otherwise <code>false</code>.</param> /// <param name="preserveFormatting"><code>true</code> to the project was loaded with the formated preserved, otherwise <code>false</code>.</param> /// <returns>The ProjectRootElement instance if one exists. Null otherwise.</returns> internal ProjectRootElement Get(string projectFile, OpenProjectRootElement openProjectRootElement, bool isExplicitlyLoaded, bool?preserveFormatting) { // Should already have been canonicalized ErrorUtilities.VerifyThrowInternalRooted(projectFile); lock (_locker) { ProjectRootElement projectRootElement; _weakCache.TryGetValue(projectFile, out projectRootElement); if (preserveFormatting != null && projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting) { // Cached project doesn't match preserveFormatting setting, so reload it projectRootElement.Reload(true, preserveFormatting); } if (projectRootElement != null && _autoReloadFromDisk) { var fileInfo = FileUtilities.GetFileInfoNoThrow(projectFile); // If the file doesn't exist on disk, go ahead and use the cached version. // It's an in-memory project that hasn't been saved yet. if (fileInfo != null) { bool forgetEntry = false; if (fileInfo.LastWriteTime != projectRootElement.LastWriteTimeWhenRead) { // File was changed on disk by external means. Cached version is no longer reliable. // We could throw here or ignore the problem, but it is a common and reasonable pattern to change a file // externally and load a new project over it to see the new content. So we dump it from the cache // to force a load from disk. There might then exist more than one ProjectRootElement with the same path, // but clients ought not get themselves into such a state - and unless they save them to disk, // it may not be a problem. forgetEntry = true; } else if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT"))) { // QA tests run too fast for the timestamp check to work. This environment variable is for their // use: it checks the file content as well as the timestamp. That's better than completely disabling // the cache as we get test coverage of the rest of the cache code. XmlDocument document = new XmlDocument(); document.PreserveWhitespace = projectRootElement.XmlDocument.PreserveWhitespace; using (var xtr = XmlReaderExtension.Create(projectRootElement.FullPath, projectRootElement.ProjectRootElementCache.LoadProjectsReadOnly)) { document.Load(xtr.Reader); } string diskContent = document.OuterXml; string cacheContent = projectRootElement.XmlDocument.OuterXml; if (diskContent != cacheContent) { forgetEntry = true; } } if (forgetEntry) { ForgetEntry(projectRootElement); DebugTraceCache("Out of date dropped from XML cache: ", projectFile); projectRootElement = null; } } } if (projectRootElement == null && openProjectRootElement != null) { projectRootElement = openProjectRootElement(projectFile, this); ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement"); ErrorUtilities.VerifyThrow(projectRootElement.FullPath == projectFile, "Got project back with incorrect path"); ErrorUtilities.VerifyThrow(_weakCache.Contains(projectFile), "Open should have renamed into cache and boosted"); } else if (projectRootElement != null) { DebugTraceCache("Satisfied from XML cache: ", projectFile); BoostEntryInStrongCache(projectRootElement); } // An implicit load will never reset the explicit flag. if (projectRootElement != null && isExplicitlyLoaded) { projectRootElement.MarkAsExplicitlyLoaded(); } return(projectRootElement); } }
/// <summary> /// Returns an existing ProjectRootElement for the specified file path, if any. /// If none exists, calls the provided delegate to load one, and adds that to the cache. /// The reason that it calls back to do this is so that the cache is locked between determining /// that the entry does not exist and adding the entry. /// /// If <see cref="_autoReloadFromDisk"/> was set to true, and the file on disk has changed since it was cached, /// it will be reloaded before being returned. /// /// Thread safe. /// </summary> /// <remarks> /// Never needs to consult the strong cache as well, since if the item is in there, it will /// not have left the weak cache. /// If item is found, boosts it to the top of the strong cache. /// </remarks> /// <param name="projectFile">The project file which contains the ProjectRootElement. Must be a full path.</param> /// <param name="openProjectRootElement">The delegate to use to load if necessary. May be null.</param> /// <returns>The ProjectRootElement instance if one exists. Null otherwise.</returns> internal ProjectRootElement Get(string projectFile, OpenProjectRootElement openProjectRootElement, bool isExplicitlyLoaded, bool preserveFormatting) { // Should already have been canonicalized ErrorUtilities.VerifyThrowInternalRooted(projectFile); lock (_locker) { ProjectRootElement projectRootElement; _weakCache.TryGetValue(projectFile, out projectRootElement); if (projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting) { // Cached project doesn't match preserveFormatting setting, so don't use it projectRootElement = null; } if (projectRootElement != null && _autoReloadFromDisk) { FileInfo fileInfo = FileUtilities.GetFileInfoNoThrow(projectFile); // If the file doesn't exist on disk, go ahead and use the cached version. // It's an in-memory project that hasn't been saved yet. if (fileInfo != null) { bool forgetEntry = false; if (fileInfo.LastWriteTime != projectRootElement.LastWriteTimeWhenRead) { // File was changed on disk by external means. Cached version is no longer reliable. // We could throw here or ignore the problem, but it is a common and reasonable pattern to change a file // externally and load a new project over it to see the new content. So we dump it from the cache // to force a load from disk. There might then exist more than one ProjectRootElement with the same path, // but clients ought not get themselves into such a state - and unless they save them to disk, // it may not be a problem. forgetEntry = true; } else if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT"))) { // QA tests run too fast for the timestamp check to work. This environment variable is for their // use: it checks the file content as well as the timestamp. That's better than completely disabling // the cache as we get test coverage of the rest of the cache code. XmlDocument document = new XmlDocument(); document.PreserveWhitespace = projectRootElement.XmlDocument.PreserveWhitespace; XmlReaderSettings dtdSettings = new XmlReaderSettings(); dtdSettings.DtdProcessing = DtdProcessing.Ignore; using (var stream = new StreamReader(projectRootElement.FullPath)) using (XmlReader xtr = XmlReader.Create(stream, dtdSettings)) { document.Load(xtr); } string diskContent = document.OuterXml; string cacheContent = projectRootElement.XmlDocument.OuterXml; if (diskContent != cacheContent) { forgetEntry = true; } } if (forgetEntry) { ForgetEntry(projectRootElement); DebugTraceCache("Out of date dropped from XML cache: ", projectFile); projectRootElement = null; } } } if (projectRootElement == null && openProjectRootElement != null) { projectRootElement = openProjectRootElement(projectFile, this); ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement"); ErrorUtilities.VerifyThrow(projectRootElement.FullPath == projectFile, "Got project back with incorrect path"); ErrorUtilities.VerifyThrow(_weakCache.Contains(projectFile), "Open should have renamed into cache and boosted"); } else if (projectRootElement != null) { DebugTraceCache("Satisfied from XML cache: ", projectFile); BoostEntryInStrongCache(projectRootElement); } // An implicit load will never reset the explicit flag. if (projectRootElement != null && isExplicitlyLoaded) { projectRootElement.MarkAsExplicitlyLoaded(); } return projectRootElement; } }
internal override ProjectRootElement Get(string projectFile, OpenProjectRootElement loadProjectRootElement, bool isExplicitlyLoaded, bool?preserveFormatting) { #if DEBUG // Verify that loadProjectRootElement delegate does not call ProjectRootElementCache.Get(). s_getEntriesNumber++; ErrorUtilities.VerifyThrow( s_getEntriesNumber == 1, "Reentrance to the ProjectRootElementCache.Get function detected." ); try { #endif // Should already have been canonicalized ErrorUtilities.VerifyThrowInternalRooted(projectFile); ProjectRootElement projectRootElement; lock (_locker) { _weakCache.TryGetValue(projectFile, out projectRootElement); if (projectRootElement != null) { BoostEntryInStrongCache(projectRootElement); // An implicit load will never reset the explicit flag. if (isExplicitlyLoaded) { projectRootElement.MarkAsExplicitlyLoaded(); } } else { DebugTraceCache("Not found in cache: ", projectFile); } if (preserveFormatting != null && projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting) { // Cached project doesn't match preserveFormatting setting, so reload it projectRootElement.Reload(true, preserveFormatting); } } bool projectRootElementIsInvalid = IsInvalidEntry(projectFile, projectRootElement); if (projectRootElementIsInvalid) { DebugTraceCache("Not satisfied from cache: ", projectFile); ForgetEntryIfExists(projectRootElement); } if (loadProjectRootElement == null) { if (projectRootElement == null || projectRootElementIsInvalid) { return(null); } else { DebugTraceCache("Satisfied from XML cache: ", projectFile); return(projectRootElement); } } // Use openProjectRootElement to reload the element if the cache element does not exist or need to be reloaded. if (projectRootElement == null || projectRootElementIsInvalid) { // We do not lock loading with common _locker of the cache, to avoid lock contention. // Decided also not to lock this section with the key specific locker to avoid the overhead and code overcomplication, as // it is not likely that two threads would use Get function for the same project simultaneously and it is not a big deal if in some cases we load the same project twice. projectRootElement = loadProjectRootElement(projectFile, this); ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement"); ErrorUtilities.VerifyThrow( projectRootElement.FullPath.Equals(projectFile, StringComparison.OrdinalIgnoreCase), "Got project back with incorrect path. Expected path: {0}, received path: {1}.", projectFile, projectRootElement.FullPath ); // An implicit load will never reset the explicit flag. if (isExplicitlyLoaded) { projectRootElement.MarkAsExplicitlyLoaded(); } // Update cache element. // It is unlikely, but it might be that while without the lock, the projectRootElement in cache was updated by another thread. // And here its entry will be replaced with the loaded projectRootElement. This is fine: // if loaded projectRootElement is out of date (so, it changed since the time we loaded it), it will be updated the next time some thread calls Get function. AddEntry(projectRootElement); } else { DebugTraceCache("Satisfied from XML cache: ", projectFile); } return(projectRootElement); #if DEBUG } finally { s_getEntriesNumber--; } #endif }