1 //------------------------------------------------------------------------------
2 // <copyright file="cache.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
10 * Copyright (c) 1999 Microsoft Corporation
13 namespace System.Web.Caching {
14 using System.Collections;
15 using System.Collections.Specialized;
16 using System.Configuration;
17 using System.Diagnostics;
18 using System.Diagnostics.CodeAnalysis;
19 using System.Runtime.InteropServices;
20 using System.Threading;
21 using System.Web.Util;
23 using Microsoft.Win32;
24 using System.Security.Permissions;
25 using System.Globalization;
26 using System.Web.Configuration;
27 using System.Web.Hosting;
28 using System.Web.Management;
29 using Debug = System.Web.Util.Debug;
33 /// <para>Represents the method that will handle the <see langword='onRemoveCallback'/>
34 /// event of a System.Web.Caching.Cache instance.</para>
36 public delegate void CacheItemRemovedCallback(
37 string key, object value, CacheItemRemovedReason reason);
40 /// <para>Represents the method that will handle the <see langword='onUpdateCallback'/>
41 /// event of a System.Web.Caching.Cache instance.</para>
43 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters",
44 Justification="Shipped this way in NetFx 2.0 SP2")]
45 public delegate void CacheItemUpdateCallback(
46 string key, CacheItemUpdateReason reason,
47 out object expensiveObject, out CacheDependency dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration);
50 /// <para> Specifies the relative priority of items stored in the System.Web.Caching.Cache. When the Web
51 /// server runs low on memory, the Cache selectively purges items to free system
52 /// memory. Items with higher priorities are less likely to be removed from the
53 /// cache when the server is under load. Web
54 /// applications can use these
55 /// values to prioritize cached items relative to one another. The default is
58 public enum CacheItemPriority {
61 /// <para> The cahce items with this priority level will be the first
62 /// to be removed when the server frees system memory by deleting items from the
68 /// <para> The cache items with this priority level
69 /// are in the second group to be removed when the server frees system memory by
70 /// deleting items from the cache. </para>
75 /// <para> The cache items with this priority level are in
76 /// the third group to be removed when the server frees system memory by deleting items from the cache. This is the default. </para>
81 /// <para> The cache items with this priority level are in the
82 /// fourth group to be removed when the server frees system memory by deleting items from the
88 /// <para>The cache items with this priority level are in the fifth group to be removed
89 /// when the server frees system memory by deleting items from the cache. </para>
94 /// <para>The cache items with this priority level will not be removed when the server
95 /// frees system memory by deleting items from the cache. </para>
100 /// <para>The default value is Normal.</para>
107 /// <para>Specifies the reason that a cached item was removed.</para>
109 public enum CacheItemRemovedReason {
112 /// <para>The item was removed from the cache by the 'System.Web.Caching.Cache.Remove' method, or by an System.Web.Caching.Cache.Insert method call specifying the same key.</para>
117 /// <para>The item was removed from the cache because it expired. </para>
122 /// <para>The item was removed from the cache because the value in the hitInterval
123 /// parameter was not met, or because the system removed it to free memory.</para>
128 /// <para>The item was removed from the cache because a file or key dependency was
135 /// <para>Specifies the reason why a cached item needs to be updated.</para>
137 [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue",
138 Justification = "This enum should mirror CacheItemRemovedReason enum in design")]
139 public enum CacheItemUpdateReason {
142 /// <para>The item needs to be updated because it expired. </para>
147 /// <para>The item needs to be updated because a file or key dependency was
153 enum CacheGetOptions {
155 ReturnCacheEntry = 0x1,
160 /// <para>Implements the cache for a Web application. There is only one instance of
161 /// this class per application domain, and it remains valid only as long as the
162 /// application domain remains active. Information about an instance of this class
163 /// is available through the <see langword='Cache'/> property of the System.Web.HttpContext.</para>
168 // - The Cache object contains a CacheInternal object.
169 // - The CacheInternal object is either a CacheSingle, or a CacheMultiple which contains mulitple
170 // CacheSingle objects.
172 public sealed class Cache : IEnumerable {
175 /// <para>Sets the absolute expiration policy to, in essence,
176 /// never. When set, this field is equal to the the System.DateTime.MaxValue , which is a constant
177 /// representing the largest possible <see langword='DateTime'/> value. The maximum date and
178 /// time value is equivilant to "12/31/9999 11:59:59 PM". This field is read-only.</para>
180 public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
184 /// <para>Sets the amount of time for sliding cache expirations to
185 /// zero. When set, this field is equal to the System.TimeSpan.Zero field, which is a constant value of
186 /// zero. This field is read-only.</para>
188 public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
190 CacheInternal _cacheInternal;
191 static CacheItemRemovedCallback s_sentinelRemovedCallback = new CacheItemRemovedCallback(SentinelEntry.OnCacheItemRemovedCallback);
195 /// <para>This constructor is for internal use only, and was accidentally made public - do not use.</para>
197 [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
202 // internal ctor used by CacheCommon that avoids the demand for UnmanagedCode.
204 internal Cache(int dummy) {
207 internal void SetCacheInternal(CacheInternal cacheInternal) {
208 _cacheInternal = cacheInternal;
213 /// <para>Gets the number of items stored in the cache. This value can be useful when
214 /// monitoring your application's performance or when using the ASP.NET tracing
215 /// functionality.</para>
219 return _cacheInternal.PublicCount;
225 IEnumerator IEnumerable.GetEnumerator() {
226 return ((IEnumerable)_cacheInternal).GetEnumerator();
231 /// <para>Returns a dictionary enumerator used for iterating through the key/value
232 /// pairs contained in the cache. Items can be added to or removed from the cache
233 /// while this method is enumerating through the cache items.</para>
235 public IDictionaryEnumerator GetEnumerator() {
236 return _cacheInternal.GetEnumerator();
241 /// <para>Gets or sets an item in the cache.</para>
243 public object this[string key] {
253 private class SentinelEntry {
255 private CacheDependency _expensiveObjectDependency;
256 private CacheItemUpdateCallback _cacheItemUpdateCallback;
258 public SentinelEntry(string key, CacheDependency expensiveObjectDependency, CacheItemUpdateCallback callback) {
260 _expensiveObjectDependency = expensiveObjectDependency;
261 _cacheItemUpdateCallback = callback;
268 public CacheDependency ExpensiveObjectDependency {
269 get { return _expensiveObjectDependency; }
272 public CacheItemUpdateCallback CacheItemUpdateCallback {
273 get { return _cacheItemUpdateCallback; }
276 public static void OnCacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason) {
277 CacheItemUpdateReason updateReason;
278 SentinelEntry entry = value as SentinelEntry;
281 case CacheItemRemovedReason.Expired:
282 updateReason = CacheItemUpdateReason.Expired;
284 case CacheItemRemovedReason.DependencyChanged:
285 updateReason = CacheItemUpdateReason.DependencyChanged;
286 if (entry.ExpensiveObjectDependency.HasChanged) {
287 // If the expensiveObject has been removed explicitly by Cache.Remove,
288 // return from the SentinelEntry removed callback
289 // thus effectively removing the SentinelEntry from the cache.
293 case CacheItemRemovedReason.Underused:
294 Debug.Fail("Reason should never be CacheItemRemovedReason.Underused since the entry was inserted as NotRemovable.");
297 // do nothing if reason is Removed
301 CacheDependency cacheDependency;
302 DateTime absoluteExpiration;
303 TimeSpan slidingExpiration;
304 object expensiveObject;
305 CacheItemUpdateCallback callback = entry.CacheItemUpdateCallback;
306 // invoke update callback
308 callback(entry.Key, updateReason, out expensiveObject, out cacheDependency, out absoluteExpiration, out slidingExpiration);
309 // Dev10 861163 - Only update the "expensive" object if the user returns a new object and the
310 // cache dependency hasn't changed. (Inserting with a cache dependency that has already changed will cause recursion.)
311 if (expensiveObject != null && (cacheDependency == null || !cacheDependency.HasChanged)) {
312 HttpRuntime.Cache.Insert(entry.Key, expensiveObject, cacheDependency, absoluteExpiration, slidingExpiration, entry.CacheItemUpdateCallback);
315 HttpRuntime.Cache.Remove(entry.Key);
318 catch (Exception e) {
319 HttpRuntime.Cache.Remove(entry.Key);
321 WebBaseEvent.RaiseRuntimeError(e, value);
330 /// <para>Retrieves an item from the cache.</para>
332 public object Get(string key) {
333 return _cacheInternal.DoGet(true, key, CacheGetOptions.None);
336 internal object Get(string key, CacheGetOptions getOptions) {
337 return _cacheInternal.DoGet(true, key, getOptions);
342 /// <para>Inserts an item into the Cache with default values.</para>
344 public void Insert(string key, object value) {
345 _cacheInternal.DoInsert(
350 NoAbsoluteExpiration,
352 CacheItemPriority.Default,
359 /// <para>Inserts an object into the System.Web.Caching.Cache that has file or key
360 /// dependencies.</para>
362 public void Insert(string key, object value, CacheDependency dependencies) {
363 _cacheInternal.DoInsert(
368 NoAbsoluteExpiration,
370 CacheItemPriority.Default,
377 /// <para>Inserts an object into the System.Web.Caching.Cache that has file or key dependencies and
378 /// expires at the value set in the <paramref name="absoluteExpiration"/> parameter.</para>
380 public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration) {
381 DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
382 _cacheInternal.DoInsert(
387 utcAbsoluteExpiration,
389 CacheItemPriority.Default,
398 CacheDependency dependencies,
399 DateTime absoluteExpiration,
400 TimeSpan slidingExpiration,
401 CacheItemPriority priority,
402 CacheItemRemovedCallback onRemoveCallback) {
404 DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
405 _cacheInternal.DoInsert(
410 utcAbsoluteExpiration,
417 // DevDiv Bugs 162763:
418 // Add a an event that fires *before* an item is evicted from the ASP.NET Cache
422 CacheDependency dependencies,
423 DateTime absoluteExpiration,
424 TimeSpan slidingExpiration,
425 CacheItemUpdateCallback onUpdateCallback) {
427 if (dependencies == null && absoluteExpiration == Cache.NoAbsoluteExpiration && slidingExpiration == Cache.NoSlidingExpiration) {
428 throw new ArgumentException(SR.GetString(SR.Invalid_Parameters_To_Insert));
430 if (onUpdateCallback == null) {
431 throw new ArgumentNullException("onUpdateCallback");
433 DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
434 // Insert updatable cache entry
435 _cacheInternal.DoInsert (
440 Cache.NoAbsoluteExpiration,
441 Cache.NoSlidingExpiration,
442 CacheItemPriority.NotRemovable,
445 // Ensure the sentinel depends on its updatable entry
446 string[] cacheKeys = { key };
447 CacheDependency expensiveObjectDep = new CacheDependency(null, cacheKeys);
448 if (dependencies == null) {
449 dependencies = expensiveObjectDep;
452 AggregateCacheDependency deps = new AggregateCacheDependency();
453 deps.Add(dependencies, expensiveObjectDep);
456 // Insert sentinel entry for the updatable cache entry
457 _cacheInternal.DoInsert(
459 CacheInternal.PrefixValidationSentinel + key,
460 new SentinelEntry(key, expensiveObjectDep, onUpdateCallback),
462 utcAbsoluteExpiration,
464 CacheItemPriority.NotRemovable,
465 Cache.s_sentinelRemovedCallback,
473 CacheDependency dependencies,
474 DateTime absoluteExpiration,
475 TimeSpan slidingExpiration,
476 CacheItemPriority priority,
477 CacheItemRemovedCallback onRemoveCallback) {
479 DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
480 return _cacheInternal.DoInsert(
485 utcAbsoluteExpiration,
494 /// <para>Removes the specified item from the cache. </para>
496 public object Remove(string key) {
497 CacheKey cacheKey = new CacheKey(key, true);
498 return _cacheInternal.DoRemove(cacheKey, CacheItemRemovedReason.Removed);
501 public long EffectivePrivateBytesLimit {
503 return _cacheInternal.EffectivePrivateBytesLimit;
507 public long EffectivePercentagePhysicalMemoryLimit {
509 return _cacheInternal.EffectivePercentagePhysicalMemoryLimit;
515 const int MEMORYSTATUS_INTERVAL_5_SECONDS = 5 * Msec.ONE_SECOND;
516 const int MEMORYSTATUS_INTERVAL_30_SECONDS = 30 * Msec.ONE_SECOND;
518 internal CacheInternal _cacheInternal;
519 internal Cache _cachePublic;
520 internal protected CacheMemoryStats _cacheMemoryStats;
521 private object _timerLock = new object();
522 private Timer _timer;
523 private int _currentPollInterval = MEMORYSTATUS_INTERVAL_30_SECONDS;
524 internal int _inCacheManagerThread;
525 internal bool _enableMemoryCollection;
526 internal bool _enableExpiration;
527 internal bool _internalConfigRead;
528 internal SRefMultiple _srefMultiple;
530 internal CacheCommon() {
531 _cachePublic = new Cache(0);
532 _srefMultiple = new SRefMultiple();
533 _cacheMemoryStats = new CacheMemoryStats(_srefMultiple);
534 _enableMemoryCollection = true;
535 _enableExpiration = true;
538 internal void Dispose(bool disposing) {
540 EnableCacheMemoryTimer(false);
541 _cacheMemoryStats.Dispose();
545 internal void AddSRefTarget(CacheInternal c) {
546 _srefMultiple.AddSRefTarget(c);
549 internal void SetCacheInternal(CacheInternal cacheInternal) {
550 _cacheInternal = cacheInternal;
551 _cachePublic.SetCacheInternal(cacheInternal);
554 internal void ReadCacheInternalConfig(CacheSection cacheSection) {
555 if (_internalConfigRead) {
560 if (_internalConfigRead) {
564 // Set it to true here so that even if we have to call ReadCacheInternalConfig
565 // from the code below, we won't get into an infinite loop.
566 _internalConfigRead = true;
568 if (cacheSection != null) {
569 _enableMemoryCollection = (!cacheSection.DisableMemoryCollection);
570 _enableExpiration = (!cacheSection.DisableExpiration);
571 _cacheMemoryStats.ReadConfig(cacheSection);
572 _currentPollInterval = CacheMemorySizePressure.PollInterval;
573 ResetFromConfigSettings();
578 internal void ResetFromConfigSettings() {
579 EnableCacheMemoryTimer(_enableMemoryCollection);
580 _cacheInternal.EnableExpirationTimer(_enableExpiration);
583 internal void EnableCacheMemoryTimer(bool enable) {
586 if (Debug.IsTagPresent("Timer") && !Debug.IsTagEnabled("Timer")) {
594 if (_timer == null) {
595 // <cache privateBytesPollTime> has not been read yet
596 _timer = new Timer(new TimerCallback(this.CacheManagerTimerCallback), null, _currentPollInterval, _currentPollInterval);
597 Debug.Trace("Cache", "Started CacheMemoryTimers");
600 _timer.Change(_currentPollInterval, _currentPollInterval);
604 Timer timer = _timer;
605 if (timer != null && Interlocked.CompareExchange(ref _timer, null, timer) == timer) {
607 Debug.Trace("Cache", "Stopped CacheMemoryTimers");
613 // wait for CacheManagerTimerCallback to finish
614 while(_inCacheManagerThread != 0) {
626 // the order of these if statements is important
628 // When above the high pressure mark, interval should be 5 seconds or less
629 if (_cacheMemoryStats.IsAboveHighPressure()) {
630 if (_currentPollInterval > MEMORYSTATUS_INTERVAL_5_SECONDS) {
631 _currentPollInterval = MEMORYSTATUS_INTERVAL_5_SECONDS;
632 _timer.Change(_currentPollInterval, _currentPollInterval);
637 // When above half the low pressure mark, interval should be 30 seconds or less
638 if ((_cacheMemoryStats.CacheSizePressure.PressureLast > _cacheMemoryStats.CacheSizePressure.PressureLow/2)
639 || (_cacheMemoryStats.TotalMemoryPressure.PressureLast > _cacheMemoryStats.TotalMemoryPressure.PressureLow/2)) {
640 // DevDivBugs 104034: allow interval to fall back down when memory pressure goes away
641 int newPollInterval = Math.Min(CacheMemorySizePressure.PollInterval, MEMORYSTATUS_INTERVAL_30_SECONDS);
642 if (_currentPollInterval != newPollInterval) {
643 _currentPollInterval = newPollInterval;
644 _timer.Change(_currentPollInterval, _currentPollInterval);
649 // there is no pressure, interval should be the value from config
650 if (_currentPollInterval != CacheMemorySizePressure.PollInterval) {
651 _currentPollInterval = CacheMemorySizePressure.PollInterval;
652 _timer.Change(_currentPollInterval, _currentPollInterval);
657 void CacheManagerTimerCallback(object state) {
658 CacheManagerThread(0);
661 internal long CacheManagerThread(int minPercent) {
662 if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
665 Debug.Trace("CacheMemory", "**BEG** CacheManagerThread " + HttpRuntime.AppDomainAppId + ", " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
668 // Dev10 633335: if the timer has been disposed, return without doing anything
672 // The timer thread must always call Update so that the CacheManager
673 // knows the size of the cache.
674 _cacheMemoryStats.Update();
676 int percent = Math.Max(minPercent, _cacheMemoryStats.GetPercentToTrim());
677 long beginTotalCount = _cacheInternal.TotalCount;
678 Stopwatch sw = Stopwatch.StartNew();
679 long trimmedOrExpired = _cacheInternal.TrimIfNecessary(percent);
681 // 1) don't update stats if the trim happend because MAX_COUNT was exceeded
682 // 2) don't update stats unless we removed at least one entry
683 if (percent > 0 && trimmedOrExpired > 0) {
684 _cacheMemoryStats.SetTrimStats(sw.Elapsed.Ticks, beginTotalCount, trimmedOrExpired);
688 Debug.Trace("CacheMemory", "**END** CacheManagerThread: " + HttpRuntime.AppDomainAppId
689 + ", percent=" + percent
690 + ", beginTotalCount=" + beginTotalCount
691 + ", trimmed=" + trimmedOrExpired
692 + ", Milliseconds=" + sw.ElapsedMilliseconds);
696 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
697 + " minPercent= " + minPercent
698 + ", percent= " + percent
699 + ", beginTotalCount=" + beginTotalCount
700 + ", trimmed=" + trimmedOrExpired
701 + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
703 return trimmedOrExpired;
706 Interlocked.Exchange(ref _inCacheManagerThread, 0);
711 abstract class CacheInternal : IEnumerable, IDisposable {
712 // cache key prefixes - they keep cache keys short and prevent conflicts
714 // NOTE: Since we already used up all the lowercase letters from 'a' to 'z',
715 // we are now using uppercase letters from 'A' to 'Z'
716 internal const string PrefixFIRST = "A";
717 internal const string PrefixResourceProvider = "A";
718 internal const string PrefixMapPathVPPFile = "Bf";
719 internal const string PrefixMapPathVPPDir = "Bd";
721 // Next prefix goes here, until we get to 'Z'
723 internal const string PrefixOutputCache = "a";
724 internal const string PrefixSqlCacheDependency = "b";
725 internal const string PrefixMemoryBuildResult = "c";
726 internal const string PrefixPathData = "d";
727 internal const string PrefixHttpCapabilities = "e";
728 internal const string PrefixMapPath = "f";
729 internal const string PrefixHttpSys = "g";
730 internal const string PrefixFileSecurity = "h";
731 internal const string PrefixInProcSessionState = "j";
732 internal const string PrefixStateApplication = "k";
733 internal const string PrefixPartialCachingControl = "l";
734 internal const string UNUSED = "m";
735 internal const string PrefixAdRotator = "n";
736 internal const string PrefixWebServiceDataSource = "o";
737 internal const string PrefixLoadXPath = "p";
738 internal const string PrefixLoadXml = "q";
739 internal const string PrefixLoadTransform = "r";
740 internal const string PrefixAspCompatThreading = "s";
741 internal const string PrefixDataSourceControl = "u";
742 internal const string PrefixValidationSentinel = "w";
743 internal const string PrefixWebEventResource = "x";
744 internal const string PrefixAssemblyPath = "y";
745 internal const string PrefixBrowserCapsHash = "z";
746 internal const string PrefixLAST = "z";
748 protected CacheCommon _cacheCommon;
749 private int _disposed;
751 // virtual methods requiring implementation
752 internal abstract int PublicCount {get;}
754 internal abstract long TotalCount {get;}
756 internal abstract IDictionaryEnumerator CreateEnumerator();
758 internal abstract CacheEntry UpdateCache(
762 CacheItemRemovedReason removedReason,
763 out object valueOld);
765 internal abstract long TrimIfNecessary(int percent);
767 internal abstract void EnableExpirationTimer(bool enable);
769 // If UseMemoryCache is true, we will direct all ASP.NET
770 // cache usage into System.Runtime.Caching.dll. This allows
771 // us to test System.Runtime.Caching.dll with all existing
772 // ASP.NET test cases (functional, perf, and stress).
774 private static bool _useMemoryCache;
775 private static volatile bool _useMemoryCacheInited;
776 internal static bool UseMemoryCache {
778 if (!_useMemoryCacheInited) {
779 RegistryKey regKey = null;
781 regKey = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\ASP.NET");
782 if (regKey != null) {
783 if ((int)regKey.GetValue("UseMemoryCache", 0)== 1) {
784 _useMemoryCache = true;
789 if (regKey != null) {
793 _useMemoryCacheInited = true;
795 return _useMemoryCache;
800 // common implementation
801 static internal CacheInternal Create() {
802 CacheCommon cacheCommon = new CacheCommon();
803 CacheInternal cacheInternal;
805 if (UseMemoryCache) {
806 cacheInternal = new MemCache(cacheCommon);
807 cacheCommon.AddSRefTarget(cacheInternal);
811 int numSubCaches = 0;
812 uint numCPUs = (uint) SystemInfo.GetNumProcessCPUs();
813 // the number of subcaches is the minimal power of 2 greater
814 // than or equal to the number of cpus
817 while (numCPUs > 0) {
821 if (numSubCaches == 1) {
822 cacheInternal = new CacheSingle(cacheCommon, null, 0);
825 cacheInternal = new CacheMultiple(cacheCommon, numSubCaches);
830 cacheCommon.SetCacheInternal(cacheInternal);
831 cacheCommon.ResetFromConfigSettings();
833 return cacheInternal;
836 protected CacheInternal(CacheCommon cacheCommon) {
837 _cacheCommon = cacheCommon;
840 protected virtual void Dispose(bool disposing) {
841 _cacheCommon.Dispose(disposing);
844 public void Dispose() {
847 // no destructor, don't need it.
848 // System.GC.SuppressFinalize(this);
851 internal bool IsDisposed { get { return _disposed == 1; } }
853 internal void ReadCacheInternalConfig(CacheSection cacheSection) {
854 _cacheCommon.ReadCacheInternalConfig(cacheSection);
857 internal long TrimCache(int percent) {
858 return _cacheCommon.CacheManagerThread(percent);
861 internal Cache CachePublic {
862 get {return _cacheCommon._cachePublic;}
865 internal long EffectivePrivateBytesLimit {
866 get { return _cacheCommon._cacheMemoryStats.CacheSizePressure.MemoryLimit; }
869 internal long EffectivePercentagePhysicalMemoryLimit {
870 get { return _cacheCommon._cacheMemoryStats.TotalMemoryPressure.MemoryLimit; }
873 IEnumerator IEnumerable.GetEnumerator() {
874 return CreateEnumerator();
877 public IDictionaryEnumerator GetEnumerator() {
878 return CreateEnumerator();
881 internal object this[string key] {
887 internal object Get(string key) {
888 return DoGet(false, key, CacheGetOptions.None);
891 internal object Get(string key, CacheGetOptions getOptions) {
892 return DoGet(false, key, getOptions);
895 internal object DoGet(bool isPublic, string key, CacheGetOptions getOptions) {
900 cacheKey = new CacheKey(key, isPublic);
901 entry = UpdateCache(cacheKey, null, false, CacheItemRemovedReason.Removed, out dummy);
903 if ((getOptions & CacheGetOptions.ReturnCacheEntry) != 0) {
915 internal void UtcInsert(string key, object value) {
920 Cache.NoAbsoluteExpiration,
921 Cache.NoSlidingExpiration,
922 CacheItemPriority.Default,
928 internal void UtcInsert(string key, object value, CacheDependency dependencies) {
933 Cache.NoAbsoluteExpiration,
934 Cache.NoSlidingExpiration,
935 CacheItemPriority.Default,
940 internal void UtcInsert(
943 CacheDependency dependencies,
944 DateTime utcAbsoluteExpiration,
945 TimeSpan slidingExpiration) {
951 utcAbsoluteExpiration,
953 CacheItemPriority.Default,
958 internal void UtcInsert(
961 CacheDependency dependencies,
962 DateTime utcAbsoluteExpiration,
963 TimeSpan slidingExpiration,
964 CacheItemPriority priority,
965 CacheItemRemovedCallback onRemoveCallback) {
971 utcAbsoluteExpiration,
978 internal object UtcAdd(
981 CacheDependency dependencies,
982 DateTime utcAbsoluteExpiration,
983 TimeSpan slidingExpiration,
984 CacheItemPriority priority,
985 CacheItemRemovedCallback onRemoveCallback) {
992 utcAbsoluteExpiration,
1000 internal object DoInsert(
1004 CacheDependency dependencies,
1005 DateTime utcAbsoluteExpiration,
1006 TimeSpan slidingExpiration,
1007 CacheItemPriority priority,
1008 CacheItemRemovedCallback onRemoveCallback,
1013 * If we throw an exception, prevent a leak by a user who
1014 * writes the following:
1016 * Cache.Insert(key, value, new CacheDependency(file));
1018 using (dependencies) {
1022 entry = new CacheEntry(
1027 utcAbsoluteExpiration,
1032 entry = UpdateCache(entry, entry, replace, CacheItemRemovedReason.Removed, out dummy);
1035 * N.B. A set can fail if two or more threads set the same key
1040 string yesno = (entry != null) ? "succeeded" : "failed";
1041 Debug.Trace("CacheAPIInsert", "Cache.Insert " + yesno + ": " + key);
1044 if (entry == null) {
1045 Debug.Trace("CacheAPIAdd", "Cache.Add added new item: " + key);
1048 Debug.Trace("CacheAPIAdd", "Cache.Add returned existing item: " + key);
1053 if (entry != null) {
1062 internal object Remove(string key) {
1063 CacheKey cacheKey = new CacheKey(key, false);
1064 return DoRemove(cacheKey, CacheItemRemovedReason.Removed);
1067 internal object Remove(CacheKey cacheKey, CacheItemRemovedReason reason) {
1068 return DoRemove(cacheKey, reason);
1072 * Remove an item from the cache, with a specific reason.
1073 * This is package access so only the cache can specify
1074 * a reason other than REMOVED.
1076 * @param key The key for the item.
1077 * @exception ArgumentException
1079 internal object DoRemove(CacheKey cacheKey, CacheItemRemovedReason reason) {
1082 UpdateCache(cacheKey, null, true, reason, out valueOld);
1085 if (valueOld != null) {
1086 Debug.Trace("CacheAPIRemove", "Cache.Remove succeeded, reason=" + reason + ": " + cacheKey);
1089 Debug.Trace("CacheAPIRemove", "Cache.Remove failed, reason=" + reason + ": " + cacheKey);
1097 sealed class CacheKeyComparer : IEqualityComparer {
1098 static CacheKeyComparer s_comparerInstance;
1100 static internal CacheKeyComparer GetInstance() {
1101 if (s_comparerInstance == null) {
1102 s_comparerInstance = new CacheKeyComparer();
1105 return s_comparerInstance;
1108 private CacheKeyComparer()
1112 bool IEqualityComparer.Equals(Object x, Object y)
1114 return Compare(x, y) == 0;
1117 // Compares two objects. An implementation of this method must return a
1118 // value less than zero if x is less than y, zero if x is equal to y, or a
1119 // value greater than zero if x is greater than y.
1120 private int Compare(Object x, Object y) {
1123 Debug.Assert(x != null && x is CacheKey);
1124 Debug.Assert(y != null && y is CacheKey);
1131 return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1139 return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1146 // Returns a hash code for the given object.
1148 int IEqualityComparer.GetHashCode(Object obj) {
1149 Debug.Assert(obj != null && obj is CacheKey);
1151 CacheKey cacheKey = (CacheKey) obj;
1153 return cacheKey.GetHashCode();
1160 sealed class CacheSingle : CacheInternal {
1162 static readonly TimeSpan INSERT_BLOCK_WAIT = new TimeSpan(0, 0, 10);
1163 const int MAX_COUNT = Int32.MaxValue / 2;
1164 const int MIN_COUNT = 10;
1167 Hashtable _entries; /* lookup table of entries */
1168 CacheExpires _expires; /* expires tables */
1169 CacheUsage _usage; /* usage tables */
1170 object _lock; /* read/write synchronization for _entries */
1171 int _disposed; /* disposed */
1172 int _totalCount; /* count of total entries */
1173 int _publicCount; /* count of public entries */
1174 ManualResetEvent _insertBlock; /* event to block inserts during high mem usage */
1175 bool _useInsertBlock; /* use insert block? */
1176 int _insertBlockCalls; /* number of callers using insert block */
1177 int _iSubCache; /* index of this cache */
1178 CacheMultiple _cacheMultiple; /* the CacheMultiple containing this cache */
1181 * Constructs a new Cache.
1183 internal CacheSingle(CacheCommon cacheCommon, CacheMultiple cacheMultiple, int iSubCache) : base(cacheCommon) {
1184 _cacheMultiple = cacheMultiple;
1185 _iSubCache = iSubCache;
1186 _entries = new Hashtable(CacheKeyComparer.GetInstance());
1187 _expires = new CacheExpires(this);
1188 _usage = new CacheUsage(this);
1189 _lock = new object();
1190 _insertBlock = new ManualResetEvent(true);
1191 cacheCommon.AddSRefTarget(this);
1195 * Dispose the cache.
1197 protected override void Dispose(bool disposing) {
1199 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
1200 if (_expires != null) {
1201 _expires.EnableExpirationTimer(false);
1205 CacheEntry[] entries = null;
1208 entries = new CacheEntry[_entries.Count];
1210 foreach (DictionaryEntry d in _entries) {
1211 entries[i++] = (CacheEntry) d.Value;
1215 foreach (CacheEntry entry in entries) {
1216 Remove(entry, CacheItemRemovedReason.Removed);
1219 // force any waiters to complete their waits. Note
1220 // that the insert block cannot be reacquired, as UseInsertBlock
1221 // checks the _disposed field.
1224 // release the block, causing it to be disposed when there
1225 // are no more callers.
1226 ReleaseInsertBlock();
1228 Debug.Trace("CacheDispose", "Cache disposed");
1232 base.Dispose(disposing);
1235 // Get the insert block manual reset event if it has not been disposed.
1236 ManualResetEvent UseInsertBlock() {
1241 int n = _insertBlockCalls;
1246 if (Interlocked.CompareExchange(ref _insertBlockCalls, n + 1, n) == n) {
1247 return _insertBlock;
1252 // Release the insert block event, and dispose it if it has been released
1253 // more times than it has been used
1254 void ReleaseInsertBlock() {
1255 if (Interlocked.Decrement(ref _insertBlockCalls) < 0) {
1256 ManualResetEvent e = _insertBlock;
1257 _insertBlock = null;
1264 // Set the insert block event.
1265 void SetInsertBlock() {
1266 ManualResetEvent e = null;
1268 e = UseInsertBlock();
1275 ReleaseInsertBlock();
1280 // Reset the insert block event.
1281 void ResetInsertBlock() {
1282 ManualResetEvent e = null;
1284 e = UseInsertBlock();
1291 ReleaseInsertBlock();
1296 // Wait on the insert block event.
1297 bool WaitInsertBlock() {
1298 bool signaled = false;
1299 ManualResetEvent e = null;
1301 e = UseInsertBlock();
1303 Debug.Trace("CacheMemoryTrimInsertBlock", "WaitInsertBlock: Cache " + _iSubCache + ": _useInsertBlock=true");
1304 signaled = e.WaitOne(INSERT_BLOCK_WAIT, false);
1305 Debug.Trace("CacheMemoryTrimInsertBlock", "Done waiting");
1310 ReleaseInsertBlock();
1317 internal void BlockInsertIfNeeded() {
1318 if (_cacheCommon._cacheMemoryStats.IsAboveHighPressure()) {
1319 Debug.Trace("CacheMemoryTrimInsertBlock", "BlockInsertIfNeeded: Cache " + _iSubCache + ": _useInsertBlock=true");
1320 _useInsertBlock = true;
1325 internal void UnblockInsert() {
1326 if (_useInsertBlock) {
1327 _useInsertBlock = false;
1329 Debug.Trace("CacheMemoryTrimInsertBlock", "UnblockInsert: Cache " + _iSubCache + ": _useInsertBlock=false");
1334 internal override int PublicCount {
1335 get {return _publicCount;}
1338 internal override long TotalCount {
1339 get {return _totalCount;}
1342 internal override IDictionaryEnumerator CreateEnumerator() {
1343 Hashtable h = new Hashtable(_publicCount);
1344 DateTime utcNow = DateTime.UtcNow;
1347 foreach (DictionaryEntry d in _entries) {
1348 CacheEntry entry = (CacheEntry) d.Value;
1350 // note that ASP.NET does not use this enumerator internally,
1351 // so we just choose public items.
1352 if (entry.IsPublic &&
1353 entry.State == CacheEntry.EntryState.AddedToCache &&
1354 ((!_cacheCommon._enableExpiration) || (utcNow <= entry.UtcExpires))) {
1355 h[entry.Key] = entry.Value;
1360 return h.GetEnumerator();
1364 * Performs all operations on the cache, with the
1365 * exception of Clear. The arguments indicate the type of operation:
1367 * @param key The key of the object.
1368 * @param newItem The new entry to be added to the cache.
1369 * @param replace Whether or not newEntry should replace an existing object in the cache.
1370 * @return The item requested. May be null.
1372 internal override CacheEntry UpdateCache(
1374 CacheEntry newEntry,
1376 CacheItemRemovedReason removedReason,
1377 out object valueOld)
1379 CacheEntry entry = null;
1380 CacheEntry oldEntry = null;
1381 bool expired = false;
1383 CacheDependency newEntryDependency = null;
1385 bool removeExpired = false;
1386 bool updateExpires = false;
1387 DateTime utcNewExpires = DateTime.MinValue;
1388 CacheEntry.EntryState entryState = CacheEntry.EntryState.NotInCache;
1389 bool newEntryNeedsClose = false;
1390 CacheItemRemovedReason newEntryRemovedReason = CacheItemRemovedReason.Removed;
1393 isGet = !replace && newEntry == null;
1394 isAdd = !replace && newEntry != null;
1397 * Perform update of cache data structures in a series to
1398 * avoid overlapping locks.
1400 * First, update the hashtable. The hashtable is the place
1401 * that guarantees what is in or out of the cache.
1403 * Loop here to remove expired items in a Get or Add, where
1404 * we can't otherwise delete an item.
1407 if (removeExpired) {
1408 Debug.Trace("CacheUpdate", "Removing expired item found in Get: " + cacheKey);
1409 UpdateCache(cacheKey, null, true, CacheItemRemovedReason.Expired, out valueOld);
1410 removeExpired = false;
1414 utcNow = DateTime.UtcNow;
1416 if (_useInsertBlock && newEntry != null && newEntry.HasUsage() /* HasUsage() means it's not NonRemovable */) {
1417 bool insertBlockReleased = WaitInsertBlock();
1420 if (!insertBlockReleased) {
1421 Debug.Trace("CacheUpdateWaitFailed", "WaitInsertBlock failed.");
1426 // the _entries hashtable supports multiple readers or one writer
1427 bool isLockEntered = false;
1429 Monitor.Enter(_lock, ref isLockEntered);
1432 entry = (CacheEntry) _entries[cacheKey];
1433 Debug.Trace("CacheUpdate", "Entry " + ((entry != null) ? "found" : "not found") + "in hashtable: " + cacheKey);
1435 if (entry != null) {
1436 entryState = entry.State;
1438 // If isGet == true, we are not hold any lock and so entryState can be anything
1441 entryState == CacheEntry.EntryState.AddingToCache ||
1442 entryState == CacheEntry.EntryState.AddedToCache,
1443 "entryState == CacheEntry.EntryState.AddingToCache || entryState == CacheEntry.EntryState.AddedToCache");
1445 expired = (_cacheCommon._enableExpiration) && (entry.UtcExpires < utcNow);
1449 * If the expired item is Added to the cache, remove it now before
1450 * its expiration timer fires up to a minute in the future.
1451 * Otherwise, just return null to indicate the item is not available.
1453 if (entryState == CacheEntry.EntryState.AddedToCache) {
1454 removeExpired = true;
1462 * If it's a call to Add, replace the item
1463 * when it has expired.
1468 * Change the removed reason.
1470 removedReason = CacheItemRemovedReason.Expired;
1474 updateExpires = (_cacheCommon._enableExpiration) && (entry.SlidingExpiration > TimeSpan.Zero);
1479 * Avoid running unnecessary code in a Get request by this simple test:
1483 * Remove an item from the hashtable.
1485 if (replace && entry != null) {
1486 bool doRemove = (entryState != CacheEntry.EntryState.AddingToCache);
1490 oldEntry.State = CacheEntry.EntryState.RemovingFromCache;
1492 _entries.Remove(oldEntry);
1493 Debug.Trace("CacheUpdate", "Entry removed from hashtable: " + cacheKey);
1497 * If we're removing and couldn't remove the old item
1498 * because its state was AddingToCache, return null
1499 * to indicate failure.
1501 if (newEntry == null) {
1502 Debug.Trace("CacheUpdate", "Removal from hashtable failed: " + cacheKey);
1509 * Add an item to the hashtable.
1511 if (newEntry != null) {
1514 if (entry != null) {
1515 if (oldEntry == null) {
1517 * We could not remove the existing entry,
1518 * either because it simply exists and replace == false,
1519 * or replace == true and it's state was AddingToCache when
1520 * we tried to remove it.
1523 newEntryRemovedReason = CacheItemRemovedReason.Removed;
1528 Debug.Trace("CacheUpdate", "Insertion into hashtable failed because old entry was not removed: " + cacheKey);
1535 /* non-definitive check */
1536 newEntryDependency = newEntry.Dependency;
1537 if (newEntryDependency != null) {
1538 if (newEntryDependency.HasChanged) {
1540 newEntryRemovedReason = CacheItemRemovedReason.DependencyChanged;
1545 Debug.Trace("CacheUpdate", "Insertion into hashtable failed because dependency changed: " + cacheKey);
1552 newEntry.State = CacheEntry.EntryState.AddingToCache;
1553 _entries.Add(newEntry, newEntry);
1556 * If this is an Add operation, indicate success
1557 * by returning null.
1560 Debug.Assert(entry == null || expired, "entry == null || expired");
1565 * Indicate success by returning the inserted entry.
1570 Debug.Trace("CacheUpdate", "Entry added to hashtable: " + cacheKey);
1575 * If we failed for an Insert, indicate failure by returning null.
1578 newEntryNeedsClose = true;
1582 * If we failed for an Add (e.g. Dependency has changed),
1583 * return the existing value. If existing value is null,
1584 * we have to close the newEntry ourselves. Otherwise, we'll
1585 * return non-null and the caller should close the item.
1587 newEntryNeedsClose = (entry == null);
1591 * If newEntry cannot be inserted, and it does not need to be
1592 * closed, set it to null so that we don't insert it later.
1593 * Leave it non-null when it needs to be closed that that we
1596 if (!newEntryNeedsClose) {
1607 if (isLockEntered) {
1608 Monitor.Exit(_lock);
1614 * Since we want Get to be fast, check here for a get without
1615 * alteration to cache.
1618 if (entry != null) {
1619 if (updateExpires) {
1620 utcNewExpires = utcNow + entry.SlidingExpiration;
1621 if (utcNewExpires - entry.UtcExpires >= CacheExpires.MIN_UPDATE_DELTA || utcNewExpires < entry.UtcExpires) {
1622 _expires.UtcUpdate(entry, utcNewExpires);
1626 UtcUpdateUsageRecursive(entry, utcNow);
1629 if (cacheKey.IsPublic) {
1630 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_RATIO_BASE);
1631 if (entry != null) {
1632 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_HITS);
1635 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_MISSES);
1639 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_RATIO_BASE);
1640 if (entry != null) {
1641 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_HITS);
1644 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_MISSES);
1648 if (entry != null) {
1649 Debug.Trace("CacheUpdate", "Cache hit: " + cacheKey);
1652 Debug.Trace("CacheUpdate", "Cache miss: " + cacheKey);
1659 int publicDelta = 0;
1660 int totalTurnover = 0;
1661 int publicTurnover = 0;
1663 if (oldEntry != null) {
1664 if (oldEntry.InExpires()) {
1665 _expires.Remove(oldEntry);
1668 if (oldEntry.InUsage()) {
1669 _usage.Remove(oldEntry);
1672 Debug.Assert(oldEntry.State == CacheEntry.EntryState.RemovingFromCache, "oldEntry.State == CacheEntry.EntryState.RemovingFromCache");
1673 oldEntry.State = CacheEntry.EntryState.RemovedFromCache;
1674 valueOld = oldEntry.Value;
1678 if (oldEntry.IsPublic) {
1684 Debug.Trace("CacheUpdate", "Entry removed from cache, reason=" + removedReason + ": " + (CacheKey) oldEntry);
1688 if (newEntry != null) {
1689 if (newEntryNeedsClose) {
1690 // Call close if newEntry could not be added.
1691 newEntry.State = CacheEntry.EntryState.RemovedFromCache;
1692 newEntry.Close(newEntryRemovedReason);
1696 Debug.Assert(!newEntry.InExpires());
1697 Debug.Assert(!newEntry.InUsage());
1699 if (_cacheCommon._enableExpiration && newEntry.HasExpiration()) {
1700 _expires.Add(newEntry);
1703 if ( _cacheCommon._enableMemoryCollection && newEntry.HasUsage() &&
1704 ( // Don't bother to set usage if it's going to expire very soon
1705 !newEntry.HasExpiration() ||
1706 newEntry.SlidingExpiration > TimeSpan.Zero ||
1707 newEntry.UtcExpires - utcNow >= CacheUsage.MIN_LIFETIME_FOR_USAGE)) {
1709 _usage.Add(newEntry);
1712 newEntry.State = CacheEntry.EntryState.AddedToCache;
1714 Debug.Trace("CacheUpdate", "Entry added to cache: " + (CacheKey)newEntry);
1718 if (newEntry.IsPublic) {
1725 // Call close after the newEntry has been fully added to the cache,
1726 // so the OnRemoveCallback can take a dependency on the newly inserted item.
1727 if (oldEntry != null) {
1728 oldEntry.Close(removedReason);
1731 // Delay monitoring change events until the oldEntry has been completely removed
1732 // from the cache, and its OnRemoveCallback called. This way we won't call the
1733 // OnRemoveCallback for newEntry before doing so for oldEntry.
1734 if (newEntry != null) {
1735 // listen to change events
1736 newEntry.MonitorDependencyChanges();
1739 * NB: We have to check for dependency changes after we add the item
1740 * to cache, because otherwise we may not remove it if it changes
1741 * between the time we check for a dependency change and the time
1742 * we set the AddedToCache bit. The worst that will happen is that
1743 * a get can occur on an item that has changed, but that can happen
1744 * anyway. The important thing is that we always remove an item that
1747 if (newEntryDependency != null && newEntryDependency.HasChanged) {
1748 Remove(newEntry, CacheItemRemovedReason.DependencyChanged);
1752 // update counts and counters
1753 if (totalDelta == 1) {
1754 Interlocked.Increment(ref _totalCount);
1755 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1757 else if (totalDelta == -1) {
1758 Interlocked.Decrement(ref _totalCount);
1759 PerfCounters.DecrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1762 if (publicDelta == 1) {
1763 Interlocked.Increment(ref _publicCount);
1764 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1766 else if (publicDelta == -1) {
1767 Interlocked.Decrement(ref _publicCount);
1768 PerfCounters.DecrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1771 if (totalTurnover > 0) {
1772 PerfCounters.IncrementCounterEx(AppPerfCounter.TOTAL_CACHE_TURNOVER_RATE, totalTurnover);
1775 if (publicTurnover > 0) {
1776 PerfCounters.IncrementCounterEx(AppPerfCounter.API_CACHE_TURNOVER_RATE, publicTurnover);
1783 void UtcUpdateUsageRecursive(CacheEntry entry, DateTime utcNow) {
1784 CacheDependency dependency;
1785 CacheEntry[] entries;
1787 // Don't update if the last update is less than 1 sec away. This way we'll
1788 // avoid over updating the usage in the scenario where a cache makes several
1790 if (utcNow - entry.UtcLastUsageUpdate > CacheUsage.CORRELATED_REQUEST_TIMEOUT || utcNow < entry.UtcLastUsageUpdate) {
1791 entry.UtcLastUsageUpdate = utcNow;
1792 if (entry.InUsage()) {
1793 CacheSingle cacheSingle;
1794 if (_cacheMultiple == null) {
1798 cacheSingle = _cacheMultiple.GetCacheSingle(entry.Key.GetHashCode());
1801 cacheSingle._usage.Update(entry);
1804 dependency = entry.Dependency;
1805 if (dependency != null) {
1806 entries = dependency.CacheEntries;
1807 if (entries != null) {
1808 foreach (CacheEntry dependent in entries) {
1809 UtcUpdateUsageRecursive(dependent, utcNow);
1816 internal override long TrimIfNecessary(int percent) {
1817 Debug.Assert(_cacheCommon._inCacheManagerThread == 1, "Trim should only occur when we're updating memory statistics.");
1818 if (!_cacheCommon._enableMemoryCollection)
1822 // do we need to drop a percentage of entries?
1824 toTrim = (int)(((long)_totalCount * (long)percent) / 100L);
1826 // would this leave us above MAX_COUNT?
1827 int minTrim = _totalCount - MAX_COUNT;
1828 if (toTrim < minTrim) {
1831 // would this put us below MIN_COUNT?
1832 int maxTrim = _totalCount - MIN_COUNT;
1833 if (toTrim > maxTrim) {
1836 // do we need to trim?
1837 if (toTrim <= 0 || HostingEnvironment.ShutdownInitiated) {
1841 int ocEntriesTrimmed = 0; // number of output cache entries trimmed
1842 int publicEntriesTrimmed = 0; // number of public entries trimmed
1843 int totalTrimmed = 0; // total number of entries trimmed
1844 int trimmedOrExpired = 0;
1845 int beginTotalCount = _totalCount;
1848 trimmedOrExpired = _expires.FlushExpiredItems(true);
1849 if (trimmedOrExpired < toTrim) {
1850 totalTrimmed = _usage.FlushUnderUsedItems(toTrim - trimmedOrExpired, ref publicEntriesTrimmed, ref ocEntriesTrimmed);
1851 trimmedOrExpired += totalTrimmed;
1854 if (totalTrimmed > 0) {
1855 // Update values for perfcounters
1856 PerfCounters.IncrementCounterEx(AppPerfCounter.CACHE_TOTAL_TRIMS, totalTrimmed);
1857 PerfCounters.IncrementCounterEx(AppPerfCounter.CACHE_API_TRIMS, publicEntriesTrimmed);
1858 PerfCounters.IncrementCounterEx(AppPerfCounter.CACHE_OUTPUT_TRIMS, ocEntriesTrimmed);
1865 Debug.Trace("CacheMemory", "TrimIfNecessary: _iSubCache= " + _iSubCache
1866 + ", beginTotalCount=" + beginTotalCount
1867 + ", endTotalCount=" + _totalCount
1868 + ", percent=" + percent
1869 + ", trimmed=" + totalTrimmed);
1871 return trimmedOrExpired;
1874 internal override void EnableExpirationTimer(bool enable) {
1875 if (_expires != null) {
1876 _expires.EnableExpirationTimer(enable);
1881 class CacheMultiple : CacheInternal {
1883 CacheSingle[] _caches;
1884 int _cacheIndexMask;
1886 internal CacheMultiple(CacheCommon cacheCommon, int numSingleCaches) : base(cacheCommon) {
1887 Debug.Assert(numSingleCaches > 1, "numSingleCaches is not greater than 1");
1888 Debug.Assert((numSingleCaches & (numSingleCaches - 1)) == 0, "numSingleCaches is not a power of 2");
1889 _cacheIndexMask = numSingleCaches - 1;
1890 _caches = new CacheSingle[numSingleCaches];
1891 for (int i = 0; i < numSingleCaches; i++) {
1892 _caches[i] = new CacheSingle(cacheCommon, this, i);
1896 protected override void Dispose(bool disposing) {
1898 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
1899 foreach (CacheSingle cacheSingle in _caches) {
1900 cacheSingle.Dispose();
1905 base.Dispose(disposing);
1908 internal override int PublicCount {
1911 foreach (CacheSingle cacheSingle in _caches) {
1912 count += cacheSingle.PublicCount;
1919 internal override long TotalCount {
1922 foreach (CacheSingle cacheSingle in _caches) {
1923 count += cacheSingle.TotalCount;
1930 internal override IDictionaryEnumerator CreateEnumerator() {
1931 IDictionaryEnumerator[] enumerators = new IDictionaryEnumerator[_caches.Length];
1932 for (int i = 0, c = _caches.Length; i < c; i++) {
1933 enumerators[i] = _caches[i].CreateEnumerator();
1936 return new AggregateEnumerator(enumerators);
1939 internal CacheSingle GetCacheSingle(int hashCode) {
1940 Debug.Assert(_caches != null && _caches.Length != 0);
1941 // Dev10 865907: Math.Abs throws OverflowException for Int32.MinValue
1943 hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode;
1945 int index = (hashCode & _cacheIndexMask);
1946 return _caches[index];
1949 internal override CacheEntry UpdateCache(
1951 CacheEntry newEntry,
1953 CacheItemRemovedReason removedReason,
1954 out object valueOld) {
1956 int hashCode = cacheKey.Key.GetHashCode();
1957 CacheSingle cacheSingle = GetCacheSingle(hashCode);
1958 return cacheSingle.UpdateCache(cacheKey, newEntry, replace, removedReason, out valueOld);
1961 internal override long TrimIfNecessary(int percent) {
1963 foreach (CacheSingle cacheSingle in _caches) {
1964 count += cacheSingle.TrimIfNecessary(percent);
1969 internal override void EnableExpirationTimer(bool enable) {
1970 foreach (CacheSingle cacheSingle in _caches) {
1971 cacheSingle.EnableExpirationTimer(enable);
1976 class AggregateEnumerator : IDictionaryEnumerator {
1977 IDictionaryEnumerator [] _enumerators;
1980 internal AggregateEnumerator(IDictionaryEnumerator [] enumerators) {
1981 _enumerators = enumerators;
1984 public bool MoveNext() {
1988 more = _enumerators[_iCurrent].MoveNext();
1992 if (_iCurrent == _enumerators.Length - 1)
2001 public void Reset() {
2002 for (int i = 0; i <= _iCurrent; i++) {
2003 _enumerators[i].Reset();
2009 public Object Current {
2011 return _enumerators[_iCurrent].Current;
2017 return _enumerators[_iCurrent].Key;
2021 public Object Value {
2023 return _enumerators[_iCurrent].Value;
2027 public DictionaryEntry Entry {
2029 return _enumerators[_iCurrent].Entry;