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