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 DisposableGCHandleRef<Timer> _timerHandleRef;
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;
529 private int _disposed = 0;
531 internal CacheCommon() {
532 _cachePublic = new Cache(0);
533 _srefMultiple = new SRefMultiple();
534 _cacheMemoryStats = new CacheMemoryStats(_srefMultiple);
535 _enableMemoryCollection = true;
536 _enableExpiration = true;
539 internal void Dispose(bool disposing) {
541 // This method must be tolerant to multiple calls to Dispose on the same instance
542 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
543 EnableCacheMemoryTimer(false);
544 _cacheMemoryStats.Dispose();
549 internal void AddSRefTarget(object o) {
550 _srefMultiple.AddSRefTarget(o);
553 internal void SetCacheInternal(CacheInternal cacheInternal) {
554 _cacheInternal = cacheInternal;
555 _cachePublic.SetCacheInternal(cacheInternal);
558 internal void ReadCacheInternalConfig(CacheSection cacheSection) {
559 if (_internalConfigRead) {
564 if (_internalConfigRead) {
568 // Set it to true here so that even if we have to call ReadCacheInternalConfig
569 // from the code below, we won't get into an infinite loop.
570 _internalConfigRead = true;
572 if (cacheSection != null) {
573 _enableMemoryCollection = (!cacheSection.DisableMemoryCollection);
574 _enableExpiration = (!cacheSection.DisableExpiration);
575 _cacheMemoryStats.ReadConfig(cacheSection);
576 _currentPollInterval = CacheMemorySizePressure.PollInterval;
577 ResetFromConfigSettings();
582 internal void ResetFromConfigSettings() {
583 EnableCacheMemoryTimer(_enableMemoryCollection);
584 _cacheInternal.EnableExpirationTimer(_enableExpiration);
587 internal void EnableCacheMemoryTimer(bool enable) {
590 if (Debug.IsTagPresent("Timer") && !Debug.IsTagEnabled("Timer")) {
598 if (_timerHandleRef == null) {
599 // <cache privateBytesPollTime> has not been read yet
600 Timer timer = new Timer(new TimerCallback(this.CacheManagerTimerCallback), null, _currentPollInterval, _currentPollInterval);
601 _timerHandleRef = new DisposableGCHandleRef<Timer>(timer);
602 Debug.Trace("Cache", "Started CacheMemoryTimers");
605 _timerHandleRef.Target.Change(_currentPollInterval, _currentPollInterval);
609 var timerHandleRef = _timerHandleRef;
610 if (timerHandleRef != null && Interlocked.CompareExchange(ref _timerHandleRef, null, timerHandleRef) == timerHandleRef) {
611 timerHandleRef.Dispose();
612 Debug.Trace("Cache", "Stopped CacheMemoryTimers");
618 // wait for CacheManagerTimerCallback to finish
619 while(_inCacheManagerThread != 0) {
628 if (_timerHandleRef == null)
631 // the order of these if statements is important
633 // When above the high pressure mark, interval should be 5 seconds or less
634 if (_cacheMemoryStats.IsAboveHighPressure()) {
635 if (_currentPollInterval > MEMORYSTATUS_INTERVAL_5_SECONDS) {
636 _currentPollInterval = MEMORYSTATUS_INTERVAL_5_SECONDS;
637 _timerHandleRef.Target.Change(_currentPollInterval, _currentPollInterval);
642 // When above half the low pressure mark, interval should be 30 seconds or less
643 if ((_cacheMemoryStats.CacheSizePressure.PressureLast > _cacheMemoryStats.CacheSizePressure.PressureLow/2)
644 || (_cacheMemoryStats.TotalMemoryPressure.PressureLast > _cacheMemoryStats.TotalMemoryPressure.PressureLow/2)) {
645 // DevDivBugs 104034: allow interval to fall back down when memory pressure goes away
646 int newPollInterval = Math.Min(CacheMemorySizePressure.PollInterval, MEMORYSTATUS_INTERVAL_30_SECONDS);
647 if (_currentPollInterval != newPollInterval) {
648 _currentPollInterval = newPollInterval;
649 _timerHandleRef.Target.Change(_currentPollInterval, _currentPollInterval);
654 // there is no pressure, interval should be the value from config
655 if (_currentPollInterval != CacheMemorySizePressure.PollInterval) {
656 _currentPollInterval = CacheMemorySizePressure.PollInterval;
657 _timerHandleRef.Target.Change(_currentPollInterval, _currentPollInterval);
662 void CacheManagerTimerCallback(object state) {
663 CacheManagerThread(0);
666 internal long CacheManagerThread(int minPercent) {
667 if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
670 Debug.Trace("CacheMemory", "**BEG** CacheManagerThread " + HttpRuntime.AppDomainAppId + ", " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
673 // Dev10 633335: if the timer has been disposed, return without doing anything
674 if (_timerHandleRef == null)
677 // The timer thread must always call Update so that the CacheManager
678 // knows the size of the cache.
679 _cacheMemoryStats.Update();
681 int percent = Math.Max(minPercent, _cacheMemoryStats.GetPercentToTrim());
682 long beginTotalCount = _cacheInternal.TotalCount;
683 Stopwatch sw = Stopwatch.StartNew();
684 long trimmedOrExpired = _cacheInternal.TrimIfNecessary(percent);
686 // 1) don't update stats if the trim happend because MAX_COUNT was exceeded
687 // 2) don't update stats unless we removed at least one entry
688 if (percent > 0 && trimmedOrExpired > 0) {
689 _cacheMemoryStats.SetTrimStats(sw.Elapsed.Ticks, beginTotalCount, trimmedOrExpired);
693 Debug.Trace("CacheMemory", "**END** CacheManagerThread: " + HttpRuntime.AppDomainAppId
694 + ", percent=" + percent
695 + ", beginTotalCount=" + beginTotalCount
696 + ", trimmed=" + trimmedOrExpired
697 + ", Milliseconds=" + sw.ElapsedMilliseconds);
701 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
702 + " minPercent= " + minPercent
703 + ", percent= " + percent
704 + ", beginTotalCount=" + beginTotalCount
705 + ", trimmed=" + trimmedOrExpired
706 + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
708 return trimmedOrExpired;
711 Interlocked.Exchange(ref _inCacheManagerThread, 0);
716 abstract class CacheInternal : IEnumerable, IDisposable {
717 // cache key prefixes - they keep cache keys short and prevent conflicts
719 // NOTE: Since we already used up all the lowercase letters from 'a' to 'z',
720 // we are now using uppercase letters from 'A' to 'Z'
721 internal const string PrefixFIRST = "A";
722 internal const string PrefixResourceProvider = "A";
723 internal const string PrefixMapPathVPPFile = "Bf";
724 internal const string PrefixMapPathVPPDir = "Bd";
726 // Next prefix goes here, until we get to 'Z'
728 internal const string PrefixOutputCache = "a";
729 internal const string PrefixSqlCacheDependency = "b";
730 internal const string PrefixMemoryBuildResult = "c";
731 internal const string PrefixPathData = "d";
732 internal const string PrefixHttpCapabilities = "e";
733 internal const string PrefixMapPath = "f";
734 internal const string PrefixHttpSys = "g";
735 internal const string PrefixFileSecurity = "h";
736 internal const string PrefixInProcSessionState = "j";
737 internal const string PrefixStateApplication = "k";
738 internal const string PrefixPartialCachingControl = "l";
739 internal const string UNUSED = "m";
740 internal const string PrefixAdRotator = "n";
741 internal const string PrefixWebServiceDataSource = "o";
742 internal const string PrefixLoadXPath = "p";
743 internal const string PrefixLoadXml = "q";
744 internal const string PrefixLoadTransform = "r";
745 internal const string PrefixAspCompatThreading = "s";
746 internal const string PrefixDataSourceControl = "u";
747 internal const string PrefixValidationSentinel = "w";
748 internal const string PrefixWebEventResource = "x";
749 internal const string PrefixAssemblyPath = "y";
750 internal const string PrefixBrowserCapsHash = "z";
751 internal const string PrefixLAST = "z";
753 protected CacheCommon _cacheCommon;
754 private int _disposed;
756 // virtual methods requiring implementation
757 internal abstract int PublicCount {get;}
759 internal abstract long TotalCount {get;}
761 internal abstract IDictionaryEnumerator CreateEnumerator();
763 internal abstract CacheEntry UpdateCache(
767 CacheItemRemovedReason removedReason,
768 out object valueOld);
770 internal abstract long TrimIfNecessary(int percent);
772 internal abstract void EnableExpirationTimer(bool enable);
774 // If UseMemoryCache is true, we will direct all ASP.NET
775 // cache usage into System.Runtime.Caching.dll. This allows
776 // us to test System.Runtime.Caching.dll with all existing
777 // ASP.NET test cases (functional, perf, and stress).
779 private static bool _useMemoryCache;
780 private static volatile bool _useMemoryCacheInited;
781 internal static bool UseMemoryCache {
783 if (!_useMemoryCacheInited) {
784 RegistryKey regKey = null;
786 regKey = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\ASP.NET");
787 if (regKey != null) {
788 if ((int)regKey.GetValue("UseMemoryCache", 0)== 1) {
789 _useMemoryCache = true;
794 if (regKey != null) {
798 _useMemoryCacheInited = true;
800 return _useMemoryCache;
805 // common implementation
806 static internal CacheInternal Create() {
807 CacheCommon cacheCommon = new CacheCommon();
808 CacheInternal cacheInternal;
810 if (UseMemoryCache) {
811 cacheInternal = new MemCache(cacheCommon);
812 cacheCommon.AddSRefTarget(cacheInternal);
816 int numSubCaches = 0;
817 uint numCPUs = (uint) SystemInfo.GetNumProcessCPUs();
818 // the number of subcaches is the minimal power of 2 greater
819 // than or equal to the number of cpus
822 while (numCPUs > 0) {
826 if (numSubCaches == 1) {
827 cacheInternal = new CacheSingle(cacheCommon, null, 0);
830 cacheInternal = new CacheMultiple(cacheCommon, numSubCaches);
835 cacheCommon.SetCacheInternal(cacheInternal);
836 cacheCommon.ResetFromConfigSettings();
838 return cacheInternal;
841 protected CacheInternal(CacheCommon cacheCommon) {
842 _cacheCommon = cacheCommon;
845 protected virtual void Dispose(bool disposing) {
846 _cacheCommon.Dispose(disposing);
849 public void Dispose() {
852 // no destructor, don't need it.
853 // System.GC.SuppressFinalize(this);
856 internal bool IsDisposed { get { return _disposed == 1; } }
858 internal void ReadCacheInternalConfig(CacheSection cacheSection) {
859 _cacheCommon.ReadCacheInternalConfig(cacheSection);
862 internal long TrimCache(int percent) {
863 return _cacheCommon.CacheManagerThread(percent);
866 internal Cache CachePublic {
867 get {return _cacheCommon._cachePublic;}
870 internal long EffectivePrivateBytesLimit {
871 get { return _cacheCommon._cacheMemoryStats.CacheSizePressure.MemoryLimit; }
874 internal long EffectivePercentagePhysicalMemoryLimit {
875 get { return _cacheCommon._cacheMemoryStats.TotalMemoryPressure.MemoryLimit; }
878 IEnumerator IEnumerable.GetEnumerator() {
879 return CreateEnumerator();
882 public IDictionaryEnumerator GetEnumerator() {
883 return CreateEnumerator();
886 internal object this[string key] {
892 internal object Get(string key) {
893 return DoGet(false, key, CacheGetOptions.None);
896 internal object Get(string key, CacheGetOptions getOptions) {
897 return DoGet(false, key, getOptions);
900 internal object DoGet(bool isPublic, string key, CacheGetOptions getOptions) {
905 cacheKey = new CacheKey(key, isPublic);
906 entry = UpdateCache(cacheKey, null, false, CacheItemRemovedReason.Removed, out dummy);
908 if ((getOptions & CacheGetOptions.ReturnCacheEntry) != 0) {
920 internal void UtcInsert(string key, object value) {
925 Cache.NoAbsoluteExpiration,
926 Cache.NoSlidingExpiration,
927 CacheItemPriority.Default,
933 internal void UtcInsert(string key, object value, CacheDependency dependencies) {
938 Cache.NoAbsoluteExpiration,
939 Cache.NoSlidingExpiration,
940 CacheItemPriority.Default,
945 internal void UtcInsert(
948 CacheDependency dependencies,
949 DateTime utcAbsoluteExpiration,
950 TimeSpan slidingExpiration) {
956 utcAbsoluteExpiration,
958 CacheItemPriority.Default,
963 internal void UtcInsert(
966 CacheDependency dependencies,
967 DateTime utcAbsoluteExpiration,
968 TimeSpan slidingExpiration,
969 CacheItemPriority priority,
970 CacheItemRemovedCallback onRemoveCallback) {
976 utcAbsoluteExpiration,
983 internal object UtcAdd(
986 CacheDependency dependencies,
987 DateTime utcAbsoluteExpiration,
988 TimeSpan slidingExpiration,
989 CacheItemPriority priority,
990 CacheItemRemovedCallback onRemoveCallback) {
997 utcAbsoluteExpiration,
1005 internal object DoInsert(
1009 CacheDependency dependencies,
1010 DateTime utcAbsoluteExpiration,
1011 TimeSpan slidingExpiration,
1012 CacheItemPriority priority,
1013 CacheItemRemovedCallback onRemoveCallback,
1018 * If we throw an exception, prevent a leak by a user who
1019 * writes the following:
1021 * Cache.Insert(key, value, new CacheDependency(file));
1023 using (dependencies) {
1027 entry = new CacheEntry(
1032 utcAbsoluteExpiration,
1037 entry = UpdateCache(entry, entry, replace, CacheItemRemovedReason.Removed, out dummy);
1040 * N.B. A set can fail if two or more threads set the same key
1045 string yesno = (entry != null) ? "succeeded" : "failed";
1046 Debug.Trace("CacheAPIInsert", "Cache.Insert " + yesno + ": " + key);
1049 if (entry == null) {
1050 Debug.Trace("CacheAPIAdd", "Cache.Add added new item: " + key);
1053 Debug.Trace("CacheAPIAdd", "Cache.Add returned existing item: " + key);
1058 if (entry != null) {
1067 internal object Remove(string key) {
1068 CacheKey cacheKey = new CacheKey(key, false);
1069 return DoRemove(cacheKey, CacheItemRemovedReason.Removed);
1072 internal object Remove(CacheKey cacheKey, CacheItemRemovedReason reason) {
1073 return DoRemove(cacheKey, reason);
1077 * Remove an item from the cache, with a specific reason.
1078 * This is package access so only the cache can specify
1079 * a reason other than REMOVED.
1081 * @param key The key for the item.
1082 * @exception ArgumentException
1084 internal object DoRemove(CacheKey cacheKey, CacheItemRemovedReason reason) {
1087 UpdateCache(cacheKey, null, true, reason, out valueOld);
1090 if (valueOld != null) {
1091 Debug.Trace("CacheAPIRemove", "Cache.Remove succeeded, reason=" + reason + ": " + cacheKey);
1094 Debug.Trace("CacheAPIRemove", "Cache.Remove failed, reason=" + reason + ": " + cacheKey);
1102 sealed class CacheKeyComparer : IEqualityComparer {
1103 static CacheKeyComparer s_comparerInstance;
1105 static internal CacheKeyComparer GetInstance() {
1106 if (s_comparerInstance == null) {
1107 s_comparerInstance = new CacheKeyComparer();
1110 return s_comparerInstance;
1113 private CacheKeyComparer()
1117 bool IEqualityComparer.Equals(Object x, Object y)
1119 return Compare(x, y) == 0;
1122 // Compares two objects. An implementation of this method must return a
1123 // value less than zero if x is less than y, zero if x is equal to y, or a
1124 // value greater than zero if x is greater than y.
1125 private int Compare(Object x, Object y) {
1128 Debug.Assert(x != null && x is CacheKey);
1129 Debug.Assert(y != null && y is CacheKey);
1136 return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1144 return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1151 // Returns a hash code for the given object.
1153 int IEqualityComparer.GetHashCode(Object obj) {
1154 Debug.Assert(obj != null && obj is CacheKey);
1156 CacheKey cacheKey = (CacheKey) obj;
1158 return cacheKey.GetHashCode();
1165 sealed class CacheSingle : CacheInternal {
1167 static readonly TimeSpan INSERT_BLOCK_WAIT = new TimeSpan(0, 0, 10);
1168 const int MAX_COUNT = Int32.MaxValue / 2;
1169 const int MIN_COUNT = 10;
1172 Hashtable _entries; /* lookup table of entries */
1173 CacheExpires _expires; /* expires tables */
1174 CacheUsage _usage; /* usage tables */
1175 object _lock; /* read/write synchronization for _entries */
1176 int _disposed; /* disposed */
1177 int _totalCount; /* count of total entries */
1178 int _publicCount; /* count of public entries */
1179 ManualResetEvent _insertBlock; /* event to block inserts during high mem usage */
1180 bool _useInsertBlock; /* use insert block? */
1181 int _insertBlockCalls; /* number of callers using insert block */
1182 int _iSubCache; /* index of this cache */
1183 CacheMultiple _cacheMultiple; /* the CacheMultiple containing this cache */
1186 * Constructs a new Cache.
1188 internal CacheSingle(CacheCommon cacheCommon, CacheMultiple cacheMultiple, int iSubCache) : base(cacheCommon) {
1189 _cacheMultiple = cacheMultiple;
1190 _iSubCache = iSubCache;
1191 _entries = new Hashtable(CacheKeyComparer.GetInstance());
1192 _expires = new CacheExpires(this);
1193 _usage = new CacheUsage(this);
1194 _lock = new object();
1195 _insertBlock = new ManualResetEvent(true);
1196 cacheCommon.AddSRefTarget(new { _entries, _expires, _usage });
1200 * Dispose the cache.
1202 protected override void Dispose(bool disposing) {
1204 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
1205 if (_expires != null) {
1206 _expires.EnableExpirationTimer(false);
1210 CacheEntry[] entries = null;
1213 entries = new CacheEntry[_entries.Count];
1215 foreach (DictionaryEntry d in _entries) {
1216 entries[i++] = (CacheEntry) d.Value;
1220 foreach (CacheEntry entry in entries) {
1221 Remove(entry, CacheItemRemovedReason.Removed);
1224 // force any waiters to complete their waits. Note
1225 // that the insert block cannot be reacquired, as UseInsertBlock
1226 // checks the _disposed field.
1229 // release the block, causing it to be disposed when there
1230 // are no more callers.
1231 ReleaseInsertBlock();
1233 Debug.Trace("CacheDispose", "Cache disposed");
1237 base.Dispose(disposing);
1240 // Get the insert block manual reset event if it has not been disposed.
1241 ManualResetEvent UseInsertBlock() {
1246 int n = _insertBlockCalls;
1251 if (Interlocked.CompareExchange(ref _insertBlockCalls, n + 1, n) == n) {
1252 return _insertBlock;
1257 // Release the insert block event, and dispose it if it has been released
1258 // more times than it has been used
1259 void ReleaseInsertBlock() {
1260 if (Interlocked.Decrement(ref _insertBlockCalls) < 0) {
1261 ManualResetEvent e = _insertBlock;
1262 _insertBlock = null;
1269 // Set the insert block event.
1270 void SetInsertBlock() {
1271 ManualResetEvent e = null;
1273 e = UseInsertBlock();
1280 ReleaseInsertBlock();
1285 // Reset the insert block event.
1286 void ResetInsertBlock() {
1287 ManualResetEvent e = null;
1289 e = UseInsertBlock();
1296 ReleaseInsertBlock();
1301 // Wait on the insert block event.
1302 bool WaitInsertBlock() {
1303 bool signaled = false;
1304 ManualResetEvent e = null;
1306 e = UseInsertBlock();
1308 Debug.Trace("CacheMemoryTrimInsertBlock", "WaitInsertBlock: Cache " + _iSubCache + ": _useInsertBlock=true");
1309 signaled = e.WaitOne(INSERT_BLOCK_WAIT, false);
1310 Debug.Trace("CacheMemoryTrimInsertBlock", "Done waiting");
1315 ReleaseInsertBlock();
1322 internal void BlockInsertIfNeeded() {
1323 if (_cacheCommon._cacheMemoryStats.IsAboveHighPressure()) {
1324 Debug.Trace("CacheMemoryTrimInsertBlock", "BlockInsertIfNeeded: Cache " + _iSubCache + ": _useInsertBlock=true");
1325 _useInsertBlock = true;
1330 internal void UnblockInsert() {
1331 if (_useInsertBlock) {
1332 _useInsertBlock = false;
1334 Debug.Trace("CacheMemoryTrimInsertBlock", "UnblockInsert: Cache " + _iSubCache + ": _useInsertBlock=false");
1339 internal override int PublicCount {
1340 get {return _publicCount;}
1343 internal override long TotalCount {
1344 get {return _totalCount;}
1347 internal override IDictionaryEnumerator CreateEnumerator() {
1348 Hashtable h = new Hashtable(_publicCount);
1349 DateTime utcNow = DateTime.UtcNow;
1352 foreach (DictionaryEntry d in _entries) {
1353 CacheEntry entry = (CacheEntry) d.Value;
1355 // note that ASP.NET does not use this enumerator internally,
1356 // so we just choose public items.
1357 if (entry.IsPublic &&
1358 entry.State == CacheEntry.EntryState.AddedToCache &&
1359 ((!_cacheCommon._enableExpiration) || (utcNow <= entry.UtcExpires))) {
1360 h[entry.Key] = entry.Value;
1365 return h.GetEnumerator();
1369 * Performs all operations on the cache, with the
1370 * exception of Clear. The arguments indicate the type of operation:
1372 * @param key The key of the object.
1373 * @param newItem The new entry to be added to the cache.
1374 * @param replace Whether or not newEntry should replace an existing object in the cache.
1375 * @return The item requested. May be null.
1377 internal override CacheEntry UpdateCache(
1379 CacheEntry newEntry,
1381 CacheItemRemovedReason removedReason,
1382 out object valueOld)
1384 CacheEntry entry = null;
1385 CacheEntry oldEntry = null;
1386 bool expired = false;
1388 CacheDependency newEntryDependency = null;
1390 bool removeExpired = false;
1391 bool updateExpires = false;
1392 DateTime utcNewExpires = DateTime.MinValue;
1393 CacheEntry.EntryState entryState = CacheEntry.EntryState.NotInCache;
1394 bool newEntryNeedsClose = false;
1395 CacheItemRemovedReason newEntryRemovedReason = CacheItemRemovedReason.Removed;
1398 isGet = !replace && newEntry == null;
1399 isAdd = !replace && newEntry != null;
1402 * Perform update of cache data structures in a series to
1403 * avoid overlapping locks.
1405 * First, update the hashtable. The hashtable is the place
1406 * that guarantees what is in or out of the cache.
1408 * Loop here to remove expired items in a Get or Add, where
1409 * we can't otherwise delete an item.
1412 if (removeExpired) {
1413 Debug.Trace("CacheUpdate", "Removing expired item found in Get: " + cacheKey);
1414 UpdateCache(cacheKey, null, true, CacheItemRemovedReason.Expired, out valueOld);
1415 removeExpired = false;
1419 utcNow = DateTime.UtcNow;
1421 if (_useInsertBlock && newEntry != null && newEntry.HasUsage() /* HasUsage() means it's not NonRemovable */) {
1422 bool insertBlockReleased = WaitInsertBlock();
1425 if (!insertBlockReleased) {
1426 Debug.Trace("CacheUpdateWaitFailed", "WaitInsertBlock failed.");
1431 // the _entries hashtable supports multiple readers or one writer
1432 bool isLockEntered = false;
1434 Monitor.Enter(_lock, ref isLockEntered);
1437 entry = (CacheEntry) _entries[cacheKey];
1438 Debug.Trace("CacheUpdate", "Entry " + ((entry != null) ? "found" : "not found") + "in hashtable: " + cacheKey);
1440 if (entry != null) {
1441 entryState = entry.State;
1443 // If isGet == true, we are not hold any lock and so entryState can be anything
1446 entryState == CacheEntry.EntryState.AddingToCache ||
1447 entryState == CacheEntry.EntryState.AddedToCache,
1448 "entryState == CacheEntry.EntryState.AddingToCache || entryState == CacheEntry.EntryState.AddedToCache");
1450 expired = (_cacheCommon._enableExpiration) && (entry.UtcExpires < utcNow);
1454 * If the expired item is Added to the cache, remove it now before
1455 * its expiration timer fires up to a minute in the future.
1456 * Otherwise, just return null to indicate the item is not available.
1458 if (entryState == CacheEntry.EntryState.AddedToCache) {
1459 removeExpired = true;
1467 * If it's a call to Add, replace the item
1468 * when it has expired.
1473 * Change the removed reason.
1475 removedReason = CacheItemRemovedReason.Expired;
1479 updateExpires = (_cacheCommon._enableExpiration) && (entry.SlidingExpiration > TimeSpan.Zero);
1484 * Avoid running unnecessary code in a Get request by this simple test:
1488 * Remove an item from the hashtable.
1490 if (replace && entry != null) {
1491 bool doRemove = (entryState != CacheEntry.EntryState.AddingToCache);
1495 oldEntry.State = CacheEntry.EntryState.RemovingFromCache;
1497 _entries.Remove(oldEntry);
1498 Debug.Trace("CacheUpdate", "Entry removed from hashtable: " + cacheKey);
1502 * If we're removing and couldn't remove the old item
1503 * because its state was AddingToCache, return null
1504 * to indicate failure.
1506 if (newEntry == null) {
1507 Debug.Trace("CacheUpdate", "Removal from hashtable failed: " + cacheKey);
1514 * Add an item to the hashtable.
1516 if (newEntry != null) {
1519 if (entry != null) {
1520 if (oldEntry == null) {
1522 * We could not remove the existing entry,
1523 * either because it simply exists and replace == false,
1524 * or replace == true and it's state was AddingToCache when
1525 * we tried to remove it.
1528 newEntryRemovedReason = CacheItemRemovedReason.Removed;
1533 Debug.Trace("CacheUpdate", "Insertion into hashtable failed because old entry was not removed: " + cacheKey);
1540 /* non-definitive check */
1541 newEntryDependency = newEntry.Dependency;
1542 if (newEntryDependency != null) {
1543 if (newEntryDependency.HasChanged) {
1545 newEntryRemovedReason = CacheItemRemovedReason.DependencyChanged;
1550 Debug.Trace("CacheUpdate", "Insertion into hashtable failed because dependency changed: " + cacheKey);
1557 newEntry.State = CacheEntry.EntryState.AddingToCache;
1558 _entries.Add(newEntry, newEntry);
1561 * If this is an Add operation, indicate success
1562 * by returning null.
1565 Debug.Assert(entry == null || expired, "entry == null || expired");
1570 * Indicate success by returning the inserted entry.
1575 Debug.Trace("CacheUpdate", "Entry added to hashtable: " + cacheKey);
1580 * If we failed for an Insert, indicate failure by returning null.
1583 newEntryNeedsClose = true;
1587 * If we failed for an Add (e.g. Dependency has changed),
1588 * return the existing value. If existing value is null,
1589 * we have to close the newEntry ourselves. Otherwise, we'll
1590 * return non-null and the caller should close the item.
1592 newEntryNeedsClose = (entry == null);
1596 * If newEntry cannot be inserted, and it does not need to be
1597 * closed, set it to null so that we don't insert it later.
1598 * Leave it non-null when it needs to be closed that that we
1601 if (!newEntryNeedsClose) {
1612 if (isLockEntered) {
1613 Monitor.Exit(_lock);
1619 * Since we want Get to be fast, check here for a get without
1620 * alteration to cache.
1623 if (entry != null) {
1624 if (updateExpires) {
1625 utcNewExpires = utcNow + entry.SlidingExpiration;
1626 if (utcNewExpires - entry.UtcExpires >= CacheExpires.MIN_UPDATE_DELTA || utcNewExpires < entry.UtcExpires) {
1627 _expires.UtcUpdate(entry, utcNewExpires);
1631 UtcUpdateUsageRecursive(entry, utcNow);
1634 if (cacheKey.IsPublic) {
1635 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_RATIO_BASE);
1636 if (entry != null) {
1637 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_HITS);
1640 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_MISSES);
1644 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_RATIO_BASE);
1645 if (entry != null) {
1646 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_HITS);
1649 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_MISSES);
1653 if (entry != null) {
1654 Debug.Trace("CacheUpdate", "Cache hit: " + cacheKey);
1657 Debug.Trace("CacheUpdate", "Cache miss: " + cacheKey);
1664 int publicDelta = 0;
1665 int totalTurnover = 0;
1666 int publicTurnover = 0;
1668 if (oldEntry != null) {
1669 if (oldEntry.InExpires()) {
1670 _expires.Remove(oldEntry);
1673 if (oldEntry.InUsage()) {
1674 _usage.Remove(oldEntry);
1677 Debug.Assert(oldEntry.State == CacheEntry.EntryState.RemovingFromCache, "oldEntry.State == CacheEntry.EntryState.RemovingFromCache");
1678 oldEntry.State = CacheEntry.EntryState.RemovedFromCache;
1679 valueOld = oldEntry.Value;
1683 if (oldEntry.IsPublic) {
1689 Debug.Trace("CacheUpdate", "Entry removed from cache, reason=" + removedReason + ": " + (CacheKey) oldEntry);
1693 if (newEntry != null) {
1694 if (newEntryNeedsClose) {
1695 // Call close if newEntry could not be added.
1696 newEntry.State = CacheEntry.EntryState.RemovedFromCache;
1697 newEntry.Close(newEntryRemovedReason);
1701 Debug.Assert(!newEntry.InExpires());
1702 Debug.Assert(!newEntry.InUsage());
1704 if (_cacheCommon._enableExpiration && newEntry.HasExpiration()) {
1705 _expires.Add(newEntry);
1708 if ( _cacheCommon._enableMemoryCollection && newEntry.HasUsage() &&
1709 ( // Don't bother to set usage if it's going to expire very soon
1710 !newEntry.HasExpiration() ||
1711 newEntry.SlidingExpiration > TimeSpan.Zero ||
1712 newEntry.UtcExpires - utcNow >= CacheUsage.MIN_LIFETIME_FOR_USAGE)) {
1714 _usage.Add(newEntry);
1717 newEntry.State = CacheEntry.EntryState.AddedToCache;
1719 Debug.Trace("CacheUpdate", "Entry added to cache: " + (CacheKey)newEntry);
1723 if (newEntry.IsPublic) {
1730 // Call close after the newEntry has been fully added to the cache,
1731 // so the OnRemoveCallback can take a dependency on the newly inserted item.
1732 if (oldEntry != null) {
1733 oldEntry.Close(removedReason);
1736 // Delay monitoring change events until the oldEntry has been completely removed
1737 // from the cache, and its OnRemoveCallback called. This way we won't call the
1738 // OnRemoveCallback for newEntry before doing so for oldEntry.
1739 if (newEntry != null) {
1740 // listen to change events
1741 newEntry.MonitorDependencyChanges();
1744 * NB: We have to check for dependency changes after we add the item
1745 * to cache, because otherwise we may not remove it if it changes
1746 * between the time we check for a dependency change and the time
1747 * we set the AddedToCache bit. The worst that will happen is that
1748 * a get can occur on an item that has changed, but that can happen
1749 * anyway. The important thing is that we always remove an item that
1752 if (newEntryDependency != null && newEntryDependency.HasChanged) {
1753 Remove(newEntry, CacheItemRemovedReason.DependencyChanged);
1757 // update counts and counters
1758 if (totalDelta == 1) {
1759 Interlocked.Increment(ref _totalCount);
1760 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1762 else if (totalDelta == -1) {
1763 Interlocked.Decrement(ref _totalCount);
1764 PerfCounters.DecrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1767 if (publicDelta == 1) {
1768 Interlocked.Increment(ref _publicCount);
1769 PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1771 else if (publicDelta == -1) {
1772 Interlocked.Decrement(ref _publicCount);
1773 PerfCounters.DecrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1776 if (totalTurnover > 0) {
1777 PerfCounters.IncrementCounterEx(AppPerfCounter.TOTAL_CACHE_TURNOVER_RATE, totalTurnover);
1780 if (publicTurnover > 0) {
1781 PerfCounters.IncrementCounterEx(AppPerfCounter.API_CACHE_TURNOVER_RATE, publicTurnover);
1788 void UtcUpdateUsageRecursive(CacheEntry entry, DateTime utcNow) {
1789 CacheDependency dependency;
1790 CacheEntry[] entries;
1792 // Don't update if the last update is less than 1 sec away. This way we'll
1793 // avoid over updating the usage in the scenario where a cache makes several
1795 if (utcNow - entry.UtcLastUsageUpdate > CacheUsage.CORRELATED_REQUEST_TIMEOUT || utcNow < entry.UtcLastUsageUpdate) {
1796 entry.UtcLastUsageUpdate = utcNow;
1797 if (entry.InUsage()) {
1798 CacheSingle cacheSingle;
1799 if (_cacheMultiple == null) {
1803 cacheSingle = _cacheMultiple.GetCacheSingle(entry.Key.GetHashCode());
1806 cacheSingle._usage.Update(entry);
1809 dependency = entry.Dependency;
1810 if (dependency != null) {
1811 entries = dependency.CacheEntries;
1812 if (entries != null) {
1813 foreach (CacheEntry dependent in entries) {
1814 UtcUpdateUsageRecursive(dependent, utcNow);
1821 internal override long TrimIfNecessary(int percent) {
1822 Debug.Assert(_cacheCommon._inCacheManagerThread == 1, "Trim should only occur when we're updating memory statistics.");
1823 if (!_cacheCommon._enableMemoryCollection)
1827 // do we need to drop a percentage of entries?
1829 toTrim = (int)(((long)_totalCount * (long)percent) / 100L);
1831 // would this leave us above MAX_COUNT?
1832 int minTrim = _totalCount - MAX_COUNT;
1833 if (toTrim < minTrim) {
1836 // would this put us below MIN_COUNT?
1837 int maxTrim = _totalCount - MIN_COUNT;
1838 if (toTrim > maxTrim) {
1841 // do we need to trim?
1842 if (toTrim <= 0 || HostingEnvironment.ShutdownInitiated) {
1846 int ocEntriesTrimmed = 0; // number of output cache entries trimmed
1847 int publicEntriesTrimmed = 0; // number of public entries trimmed
1848 int totalTrimmed = 0; // total number of entries trimmed
1849 int trimmedOrExpired = 0;
1850 int beginTotalCount = _totalCount;
1853 trimmedOrExpired = _expires.FlushExpiredItems(true);
1854 if (trimmedOrExpired < toTrim) {
1855 totalTrimmed = _usage.FlushUnderUsedItems(toTrim - trimmedOrExpired, ref publicEntriesTrimmed, ref ocEntriesTrimmed);
1856 trimmedOrExpired += totalTrimmed;
1859 if (totalTrimmed > 0) {
1860 // Update values for perfcounters
1861 PerfCounters.IncrementCounterEx(AppPerfCounter.CACHE_TOTAL_TRIMS, totalTrimmed);
1862 PerfCounters.IncrementCounterEx(AppPerfCounter.CACHE_API_TRIMS, publicEntriesTrimmed);
1863 PerfCounters.IncrementCounterEx(AppPerfCounter.CACHE_OUTPUT_TRIMS, ocEntriesTrimmed);
1870 Debug.Trace("CacheMemory", "TrimIfNecessary: _iSubCache= " + _iSubCache
1871 + ", beginTotalCount=" + beginTotalCount
1872 + ", endTotalCount=" + _totalCount
1873 + ", percent=" + percent
1874 + ", trimmed=" + totalTrimmed);
1876 return trimmedOrExpired;
1879 internal override void EnableExpirationTimer(bool enable) {
1880 if (_expires != null) {
1881 _expires.EnableExpirationTimer(enable);
1886 class CacheMultiple : CacheInternal {
1888 DisposableGCHandleRef<CacheSingle>[] _cachesRefs;
1889 int _cacheIndexMask;
1891 internal CacheMultiple(CacheCommon cacheCommon, int numSingleCaches) : base(cacheCommon) {
1892 Debug.Assert(numSingleCaches > 1, "numSingleCaches is not greater than 1");
1893 Debug.Assert((numSingleCaches & (numSingleCaches - 1)) == 0, "numSingleCaches is not a power of 2");
1894 _cacheIndexMask = numSingleCaches - 1;
1896 // Each CacheSingle will have its own SRef reporting the size of the data it references.
1897 // Objects in this CacheSingle may have refs to the root Cache and therefore reference other instances of CacheSingle.
1898 // This leads to an unbalanced tree of SRefs and makes GC less efficient while calculating multiple SRefs on multiple cores.
1899 // Using DisposableGCHandleRef here prevents SRefs from calculating data that does not belong to other CacheSingle instances.
1900 _cachesRefs = new DisposableGCHandleRef<CacheSingle>[numSingleCaches];
1901 for (int i = 0; i < numSingleCaches; i++) {
1902 _cachesRefs[i] = new DisposableGCHandleRef<CacheSingle>(new CacheSingle(cacheCommon, this, i));
1906 protected override void Dispose(bool disposing) {
1908 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
1909 foreach (var cacheSingleRef in _cachesRefs) {
1910 // Unfortunately the application shutdown logic allows user to access cache even after its disposal.
1911 // We'll keep the GCHandle inside cacheSingleRef until it gets reclaimed during appdomain shutdown.
1912 // And we'll only dispose the Target to preserve the old behavior.
1913 cacheSingleRef.Target.Dispose();
1918 base.Dispose(disposing);
1921 internal override int PublicCount {
1924 foreach (var cacheSingleRef in _cachesRefs) {
1925 count += cacheSingleRef.Target.PublicCount;
1932 internal override long TotalCount {
1935 foreach (var cacheSingleRef in _cachesRefs) {
1936 count += cacheSingleRef.Target.TotalCount;
1943 internal override IDictionaryEnumerator CreateEnumerator() {
1944 IDictionaryEnumerator[] enumerators = new IDictionaryEnumerator[_cachesRefs.Length];
1945 for (int i = 0, c = _cachesRefs.Length; i < c; i++) {
1946 enumerators[i] = _cachesRefs[i].Target.CreateEnumerator();
1949 return new AggregateEnumerator(enumerators);
1952 internal CacheSingle GetCacheSingle(int hashCode) {
1953 Debug.Assert(_cachesRefs != null && _cachesRefs.Length != 0);
1954 // Dev10 865907: Math.Abs throws OverflowException for Int32.MinValue
1956 hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode;
1958 int index = (hashCode & _cacheIndexMask);
1959 Debug.Assert(_cachesRefs[index].Target != null);
1960 return _cachesRefs[index].Target;
1963 internal override CacheEntry UpdateCache(
1965 CacheEntry newEntry,
1967 CacheItemRemovedReason removedReason,
1968 out object valueOld) {
1970 int hashCode = cacheKey.Key.GetHashCode();
1971 CacheSingle cacheSingle = GetCacheSingle(hashCode);
1972 return cacheSingle.UpdateCache(cacheKey, newEntry, replace, removedReason, out valueOld);
1975 internal override long TrimIfNecessary(int percent) {
1977 foreach (var cacheSingleRef in _cachesRefs) {
1978 count += cacheSingleRef.Target.TrimIfNecessary(percent);
1983 internal override void EnableExpirationTimer(bool enable) {
1984 foreach (var cacheSingleRef in _cachesRefs) {
1985 cacheSingleRef.Target.EnableExpirationTimer(enable);
1990 class AggregateEnumerator : IDictionaryEnumerator {
1991 IDictionaryEnumerator [] _enumerators;
1994 internal AggregateEnumerator(IDictionaryEnumerator [] enumerators) {
1995 _enumerators = enumerators;
1998 public bool MoveNext() {
2002 more = _enumerators[_iCurrent].MoveNext();
2006 if (_iCurrent == _enumerators.Length - 1)
2015 public void Reset() {
2016 for (int i = 0; i <= _iCurrent; i++) {
2017 _enumerators[i].Reset();
2023 public Object Current {
2025 return _enumerators[_iCurrent].Current;
2031 return _enumerators[_iCurrent].Key;
2035 public Object Value {
2037 return _enumerators[_iCurrent].Value;
2041 public DictionaryEntry Entry {
2043 return _enumerators[_iCurrent].Entry;