1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 //----------------------------------------------------------------------- 4 // </copyright> 5 // <summary>Holds weak references to ProjectRootElement's for design time sharing purposes.</summary> 6 //----------------------------------------------------------------------- 7 8 using System; 9 using System.Collections.Generic; 10 using System.IO; 11 using System.Linq; 12 using System.Text; 13 using System.Xml; 14 using Microsoft.Build.Construction; 15 16 using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; 17 using Microsoft.Build.Collections; 18 using Microsoft.Build.Shared; 19 using System.Diagnostics; 20 using System.Globalization; 21 using Microsoft.Build.BackEnd; 22 using Microsoft.Build.Internal; 23 using OutOfProcNode = Microsoft.Build.Execution.OutOfProcNode; 24 25 namespace Microsoft.Build.Evaluation 26 { 27 /// <summary> 28 /// Maintains a cache of all loaded ProjectRootElement's for design time purposes. 29 /// Weak references are held to add added ProjectRootElement's. 30 /// Strong references are held to a limited number of added ProjectRootElement's. 31 /// 32 /// 1. Loads of a ProjectRootElement will share any existing loaded ProjectRootElement, rather 33 /// than loading and parsing a new one. This is the case whether the ProjectRootElement 34 /// is loaded directly or imported. 35 /// 36 /// 2. For design time, only a weak reference needs to be held, because all users have a strong reference. 37 /// 38 /// 3. Because all loads of a ProjectRootElement consult this cache, they can be assured that any 39 /// entries in this cache are up to date. For example, if a ProjectRootElement is modified and saved, 40 /// the cached ProjectRootElement will be the loaded one that was saved, so it will be up to date. 41 /// 42 /// 4. If, after a project has been loaded, an external app changes the project file content on disk, it is 43 /// important that a subsequent load of that project does not return stale ProjectRootElement. To avoid this, the 44 /// timestamp of the file on disk is compared to the timestamp of the file at the time that the ProjectRootElement loaded it. 45 /// 46 /// 5. For build time, some strong references need to be held, as otherwise the ProjectRootElement's for reuseable 47 /// imports will be collected, and time will be wasted reparsing them. However we do not want to hold strong references 48 /// to all ProjectRootElement's, consuming memory without end. So a simple priority queue is used. All Adds and Gets boost their 49 /// entry to the top. As the queue gets too big, low priority entries are dropped. 50 /// 51 /// No guesses are made at which files are more interesting to cache, beyond the most-recently-used list. For example, ".targets" files 52 /// or imported files are not treated specially, as this is a potentially unreliable heuristic. Besides, caching a project file itself could 53 /// be useful, if for example you want to build it twice with different sets of properties. 54 /// 55 /// Because of the strongly typed list, some ProjectRootElement's will be held onto indefinitely. This is an acceptable price to pay for 56 /// being able to provide a commonly used ProjectRootElement immediately it's needed. It is mitigated by the list being finite and small, and 57 /// because we allow ProjectCollection.UnloadAllProjects to hint to us to clear the list. 58 /// 59 /// Implicit references are those which were loaded as a result of a build, and not explicitly loaded through, for instance, the project 60 /// collection. 61 /// 62 /// </summary> 63 internal class ProjectRootElementCache 64 { 65 /// <summary> 66 /// The maximum number of entries to keep strong references to. 67 /// This has to be strong enough to make sure that key .targets files aren't pushed 68 /// off by transient loads of non-reusable files like .user files. 69 /// 70 /// Made this as large as 50 because VC has a large number of 71 /// regularly used property sheets and other imports. 72 /// If you change this, update the unit tests. 73 /// </summary> 74 /// <remarks> 75 /// If this number is increased much higher, the datastructure may 76 /// need to be changed from a linked list, since it's currently O(n). 77 /// </remarks> 78 private static readonly int s_maximumStrongCacheSize = 50; 79 80 /// <summary> 81 /// Whether the cache should log activity to the Debug.Out stream 82 /// </summary> 83 private static bool s_debugLogCacheActivity; 84 85 /// <summary> 86 /// The map of weakly-held ProjectRootElement's 87 /// </summary> 88 /// <remarks> 89 /// Be sure that the string keys are strongly held, or unpredictable bad 90 /// behavior will ensue. 91 /// </remarks> 92 private WeakValueDictionary<string, ProjectRootElement> _weakCache; 93 94 /// <summary> 95 /// The list of strongly-held ProjectRootElement's 96 /// </summary> 97 private LinkedList<ProjectRootElement> _strongCache; 98 99 /// <summary> 100 /// Whether the cache should check the timestamp of the file on disk 101 /// whenever it is requested, and update with the latest content of that 102 /// file if it has changed. 103 /// </summary> 104 private bool _autoReloadFromDisk; 105 106 /// <summary> 107 /// Locking object for this shared cache 108 /// </summary> 109 private Object _locker = new Object(); 110 111 /// <summary> 112 /// Static constructor to choose cache size. 113 /// </summary> ProjectRootElementCache()114 static ProjectRootElementCache() 115 { 116 // Configurable in case a customer has related perf problems after shipping and so that 117 // we can measure different values for perf easily. 118 string userSpecifiedSize = Environment.GetEnvironmentVariable("MSBUILDPROJECTROOTELEMENTCACHESIZE"); 119 if (!String.IsNullOrEmpty(userSpecifiedSize)) 120 { 121 // Not catching as this is an undocumented setting 122 s_maximumStrongCacheSize = Convert.ToInt32(userSpecifiedSize, NumberFormatInfo.InvariantInfo); 123 } 124 125 s_debugLogCacheActivity = Environment.GetEnvironmentVariable("MSBUILDDEBUGXMLCACHE") == "1"; 126 } 127 128 /// <summary> 129 /// Creates an empty cache. 130 /// </summary> ProjectRootElementCache(bool autoReloadFromDisk)131 internal ProjectRootElementCache(bool autoReloadFromDisk) 132 { 133 DebugTraceCache("Constructing with autoreload from disk: ", autoReloadFromDisk); 134 135 _weakCache = new WeakValueDictionary<string, ProjectRootElement>(StringComparer.OrdinalIgnoreCase); 136 _strongCache = new LinkedList<ProjectRootElement>(); 137 _autoReloadFromDisk = autoReloadFromDisk; 138 } 139 140 /// <summary> 141 /// Handler for which project root element just got added to the cache 142 /// </summary> ProjectRootElementCacheAddEntryHandler(object sender, ProjectRootElementCacheAddEntryEventArgs e)143 internal delegate void ProjectRootElementCacheAddEntryHandler(object sender, ProjectRootElementCacheAddEntryEventArgs e); 144 145 /// <summary> 146 /// Delegate for StrongCacheEntryRemoved event 147 /// </summary> StrongCacheEntryRemovedDelegate(object sender, ProjectRootElement projectRootElement)148 internal delegate void StrongCacheEntryRemovedDelegate(object sender, ProjectRootElement projectRootElement); 149 150 /// <summary> 151 /// Callback to create a ProjectRootElement if need be 152 /// </summary> OpenProjectRootElement(string path, ProjectRootElementCache cache)153 internal delegate ProjectRootElement OpenProjectRootElement(string path, ProjectRootElementCache cache); 154 155 /// <summary> 156 /// Event that is fired when an entry in the Strong Cache is removed. 157 /// </summary> 158 internal static event StrongCacheEntryRemovedDelegate StrongCacheEntryRemoved; 159 160 /// <summary> 161 /// Event which is fired when a project root element is added to this cache. 162 /// </summary> 163 internal event ProjectRootElementCacheAddEntryHandler ProjectRootElementAddedHandler; 164 165 /// <summary> 166 /// Event which is fired when a project root element in this cache is dirtied. 167 /// </summary> 168 internal event EventHandler<ProjectXmlChangedEventArgs> ProjectRootElementDirtied; 169 170 /// <summary> 171 /// Event which is fired when a project is marked dirty. 172 /// </summary> 173 internal event EventHandler<ProjectChangedEventArgs> ProjectDirtied; 174 175 /// <summary> 176 /// Returns an existing ProjectRootElement for the specified file path, if any. 177 /// If none exists, calls the provided delegate to load one, and adds that to the cache. 178 /// The reason that it calls back to do this is so that the cache is locked between determining 179 /// that the entry does not exist and adding the entry. 180 /// 181 /// If <see cref="_autoReloadFromDisk"/> was set to true, and the file on disk has changed since it was cached, 182 /// it will be reloaded before being returned. 183 /// 184 /// Thread safe. 185 /// </summary> 186 /// <remarks> 187 /// Never needs to consult the strong cache as well, since if the item is in there, it will 188 /// not have left the weak cache. 189 /// If item is found, boosts it to the top of the strong cache. 190 /// </remarks> 191 /// <param name="projectFile">The project file which contains the ProjectRootElement. Must be a full path.</param> 192 /// <param name="openProjectRootElement">The delegate to use to load if necessary. May be null.</param> 193 /// <param name="isExplicitlyLoaded"><code>true</code> if the project is explicitly loaded, otherwise <code>false</code>.</param> 194 /// <param name="preserveFormatting"><code>true</code> to the project was loaded with the formated preserved, otherwise <code>false</code>.</param> 195 /// <returns>The ProjectRootElement instance if one exists. Null otherwise.</returns> Get(string projectFile, OpenProjectRootElement openProjectRootElement, bool isExplicitlyLoaded, bool? preserveFormatting)196 internal ProjectRootElement Get(string projectFile, OpenProjectRootElement openProjectRootElement, bool isExplicitlyLoaded, 197 bool? preserveFormatting) 198 { 199 // Should already have been canonicalized 200 ErrorUtilities.VerifyThrowInternalRooted(projectFile); 201 202 lock (_locker) 203 { 204 ProjectRootElement projectRootElement; 205 _weakCache.TryGetValue(projectFile, out projectRootElement); 206 207 if (preserveFormatting != null && projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting) 208 { 209 // Cached project doesn't match preserveFormatting setting, so reload it 210 projectRootElement.Reload(true, preserveFormatting); 211 } 212 213 if (projectRootElement != null && _autoReloadFromDisk) 214 { 215 FileInfo fileInfo = FileUtilities.GetFileInfoNoThrow(projectFile); 216 217 // If the file doesn't exist on disk, go ahead and use the cached version. 218 // It's an in-memory project that hasn't been saved yet. 219 if (fileInfo != null) 220 { 221 bool forgetEntry = false; 222 223 if (fileInfo.LastWriteTime != projectRootElement.LastWriteTimeWhenRead) 224 { 225 // File was changed on disk by external means. Cached version is no longer reliable. 226 // We could throw here or ignore the problem, but it is a common and reasonable pattern to change a file 227 // externally and load a new project over it to see the new content. So we dump it from the cache 228 // to force a load from disk. There might then exist more than one ProjectRootElement with the same path, 229 // but clients ought not get themselves into such a state - and unless they save them to disk, 230 // it may not be a problem. 231 forgetEntry = true; 232 } 233 else if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT"))) 234 { 235 // QA tests run too fast for the timestamp check to work. This environment variable is for their 236 // use: it checks the file content as well as the timestamp. That's better than completely disabling 237 // the cache as we get test coverage of the rest of the cache code. 238 XmlDocument document = new XmlDocument(); 239 document.PreserveWhitespace = projectRootElement.XmlDocument.PreserveWhitespace; 240 241 using (var xtr = XmlReaderExtension.Create(projectRootElement.FullPath)) 242 { 243 document.Load(xtr.Reader); 244 } 245 246 string diskContent = document.OuterXml; 247 string cacheContent = projectRootElement.XmlDocument.OuterXml; 248 249 if (diskContent != cacheContent) 250 { 251 forgetEntry = true; 252 } 253 } 254 255 if (forgetEntry) 256 { 257 ForgetEntry(projectRootElement); 258 259 DebugTraceCache("Out of date dropped from XML cache: ", projectFile); 260 projectRootElement = null; 261 } 262 } 263 } 264 265 if (projectRootElement == null && openProjectRootElement != null) 266 { 267 projectRootElement = openProjectRootElement(projectFile, this); 268 269 ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement"); 270 ErrorUtilities.VerifyThrow(projectRootElement.FullPath == projectFile, "Got project back with incorrect path"); 271 ErrorUtilities.VerifyThrow(_weakCache.Contains(projectFile), "Open should have renamed into cache and boosted"); 272 } 273 else if (projectRootElement != null) 274 { 275 DebugTraceCache("Satisfied from XML cache: ", projectFile); 276 BoostEntryInStrongCache(projectRootElement); 277 } 278 279 // An implicit load will never reset the explicit flag. 280 if (projectRootElement != null && isExplicitlyLoaded) 281 { 282 projectRootElement.MarkAsExplicitlyLoaded(); 283 } 284 285 return projectRootElement; 286 } 287 } 288 289 /// <summary> 290 /// Add an entry to the cache. 291 /// </summary> AddEntry(ProjectRootElement projectRootElement)292 internal void AddEntry(ProjectRootElement projectRootElement) 293 { 294 lock (_locker) 295 { 296 RenameEntryInternal(null, projectRootElement); 297 298 RaiseProjectRootElementAddedToCacheEvent(projectRootElement); 299 } 300 } 301 302 /// <summary> 303 /// Raises the <see cref="ProjectRootElementDirtied"/> event. 304 /// </summary> 305 /// <param name="sender">The dirtied project root element.</param> 306 /// <param name="e">Details on the PRE and the nature of the change.</param> OnProjectRootElementDirtied(ProjectRootElement sender, ProjectXmlChangedEventArgs e)307 internal void OnProjectRootElementDirtied(ProjectRootElement sender, ProjectXmlChangedEventArgs e) 308 { 309 var cacheDirtied = this.ProjectRootElementDirtied; 310 if (cacheDirtied != null) 311 { 312 cacheDirtied(sender, e); 313 } 314 } 315 316 /// <summary> 317 /// Raises the <see cref="ProjectDirtied"/> event. 318 /// </summary> 319 /// <param name="sender">The dirtied project.</param> 320 /// <param name="e">Details on the Project and the change.</param> OnProjectDirtied(Project sender, ProjectChangedEventArgs e)321 internal void OnProjectDirtied(Project sender, ProjectChangedEventArgs e) 322 { 323 var projectDirtied = this.ProjectDirtied; 324 if (projectDirtied != null) 325 { 326 projectDirtied(sender, e); 327 } 328 } 329 330 /// <summary> 331 /// Rename an entry in the cache. 332 /// Entry must already be in the cache. 333 /// </summary> RenameEntry(string oldFullPath, ProjectRootElement projectRootElement)334 internal void RenameEntry(string oldFullPath, ProjectRootElement projectRootElement) 335 { 336 lock (_locker) 337 { 338 ErrorUtilities.VerifyThrowArgumentLength(oldFullPath, "oldFullPath"); 339 RenameEntryInternal(oldFullPath, projectRootElement); 340 } 341 } 342 343 /// <summary> 344 /// Returns any a ProjectRootElement in the cache with the provided full path, 345 /// otherwise null. 346 /// </summary> TryGet(string projectFile)347 internal ProjectRootElement TryGet(string projectFile) 348 { 349 return TryGet(projectFile, preserveFormatting: null); 350 } 351 352 /// <summary> 353 /// Returns any a ProjectRootElement in the cache with the provided full path, 354 /// otherwise null. 355 /// </summary> TryGet(string projectFile, bool? preserveFormatting)356 internal ProjectRootElement TryGet(string projectFile, bool? preserveFormatting) 357 { 358 ProjectRootElement result = Get( 359 projectFile, 360 openProjectRootElement: null, // no delegate to load it 361 isExplicitlyLoaded: false, // Since we are not creating a PRE this can be true or false 362 preserveFormatting: preserveFormatting); 363 364 return result; 365 } 366 367 /// <summary> 368 /// Discards strong references held by the cache. 369 /// </summary> 370 /// <remarks> 371 /// The weak cache is never cleared, as we need it to guarantee that the appdomain never 372 /// has two ProjectRootElement's for a particular file. Attempts to clear out the weak cache 373 /// resulted in this guarantee being broken and subtle bugs popping up everywhere. 374 /// </remarks> DiscardStrongReferences()375 internal void DiscardStrongReferences() 376 { 377 lock (_locker) 378 { 379 DebugTraceCache("Clearing strong refs: ", _strongCache.Count); 380 381 LinkedList<ProjectRootElement> oldStrongCache = _strongCache; 382 _strongCache = new LinkedList<ProjectRootElement>(); 383 384 foreach (ProjectRootElement projectRootElement in oldStrongCache) 385 { 386 RaiseProjectRootElementRemovedFromStrongCache(projectRootElement); 387 } 388 389 // A scavenge of the weak cache is probably not worth it as 390 // the GC would have had to run immediately after the line above. 391 } 392 } 393 394 /// <summary> 395 /// Clears out the cache. 396 /// Called when all projects are unloaded and possibly when a build is done. 397 /// </summary> Clear()398 internal void Clear() 399 { 400 lock (_locker) 401 { 402 LinkedList<ProjectRootElement> oldStrongCache = _strongCache; 403 _weakCache = new WeakValueDictionary<string, ProjectRootElement>(StringComparer.OrdinalIgnoreCase); 404 _strongCache = new LinkedList<ProjectRootElement>(); 405 406 foreach (ProjectRootElement projectRootElement in oldStrongCache) 407 { 408 RaiseProjectRootElementRemovedFromStrongCache(projectRootElement); 409 } 410 } 411 } 412 413 /// <summary> 414 /// Discard any entries (weak and strong) which do not have the explicitlyLoaded flag set. 415 /// </summary> DiscardImplicitReferences()416 internal void DiscardImplicitReferences() 417 { 418 lock (_locker) 419 { 420 // Make a new Weak cache only with items that have been explicitly loaded, this will be a small number, there will most likely 421 // be many items which were not explicitly loaded (ie p2p references). 422 WeakValueDictionary<string, ProjectRootElement> oldWeakCache = _weakCache; 423 _weakCache = new WeakValueDictionary<string, ProjectRootElement>(StringComparer.OrdinalIgnoreCase); 424 425 LinkedList<ProjectRootElement> oldStrongCache = _strongCache; 426 _strongCache = new LinkedList<ProjectRootElement>(); 427 428 foreach (string projectPath in oldWeakCache.Keys) 429 { 430 ProjectRootElement rootElement; 431 432 if (oldWeakCache.TryGetValue(projectPath, out rootElement)) 433 { 434 if (rootElement.IsExplicitlyLoaded) 435 { 436 _weakCache[projectPath] = rootElement; 437 } 438 439 if (rootElement.IsExplicitlyLoaded && oldStrongCache.Contains(rootElement)) 440 { 441 _strongCache.AddFirst(rootElement); 442 } 443 else 444 { 445 _strongCache.Remove(rootElement); 446 RaiseProjectRootElementRemovedFromStrongCache(rootElement); 447 } 448 } 449 } 450 } 451 } 452 453 /// <summary> 454 /// Forces a removal of a project root element from the weak cache if it is present. 455 /// </summary> 456 /// <param name="projectRootElement">The project root element to remove.</param> 457 /// <remarks> 458 /// No exception is thrown if this project root element is in use by currently loaded projects 459 /// by this method. The calling method must know that this is a safe operation. 460 /// There may of course be strong references to the project root element from customer code. 461 /// The assumption is that when they instruct the project collection to unload it, which 462 /// leads to this being called, they are releasing their strong references too (or it doesn't matter) 463 /// </remarks> DiscardAnyWeakReference(ProjectRootElement projectRootElement)464 internal void DiscardAnyWeakReference(ProjectRootElement projectRootElement) 465 { 466 ErrorUtilities.VerifyThrowArgumentNull(projectRootElement, "projectRootElement"); 467 468 // A PRE may be unnamed if it was only used in memory. 469 if (projectRootElement.FullPath != null) 470 { 471 lock (_locker) 472 { 473 _weakCache.Remove(projectRootElement.FullPath); 474 } 475 } 476 } 477 478 /// <summary> 479 /// Raises an event which is raised when a project root element is added to the cache. 480 /// </summary> RaiseProjectRootElementAddedToCacheEvent(ProjectRootElement rootElement)481 private void RaiseProjectRootElementAddedToCacheEvent(ProjectRootElement rootElement) 482 { 483 if (ProjectRootElementAddedHandler != null) 484 { 485 ProjectRootElementAddedHandler(this, new ProjectRootElementCacheAddEntryEventArgs(rootElement)); 486 } 487 } 488 489 /// <summary> 490 /// Raises an event which is raised when a project root element is removed from the strong cache. 491 /// </summary> RaiseProjectRootElementRemovedFromStrongCache(ProjectRootElement projectRootElement)492 private void RaiseProjectRootElementRemovedFromStrongCache(ProjectRootElement projectRootElement) 493 { 494 StrongCacheEntryRemovedDelegate removedEvent = StrongCacheEntryRemoved; 495 if (null != removedEvent) 496 { 497 removedEvent(this, projectRootElement); 498 } 499 } 500 501 /// <summary> 502 /// Add or rename an entry in the cache. 503 /// Old full path may be null iff it was not already in the cache. 504 /// </summary> 505 /// <remarks> 506 /// Must be called within the cache lock. 507 /// </remarks> RenameEntryInternal(string oldFullPathIfAny, ProjectRootElement projectRootElement)508 private void RenameEntryInternal(string oldFullPathIfAny, ProjectRootElement projectRootElement) 509 { 510 ErrorUtilities.VerifyThrowInternalNull(projectRootElement.FullPath, "FullPath"); 511 512 if (oldFullPathIfAny != null) 513 { 514 ErrorUtilities.VerifyThrowInternalRooted(oldFullPathIfAny); 515 ErrorUtilities.VerifyThrow(_weakCache[oldFullPathIfAny] == projectRootElement, "Should already be present"); 516 _weakCache.Remove(oldFullPathIfAny); 517 } 518 519 // There may already be a ProjectRootElement in the cache with the new name. In this case we cannot throw an exception; 520 // we must merely replace it. This is because it may be an unrooted entry 521 // (and thus gone from the client's point of view) that merely remains 522 // in the cache because we still have a reference to it from our strong cache. 523 // Another possibility is that there are two, unrelated, un-saved, in-memory projects that were given the same path. 524 // Replacing the cache entry does not in itself cause a problem -- if there are any actual users of the old 525 // entry they will not be affected. There would then exist more than one ProjectRootElement with the same path, 526 // but clients ought not get themselves into such a state - and unless they save them to disk, 527 // it may not be a problem. Replacing also doesn't cause a problem for the strong cache, 528 // as it is never consulted by us, but it is reasonable for us to remove the old entry in that case. 529 ProjectRootElement existingWeakEntry; 530 _weakCache.TryGetValue(projectRootElement.FullPath, out existingWeakEntry); 531 532 if (existingWeakEntry != null && !Object.ReferenceEquals(existingWeakEntry, projectRootElement)) 533 { 534 _strongCache.Remove(existingWeakEntry); 535 RaiseProjectRootElementRemovedFromStrongCache(existingWeakEntry); 536 } 537 538 DebugTraceCache("Adding: ", projectRootElement.FullPath); 539 _weakCache[projectRootElement.FullPath] = projectRootElement; 540 541 BoostEntryInStrongCache(projectRootElement); 542 } 543 544 /// <summary> 545 /// Update the strong cache. 546 /// If the item is already a member of the list, move it to the top. 547 /// Otherwise, just add it to the top. 548 /// If the list is too large, remove an entry from the bottom. 549 /// </summary> 550 /// <remarks> 551 /// Must be called within the cache lock. 552 /// If the size of strong cache gets large, this needs a faster data structure 553 /// than a linked list. It's currently O(n). 554 /// </remarks> BoostEntryInStrongCache(ProjectRootElement projectRootElement)555 private void BoostEntryInStrongCache(ProjectRootElement projectRootElement) 556 { 557 LinkedListNode<ProjectRootElement> node = _strongCache.First; 558 559 while (node != null) 560 { 561 if (Object.ReferenceEquals(node.Value, projectRootElement)) 562 { 563 // DebugTraceCache("Boosting: ", projectRootElement.FullPath); 564 _strongCache.Remove(node); 565 _strongCache.AddFirst(node); 566 567 return; 568 } 569 570 node = node.Next; 571 } 572 573 _strongCache.AddFirst(projectRootElement); 574 575 if (_strongCache.Count > s_maximumStrongCacheSize) 576 { 577 node = _strongCache.Last; 578 579 DebugTraceCache("Shedding: ", node.Value.FullPath); 580 _strongCache.Remove(node); 581 RaiseProjectRootElementRemovedFromStrongCache(node.Value); 582 } 583 } 584 585 /// <summary> 586 /// Completely remove an entry from this cache 587 /// </summary> 588 /// <remarks> 589 /// Must be called within the cache lock. 590 /// </remarks> ForgetEntry(ProjectRootElement projectRootElement)591 private void ForgetEntry(ProjectRootElement projectRootElement) 592 { 593 DebugTraceCache("Forgetting: ", projectRootElement.FullPath); 594 595 _weakCache.Remove(projectRootElement.FullPath); 596 597 LinkedListNode<ProjectRootElement> strongCacheEntry = _strongCache.Find(projectRootElement); 598 if (strongCacheEntry != null) 599 { 600 _strongCache.Remove(strongCacheEntry); 601 RaiseProjectRootElementRemovedFromStrongCache(strongCacheEntry.Value); 602 } 603 } 604 605 /// <summary> 606 /// Write debugging messages to the Debug.Out stream. 607 /// </summary> DebugTraceCache(string message, bool param1)608 private void DebugTraceCache(string message, bool param1) 609 { 610 if (s_debugLogCacheActivity) 611 { 612 DebugTraceCache(message, Convert.ToString(param1, CultureInfo.InvariantCulture)); 613 } 614 } 615 616 /// <summary> 617 /// Write debugging messages to the Debug.Out stream. 618 /// </summary> DebugTraceCache(string message, int param1)619 private void DebugTraceCache(string message, int param1) 620 { 621 if (s_debugLogCacheActivity) 622 { 623 DebugTraceCache(message, Convert.ToString(param1, CultureInfo.InvariantCulture)); 624 } 625 } 626 627 /// <summary> 628 /// Write debugging messages to the Debug.Out stream. 629 /// </summary> DebugTraceCache(string message, string param1)630 private void DebugTraceCache(string message, string param1) 631 { 632 if (s_debugLogCacheActivity) 633 { 634 string prefix = OutOfProcNode.IsOutOfProcNode ? "C" : "P"; 635 Trace.WriteLine(prefix + " " + Process.GetCurrentProcess().Id + " | " + message + param1); 636 } 637 } 638 639 /// <summary> 640 /// This class is an event that holds which ProjectRootElement was added to the root element cache. 641 /// </summary> 642 internal class ProjectRootElementCacheAddEntryEventArgs : EventArgs 643 { 644 /// <summary> 645 /// Root element which was just added to the cache. 646 /// </summary> 647 private ProjectRootElement _rootElement; 648 649 /// <summary> 650 /// Takes the root element which was added to the results cache. 651 /// </summary> ProjectRootElementCacheAddEntryEventArgs(ProjectRootElement element)652 internal ProjectRootElementCacheAddEntryEventArgs(ProjectRootElement element) 653 { 654 _rootElement = element; 655 } 656 657 /// <summary> 658 /// Root element which was just added to the cache. 659 /// </summary> 660 public ProjectRootElement RootElement 661 { 662 get { return _rootElement; } 663 } 664 } 665 } 666 } 667