1 //
2 // System.Web.Caching.Cache
3 //
4 // Author(s):
5 //  Lluis Sanchez (lluis@ximian.com)
6 //  Marek Habersack <mhabersack@novell.com>
7 //
8 // (C) 2005-2009 Novell, Inc (http://novell.com)
9 //
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 
31 using System.Threading;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Linq;
35 using System.Security.Permissions;
36 using System.Web.Configuration;
37 
38 namespace System.Web.Caching
39 {
40 	// CAS - no InheritanceDemand here as the class is sealed
41 	[AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
42 	public sealed class Cache: IEnumerable
43 	{
44 		const int LOW_WATER_MARK = 10000; // Target number of items if high water mark is reached
45 		const int HIGH_WATER_MARK = 15000; // We start collection after exceeding this count
46 
47 		public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
48 		public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
49 
50 		// cacheLock will be released in the code below without checking whether it was
51 		// actually acquired. The API doesn't offer a reliable way to check whether the lock
52 		// is being held by the current thread and since Mono does't implement CER
53 		// (Constrained Execution Regions -
54 		// http://msdn.microsoft.com/en-us/library/ms228973.aspx) currently, we have no
55 		// reliable way of recording the information that the lock has been successfully
56 		// acquired.
57 		// It can happen that a Thread.Abort occurs while acquiring the lock and the lock
58 		// isn't  actually held. In this case the attempt to release a lock will throw an
59 		// exception. It's better than a race of setting a boolean flag  after acquiring the
60 		// lock and then relying upon it here to release it - that may cause a deadlock
61 		// should we fail to release the lock  which was successfully acquired but
62 		// Thread.Abort happened right after that during the stloc instruction to set the
63 		// boolean flag. Once CERs are supported we can use the boolean flag reliably.
64 		ReaderWriterLockSlim cacheLock;
65 		CacheItemLRU cache;
66 		CacheItemPriorityQueue timedItems;
67 		Timer expirationTimer;
68 		long expirationTimerPeriod = 0;
69 		Cache dependencyCache;
70 		bool? disableExpiration;
71 		long privateBytesLimit = -1;
72 		long percentagePhysicalMemoryLimit = -1;
73 
74 		bool DisableExpiration {
75 			get {
76 				if (disableExpiration == null) {
77 					var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
78 					if (cs == null)
79 						disableExpiration = false;
80 					else
81 						disableExpiration = (bool)cs.DisableExpiration;
82 				}
83 
84 				return (bool)disableExpiration;
85 			}
86 		}
87 
88 		public long EffectivePrivateBytesLimit {
89 			get {
90 				if (privateBytesLimit == -1) {
91 					var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
92 					if (cs == null)
93 						privateBytesLimit = 0;
94 					else
95 						privateBytesLimit = cs.PrivateBytesLimit;
96 
97 					if (privateBytesLimit == 0) {
98 						// http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
99 						// TODO: calculate
100 						privateBytesLimit = 734003200;
101 					}
102 				}
103 
104 				return privateBytesLimit;
105 			}
106 		}
107 
108 		public long EffectivePercentagePhysicalMemoryLimit {
109 			get {
110 				if (percentagePhysicalMemoryLimit == -1) {
111 					var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
112 					if (cs == null)
113 						percentagePhysicalMemoryLimit = 0;
114 					else
115 						percentagePhysicalMemoryLimit = cs.PercentagePhysicalMemoryUsedLimit;
116 
117 					if (percentagePhysicalMemoryLimit == 0) {
118 						// http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
119 						// TODO: calculate
120 						percentagePhysicalMemoryLimit = 97;
121 					}
122 				}
123 
124 				return percentagePhysicalMemoryLimit;
125 			}
126 		}
127 
Cache()128 		public Cache ()
129 		{
130 			cacheLock = new ReaderWriterLockSlim ();
131 			cache = new CacheItemLRU (this, HIGH_WATER_MARK, LOW_WATER_MARK);
132 		}
133 
134 		public int Count {
135 			get { return cache.Count; }
136 		}
137 
138 		public object this [string key] {
139 			get { return Get (key); }
140 			set { Insert (key, value); }
141 		}
142 
143 		// Must ALWAYS be called with the cache write lock held
RemoveCacheItem(string key)144 		CacheItem RemoveCacheItem (string key)
145 		{
146 			if (key == null)
147 				return null;
148 
149 			CacheItem ret = cache [key];
150 			if (ret == null)
151 				return null;
152 
153 			if (timedItems != null)
154 				timedItems.OnItemDisable (ret);
155 
156 			ret.Disabled = true;
157 			cache.Remove (key);
158 
159 			return ret;
160 		}
161 
Add(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)162 		public object Add (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
163 		{
164 			if (key == null)
165 				throw new ArgumentNullException ("key");
166 
167 			try {
168 				cacheLock.EnterWriteLock ();
169 				CacheItem it = cache [key];
170 
171 				if (it != null)
172 					return it.Value;
173 				Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, false);
174 			} finally {
175 				// See comment at the top of the file, above cacheLock declaration
176 				cacheLock.ExitWriteLock ();
177 			}
178 
179 			return null;
180 		}
181 
Get(string key)182 		public object Get (string key)
183 		{
184 			try {
185 				cacheLock.EnterUpgradeableReadLock ();
186 				CacheItem it = cache [key];
187 				if (it == null)
188 					return null;
189 
190 				if (it.Dependency != null && it.Dependency.HasChanged) {
191 					try {
192 						cacheLock.EnterWriteLock ();
193 						if (!NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
194 							Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
195 					} finally {
196 						// See comment at the top of the file, above cacheLock declaration
197 						cacheLock.ExitWriteLock ();
198 					}
199 
200 					return null;
201 				}
202 
203 				if (!DisableExpiration) {
204 					if (it.SlidingExpiration != NoSlidingExpiration) {
205 						it.AbsoluteExpiration = DateTime.Now + it.SlidingExpiration;
206 						// Cast to long is ok since we know that sliding expiration
207 						// is less than 365 days (31536000000ms)
208 						long remaining = (long)it.SlidingExpiration.TotalMilliseconds;
209 						it.ExpiresAt = it.AbsoluteExpiration.Ticks;
210 
211 						if (expirationTimer != null && (expirationTimerPeriod == 0 || expirationTimerPeriod > remaining)) {
212 							expirationTimerPeriod = remaining;
213 							expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
214 						}
215 
216 					} else if (DateTime.Now >= it.AbsoluteExpiration) {
217 						try {
218 							cacheLock.EnterWriteLock ();
219 							if (!NeedsUpdate (it, CacheItemUpdateReason.Expired, false))
220 								Remove (key, CacheItemRemovedReason.Expired, false, true);
221 						} finally {
222 							// See comment at the top of the file, above cacheLock declaration
223 							cacheLock.ExitWriteLock ();
224 						}
225 
226 						return null;
227 					}
228 				}
229 
230 				return it.Value;
231 			} finally {
232 				// See comment at the top of the file, above cacheLock declaration
233 				cacheLock.ExitUpgradeableReadLock ();
234 			}
235 		}
236 
Insert(string key, object value)237 		public void Insert (string key, object value)
238 		{
239 			Insert (key, value, null, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
240 		}
241 
Insert(string key, object value, CacheDependency dependencies)242 		public void Insert (string key, object value, CacheDependency dependencies)
243 		{
244 			Insert (key, value, dependencies, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
245 		}
246 
Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration)247 		public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration)
248 		{
249 			Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, null, true);
250 		}
251 
Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemUpdateCallback onUpdateCallback)252 		public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
253 				    CacheItemUpdateCallback onUpdateCallback)
254 		{
255 			Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, onUpdateCallback, true);
256 		}
257 
Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)258 		public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
259 				    CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
260 		{
261 			Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, true);
262 		}
263 
Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback, CacheItemUpdateCallback onUpdateCallback, bool doLock)264 		void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
265 			     CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback, CacheItemUpdateCallback onUpdateCallback, bool doLock)
266 		{
267 			if (key == null)
268 				throw new ArgumentNullException ("key");
269 			if (value == null)
270 				throw new ArgumentNullException ("value");
271 			if (slidingExpiration < TimeSpan.Zero || slidingExpiration > TimeSpan.FromDays (365))
272 				throw new ArgumentNullException ("slidingExpiration");
273 			if (absoluteExpiration != NoAbsoluteExpiration && slidingExpiration != NoSlidingExpiration)
274 				throw new ArgumentException ("Both absoluteExpiration and slidingExpiration are specified");
275 
276 			CacheItem ci = new CacheItem ();
277 			ci.Value = value;
278 			ci.Key = key;
279 
280 			if (dependencies != null) {
281 				ci.Dependency = dependencies;
282 				dependencies.DependencyChanged += new EventHandler (OnDependencyChanged);
283 				dependencies.SetCache (DependencyCache);
284 			}
285 
286 			ci.Priority = priority;
287 			SetItemTimeout (ci, absoluteExpiration, slidingExpiration, onRemoveCallback, onUpdateCallback, key, doLock);
288 		}
289 
SetItemTimeout(string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock)290 		internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock)
291 		{
292 			CacheItem ci = null;
293 			try {
294 				if (doLock)
295 					cacheLock.EnterWriteLock ();
296 
297 				ci = cache [key];
298 				if (ci != null)
299 					SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, key, false);
300 			} finally {
301 				if (doLock) {
302 					// See comment at the top of the file, above cacheLock declaration
303 					cacheLock.ExitWriteLock ();
304 				}
305 			}
306 		}
307 
SetItemTimeout(CacheItem ci, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemRemovedCallback onRemoveCallback, CacheItemUpdateCallback onUpdateCallback, string key, bool doLock)308 		void SetItemTimeout (CacheItem ci, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemRemovedCallback onRemoveCallback,
309 				     CacheItemUpdateCallback onUpdateCallback, string key, bool doLock)
310 		{
311 			bool disableExpiration = DisableExpiration;
312 
313 			if (!disableExpiration) {
314 				ci.SlidingExpiration = slidingExpiration;
315 				if (slidingExpiration != NoSlidingExpiration)
316 					ci.AbsoluteExpiration = DateTime.Now + slidingExpiration;
317 				else
318 					ci.AbsoluteExpiration = absoluteExpiration;
319 			}
320 
321 			ci.OnRemoveCallback = onRemoveCallback;
322 			ci.OnUpdateCallback = onUpdateCallback;
323 
324 			try {
325 				if (doLock)
326 					cacheLock.EnterWriteLock ();
327 
328 				if (key != null) {
329 					cache [key] = ci;
330 					cache.EvictIfNecessary ();
331 				}
332 
333 				ci.LastChange = DateTime.Now;
334 				if (!disableExpiration && ci.AbsoluteExpiration != NoAbsoluteExpiration) {
335 					bool enqueue;
336 					if (ci.IsTimedItem) {
337 						enqueue = UpdateTimedItem (ci);
338 						if (!enqueue)
339 							UpdateTimerPeriod (ci);
340 					} else
341 						enqueue = true;
342 
343 					if (enqueue) {
344 						ci.IsTimedItem = true;
345 						EnqueueTimedItem (ci);
346 					}
347 
348 				}
349 			} finally {
350 				if (doLock) {
351 					// See comment at the top of the file, above cacheLock declaration
352 					cacheLock.ExitWriteLock ();
353 				}
354 			}
355 		}
356 
357 		// MUST be called with cache lock held
UpdateTimedItem(CacheItem item)358 		bool UpdateTimedItem (CacheItem item)
359 		{
360 			if (timedItems == null)
361 				return true;
362 
363 			item.ExpiresAt = item.AbsoluteExpiration.Ticks;
364 			return !timedItems.Update (item);
365 		}
366 
367 		// MUST be called with cache lock held
UpdateTimerPeriod(CacheItem item)368 		void UpdateTimerPeriod (CacheItem item)
369 		{
370 			if (timedItems == null)
371 				timedItems = new CacheItemPriorityQueue ();
372 
373 			long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - DateTime.Now).TotalMilliseconds);
374 			item.ExpiresAt = item.AbsoluteExpiration.Ticks;
375 
376 			if (remaining > 4294967294)
377 				// Maximum due time for timer
378 				// Item will expire properly anyway, as the timer will be
379 				// rescheduled for the item's expiration time once that item is
380 				// bubbled to the top of the priority queue.
381 				remaining = 4294967294;
382 
383 			if (expirationTimer != null && expirationTimerPeriod <= remaining)
384 				return;
385 			expirationTimerPeriod = remaining;
386 
387 			if (expirationTimer == null)
388 				expirationTimer = new Timer (new TimerCallback (ExpireItems), null, expirationTimerPeriod, expirationTimerPeriod);
389 			else
390 				expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
391 		}
392 
393 		// MUST be called with cache lock held
EnqueueTimedItem(CacheItem item)394 		void EnqueueTimedItem (CacheItem item)
395 		{
396 			UpdateTimerPeriod (item);
397 			timedItems.Enqueue (item);
398 		}
399 
Remove(string key)400 		public object Remove (string key)
401 		{
402 			return Remove (key, CacheItemRemovedReason.Removed, true, true);
403 		}
404 
Remove(string key, CacheItemRemovedReason reason, bool doLock, bool invokeCallback)405 		internal object Remove (string key, CacheItemRemovedReason reason, bool doLock, bool invokeCallback)
406 		{
407 			CacheItem it = null;
408 			try {
409 				if (doLock)
410 					cacheLock.EnterWriteLock ();
411 
412 				it = RemoveCacheItem (key);
413 			} finally {
414 				if (doLock) {
415 					// See comment at the top of the file, above cacheLock declaration
416 					cacheLock.ExitWriteLock ();
417 				}
418 			}
419 
420 			object ret = null;
421 			if (it != null) {
422 				if (it.Dependency != null) {
423 					it.Dependency.SetCache (null);
424 					it.Dependency.DependencyChanged -= new EventHandler (OnDependencyChanged);
425 					it.Dependency.Dispose ();
426 				}
427 				if (invokeCallback && it.OnRemoveCallback != null) {
428 					try {
429 						it.OnRemoveCallback (key, it.Value, reason);
430 					} catch {
431 						//TODO: anything to be done here?
432 					}
433 				}
434 				ret = it.Value;
435 				it.Value = null;
436 				it.Key = null;
437 				it.Dependency = null;
438 				it.OnRemoveCallback = null;
439 				it.OnUpdateCallback = null;
440 				it = null;
441 			}
442 
443 			return ret;
444 		}
445 
446 		// Used when shutting down the application so that
447 		// session_end events are sent for all sessions.
InvokePrivateCallbacks()448 		internal void InvokePrivateCallbacks ()
449 		{
450 			try {
451 				cacheLock.EnterReadLock ();
452 				cache.InvokePrivateCallbacks ();
453 			}  finally {
454 				// See comment at the top of the file, above cacheLock declaration
455 				cacheLock.ExitReadLock ();
456 			}
457 		}
458 
GetEnumerator()459 		public IDictionaryEnumerator GetEnumerator ()
460 		{
461 			List <CacheItem> list = null;
462 			try {
463 				cacheLock.EnterReadLock ();
464 				list = cache.ToList ();
465 			} finally {
466 				// See comment at the top of the file, above cacheLock declaration
467 				cacheLock.ExitReadLock ();
468 			}
469 
470 			return new CacheItemEnumerator (list);
471 		}
472 
IEnumerable.GetEnumerator()473 		IEnumerator IEnumerable.GetEnumerator ()
474 		{
475 			return GetEnumerator ();
476 		}
477 
OnDependencyChanged(object o, EventArgs a)478 		void OnDependencyChanged (object o, EventArgs a)
479 		{
480 			CheckDependencies ();
481 		}
482 
NeedsUpdate(CacheItem item, CacheItemUpdateReason reason, bool needLock)483 		bool NeedsUpdate (CacheItem item, CacheItemUpdateReason reason, bool needLock)
484 		{
485 			try {
486 				if (needLock)
487 					cacheLock.EnterWriteLock ();
488 
489 				if (item == null || item.OnUpdateCallback == null)
490 					return false;
491 
492 				object expensiveObject;
493 				CacheDependency dependency;
494 				DateTime absoluteExpiration;
495 				TimeSpan slidingExpiration;
496 				string key = item.Key;
497 				CacheItemUpdateCallback updateCB = item.OnUpdateCallback;
498 
499 				updateCB (key, reason, out expensiveObject, out dependency, out absoluteExpiration, out slidingExpiration);
500 				if (expensiveObject == null)
501 					return false;
502 
503 				CacheItemPriority priority = item.Priority;
504 				CacheItemRemovedCallback removeCB = item.OnRemoveCallback;
505 				CacheItemRemovedReason whyRemoved;
506 
507 				switch (reason) {
508 					case CacheItemUpdateReason.Expired:
509 						whyRemoved = CacheItemRemovedReason.Expired;
510 						break;
511 
512 					case CacheItemUpdateReason.DependencyChanged:
513 						whyRemoved = CacheItemRemovedReason.DependencyChanged;
514 						break;
515 
516 					default:
517 						whyRemoved = CacheItemRemovedReason.Removed;
518 						break;
519 				}
520 
521 				Remove (key, whyRemoved, false, false);
522 				Insert (key, expensiveObject, dependency, absoluteExpiration, slidingExpiration, priority, removeCB, updateCB, false);
523 
524 				return true;
525 			} catch (Exception) {
526 				return false;
527 			} finally {
528 				if (needLock) {
529 					// See comment at the top of the file, above cacheLock declaration
530 					cacheLock.ExitWriteLock ();
531 				}
532 			}
533 		}
534 
ExpireItems(object data)535 		void ExpireItems (object data)
536 		{
537 			DateTime now = DateTime.Now;
538 			CacheItem item = null;
539 
540 			expirationTimer.Change (Timeout.Infinite, Timeout.Infinite);
541 			try {
542 				cacheLock.EnterWriteLock ();
543 				while (true) {
544 					item = timedItems.Peek ();
545 
546 					if (item == null) {
547 						if (timedItems.Count == 0)
548 							break;
549 
550 						timedItems.Dequeue ();
551 						continue;
552 					}
553 
554 					if (!item.Disabled && item.ExpiresAt > now.Ticks)
555 						break;
556 
557 					if (item.Disabled) {
558 						item = timedItems.Dequeue ();
559 						continue;
560 					}
561 
562 					item = timedItems.Dequeue ();
563 					if (item != null)
564 						if (!NeedsUpdate (item, CacheItemUpdateReason.Expired, false))
565 							Remove (item.Key, CacheItemRemovedReason.Expired, false, true);
566 				}
567 			} finally {
568 				// See comment at the top of the file, above cacheLock declaration
569 				cacheLock.ExitWriteLock ();
570 			}
571 
572 			if (item != null) {
573 				long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - now).TotalMilliseconds);
574 				if (remaining > 0 && (expirationTimerPeriod == 0 || expirationTimerPeriod > remaining)) {
575 					expirationTimerPeriod = remaining;
576 					expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
577 					return;
578 				}
579 				if (expirationTimerPeriod > 0)
580 					return;
581 			}
582 
583 			expirationTimer.Change (Timeout.Infinite, Timeout.Infinite);
584 			expirationTimerPeriod = 0;
585 		}
586 
CheckDependencies()587 		internal void CheckDependencies ()
588 		{
589 			try {
590 				cacheLock.EnterWriteLock ();
591 				List <CacheItem> list = cache.SelectItems (it => {
592 					if (it == null)
593 						return false;
594 					if (it.Dependency != null && it.Dependency.HasChanged && !NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
595 						return true;
596 					return false;
597 				});
598 
599 				foreach (CacheItem it in list)
600 					Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
601 				list.Clear ();
602 				list.TrimExcess ();
603 			} finally {
604 				// See comment at the top of the file, above cacheLock declaration
605 				cacheLock.ExitWriteLock ();
606 			}
607 		}
608 
GetKeyLastChange(string key)609 		internal DateTime GetKeyLastChange (string key)
610 		{
611 			try {
612 				cacheLock.EnterReadLock ();
613 				CacheItem it = cache [key];
614 
615 				if (it == null)
616 					return DateTime.MaxValue;
617 
618 				return it.LastChange;
619 			} finally {
620 				// See comment at the top of the file, above cacheLock declaration
621 				cacheLock.ExitReadLock ();
622 			}
623 		}
624 
625 		internal Cache DependencyCache {
626 			get {
627 				if (dependencyCache == null)
628 					return this;
629 
630 				return dependencyCache;
631 			}
632 			set { dependencyCache = value; }
633 		}
634 	}
635 }
636