[coop] Temporarily restore MonoThreadInfo when TLS destructor runs. Fixes #43099
[mono.git] / mcs / class / referencesource / System.Web / Cache / cache.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="cache.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 /*
8  * Cache class
9  *
10  * Copyright (c) 1999 Microsoft Corporation
11  */
12
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;
22     using System.Web;
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;
30
31
32     /// <devdoc>
33     /// <para>Represents the method that will handle the <see langword='onRemoveCallback'/>
34     /// event of a System.Web.Caching.Cache instance.</para>
35     /// </devdoc>
36     public delegate void CacheItemRemovedCallback(
37             string key, object value, CacheItemRemovedReason reason);
38
39     /// <devdoc>
40     /// <para>Represents the method that will handle the <see langword='onUpdateCallback'/>
41     /// event of a System.Web.Caching.Cache instance.</para>
42     /// </devdoc>
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);
48
49     /// <devdoc>
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
56     ///    normal.</para>
57     /// </devdoc>
58     public enum CacheItemPriority {
59
60         /// <devdoc>
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
63         ///       cache.</para>
64         /// </devdoc>
65         Low = 1,
66
67         /// <devdoc>
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>
71         /// </devdoc>
72         BelowNormal,
73
74         /// <devdoc>
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>
77         /// </devdoc>
78         Normal,
79
80         /// <devdoc>
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
83         ///       cache. </para>
84         /// </devdoc>
85         AboveNormal,
86
87         /// <devdoc>
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>
90         /// </devdoc>
91         High,
92
93         /// <devdoc>
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>
96         /// </devdoc>
97         NotRemovable,
98
99         /// <devdoc>
100         ///    <para>The default value is Normal.</para>
101         /// </devdoc>
102         Default = Normal
103     }
104
105
106     /// <devdoc>
107     ///    <para>Specifies the reason that a cached item was removed.</para>
108     /// </devdoc>
109     public enum CacheItemRemovedReason {
110
111         /// <devdoc>
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>
113         /// </devdoc>
114         Removed = 1,
115
116         /// <devdoc>
117         ///    <para>The item was removed from the cache because it expired. </para>
118         /// </devdoc>
119         Expired,
120
121         /// <devdoc>
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>
124         /// </devdoc>
125         Underused,
126
127         /// <devdoc>
128         ///    <para>The item was removed from the cache because a file or key dependency was
129         ///       changed.</para>
130         /// </devdoc>
131         DependencyChanged
132     }
133
134     /// <devdoc>
135     ///    <para>Specifies the reason why a cached item needs to be updated.</para>
136     /// </devdoc>
137     [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue",
138             Justification = "This enum should mirror CacheItemRemovedReason enum in design")]
139     public enum CacheItemUpdateReason {
140
141         /// <devdoc>
142         ///    <para>The item needs to be updated because it expired. </para>
143         /// </devdoc>
144         Expired = 1,
145
146         /// <devdoc>
147         ///    <para>The item needs to be updated because a file or key dependency was
148         ///       changed.</para>
149         /// </devdoc>
150         DependencyChanged
151     }
152
153     enum CacheGetOptions {
154         None                = 0,
155         ReturnCacheEntry    = 0x1,
156     }
157
158
159     /// <devdoc>
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>
164     /// </devdoc>
165
166     //
167     // Extra notes:
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.
171     //
172     public sealed class Cache : IEnumerable {
173
174         /// <devdoc>
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>
179         /// </devdoc>
180         public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
181
182
183         /// <devdoc>
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>
187         /// </devdoc>
188         public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
189
190         CacheInternal   _cacheInternal;
191         static CacheItemRemovedCallback s_sentinelRemovedCallback = new CacheItemRemovedCallback(SentinelEntry.OnCacheItemRemovedCallback);
192
193         /// <internalonly/>
194         /// <devdoc>
195         ///    <para>This constructor is for internal use only, and was accidentally made public - do not use.</para>
196         /// </devdoc>
197         [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
198         public Cache() {
199         }
200
201         //
202         // internal ctor used by CacheCommon that avoids the demand for UnmanagedCode.
203         //
204         internal Cache(int dummy) {
205         }
206
207         internal void SetCacheInternal(CacheInternal cacheInternal) {
208             _cacheInternal = cacheInternal;
209         }
210
211
212         /// <devdoc>
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>
216         /// </devdoc>
217         public int Count {
218             get {
219                 return _cacheInternal.PublicCount;
220             }
221         }
222
223
224         /// <internalonly/>
225         IEnumerator IEnumerable.GetEnumerator() {
226             return ((IEnumerable)_cacheInternal).GetEnumerator();
227         }
228
229
230         /// <devdoc>
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>
234         /// </devdoc>
235         public IDictionaryEnumerator GetEnumerator() {
236             return _cacheInternal.GetEnumerator();
237         }
238
239
240         /// <devdoc>
241         ///    <para>Gets or sets an item in the cache.</para>
242         /// </devdoc>
243         public object this[string key] {
244             get {
245                 return Get(key);
246             }
247
248             set {
249                 Insert(key, value);
250             }
251         }
252
253         private class SentinelEntry {
254             private string _key;
255             private CacheDependency _expensiveObjectDependency;
256             private CacheItemUpdateCallback _cacheItemUpdateCallback;
257
258             public SentinelEntry(string key, CacheDependency expensiveObjectDependency, CacheItemUpdateCallback callback) {
259                 _key = key;
260                 _expensiveObjectDependency = expensiveObjectDependency;
261                 _cacheItemUpdateCallback = callback;
262             }
263
264             public string Key {
265                 get { return _key; }
266             }
267
268             public CacheDependency ExpensiveObjectDependency {
269                 get { return _expensiveObjectDependency; }
270             }
271
272             public CacheItemUpdateCallback CacheItemUpdateCallback {
273                 get { return _cacheItemUpdateCallback; }
274             }
275
276             public static void OnCacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason) {
277                 CacheItemUpdateReason updateReason;
278                 SentinelEntry entry = value as SentinelEntry;
279
280                 switch (reason) {
281                     case CacheItemRemovedReason.Expired:
282                         updateReason = CacheItemUpdateReason.Expired;
283                         break;
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.
290                             return;
291                         }
292                         break;
293                     case CacheItemRemovedReason.Underused:
294                         Debug.Fail("Reason should never be CacheItemRemovedReason.Underused since the entry was inserted as NotRemovable.");
295                         return;
296                     default:
297                         // do nothing if reason is Removed
298                         return;
299                 }
300
301                 CacheDependency cacheDependency;
302                 DateTime absoluteExpiration;
303                 TimeSpan slidingExpiration;
304                 object expensiveObject;
305                 CacheItemUpdateCallback callback = entry.CacheItemUpdateCallback;
306                 // invoke update callback
307                 try {
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);
313                     }
314                     else {
315                         HttpRuntime.Cache.Remove(entry.Key);
316                     }
317                 }
318                 catch (Exception e) {
319                     HttpRuntime.Cache.Remove(entry.Key);
320                     try {
321                         WebBaseEvent.RaiseRuntimeError(e, value);
322                     }
323                     catch {
324                     }
325                 }
326             }
327         }
328
329         /// <devdoc>
330         ///    <para>Retrieves an item from the cache.</para>
331         /// </devdoc>
332         public object Get(string key) {
333             return _cacheInternal.DoGet(true, key, CacheGetOptions.None);
334         }
335
336         internal object Get(string key, CacheGetOptions getOptions) {
337             return _cacheInternal.DoGet(true, key, getOptions);
338         }
339
340
341         /// <devdoc>
342         ///    <para>Inserts an item into the Cache with default values.</para>
343         /// </devdoc>
344         public void Insert(string key, object value) {
345             _cacheInternal.DoInsert(
346                         true,
347                         key,
348                         value,
349                         null,
350                         NoAbsoluteExpiration,
351                         NoSlidingExpiration,
352                         CacheItemPriority.Default,
353                         null,
354                         true);
355         }
356
357
358         /// <devdoc>
359         /// <para>Inserts an object into the System.Web.Caching.Cache that has file or key
360         ///    dependencies.</para>
361         /// </devdoc>
362         public void Insert(string key, object value, CacheDependency dependencies) {
363             _cacheInternal.DoInsert(
364                         true,
365                         key,
366                         value,
367                         dependencies,
368                         NoAbsoluteExpiration,
369                         NoSlidingExpiration,
370                         CacheItemPriority.Default,
371                         null,
372                         true);
373         }
374
375
376         /// <devdoc>
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>
379         /// </devdoc>
380         public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration) {
381             DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
382             _cacheInternal.DoInsert(
383                         true,
384                         key,
385                         value,
386                         dependencies,
387                         utcAbsoluteExpiration,
388                         slidingExpiration,
389                         CacheItemPriority.Default,
390                         null,
391                         true);
392         }
393
394
395         public void Insert(
396                 string key,
397                 object value,
398                 CacheDependency dependencies,
399                 DateTime absoluteExpiration,
400                 TimeSpan slidingExpiration,
401                 CacheItemPriority priority,
402                 CacheItemRemovedCallback onRemoveCallback) {
403
404             DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
405             _cacheInternal.DoInsert(
406                         true,
407                         key,
408                         value,
409                         dependencies,
410                         utcAbsoluteExpiration,
411                         slidingExpiration,
412                         priority,
413                         onRemoveCallback,
414                         true);
415         }
416
417         // DevDiv Bugs 162763: 
418         // Add a an event that fires *before* an item is evicted from the ASP.NET Cache
419         public void Insert(
420                 string key,
421                 object value,
422                 CacheDependency dependencies,
423                 DateTime absoluteExpiration,
424                 TimeSpan slidingExpiration,
425                 CacheItemUpdateCallback onUpdateCallback) {
426
427             if (dependencies == null && absoluteExpiration == Cache.NoAbsoluteExpiration && slidingExpiration == Cache.NoSlidingExpiration) {
428                 throw new ArgumentException(SR.GetString(SR.Invalid_Parameters_To_Insert));
429             }
430             if (onUpdateCallback == null) {
431                 throw new ArgumentNullException("onUpdateCallback");
432             }
433             DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
434             // Insert updatable cache entry
435             _cacheInternal.DoInsert (
436                         true,
437                         key,
438                         value,
439                         null,
440                         Cache.NoAbsoluteExpiration,
441                         Cache.NoSlidingExpiration,
442                         CacheItemPriority.NotRemovable,
443                         null,
444                         true);
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;
450             }
451             else {
452                 AggregateCacheDependency deps = new AggregateCacheDependency();
453                 deps.Add(dependencies, expensiveObjectDep);
454                 dependencies = deps;
455             }
456             // Insert sentinel entry for the updatable cache entry 
457             _cacheInternal.DoInsert(
458                         false,
459                         CacheInternal.PrefixValidationSentinel + key,
460                         new SentinelEntry(key, expensiveObjectDep, onUpdateCallback),
461                         dependencies,
462                         utcAbsoluteExpiration,
463                         slidingExpiration,
464                         CacheItemPriority.NotRemovable,
465                         Cache.s_sentinelRemovedCallback,
466                         true);
467         }
468
469
470         public object Add(
471                 string key,
472                 object value,
473                 CacheDependency dependencies,
474                 DateTime absoluteExpiration,
475                 TimeSpan slidingExpiration,
476                 CacheItemPriority priority,
477                 CacheItemRemovedCallback onRemoveCallback) {
478
479             DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
480             return _cacheInternal.DoInsert(
481                         true,
482                         key,
483                         value,
484                         dependencies,
485                         utcAbsoluteExpiration,
486                         slidingExpiration,
487                         priority,
488                         onRemoveCallback,
489                         false);
490         }
491
492
493         /// <devdoc>
494         ///    <para>Removes the specified item from the cache. </para>
495         /// </devdoc>
496         public object Remove(string key) {
497             CacheKey cacheKey = new CacheKey(key, true);
498             return _cacheInternal.DoRemove(cacheKey, CacheItemRemovedReason.Removed);
499         }
500
501         public long EffectivePrivateBytesLimit {
502             get {
503                 return _cacheInternal.EffectivePrivateBytesLimit;
504             }
505         }
506
507         public long EffectivePercentagePhysicalMemoryLimit {
508             get {
509                 return _cacheInternal.EffectivePercentagePhysicalMemoryLimit;
510             }
511         }
512     }
513
514     class CacheCommon {
515         const int MEMORYSTATUS_INTERVAL_5_SECONDS = 5 * Msec.ONE_SECOND;
516         const int MEMORYSTATUS_INTERVAL_30_SECONDS = 30 * Msec.ONE_SECOND;
517
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;
529
530         internal CacheCommon() {
531             _cachePublic = new Cache(0);
532             _srefMultiple = new SRefMultiple();
533             _cacheMemoryStats = new CacheMemoryStats(_srefMultiple);
534             _enableMemoryCollection = true;
535             _enableExpiration = true;
536         }
537
538         internal void Dispose(bool disposing) {
539             if (disposing) {
540                 EnableCacheMemoryTimer(false);
541                 _cacheMemoryStats.Dispose();
542             }
543         }
544
545         internal void AddSRefTarget(CacheInternal c) {
546             _srefMultiple.AddSRefTarget(c);
547         }
548
549         internal void SetCacheInternal(CacheInternal cacheInternal) {
550             _cacheInternal = cacheInternal;
551             _cachePublic.SetCacheInternal(cacheInternal);
552         }
553
554         internal void ReadCacheInternalConfig(CacheSection cacheSection) {
555             if (_internalConfigRead) {
556                 return;
557             }
558
559             lock (this) {
560                 if (_internalConfigRead) {
561                     return;
562                 }
563
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;
567
568                 if (cacheSection != null) {
569                     _enableMemoryCollection = (!cacheSection.DisableMemoryCollection);
570                     _enableExpiration = (!cacheSection.DisableExpiration);
571                     _cacheMemoryStats.ReadConfig(cacheSection);
572                     _currentPollInterval = CacheMemorySizePressure.PollInterval;
573                     ResetFromConfigSettings();
574                 }
575             }
576         }
577
578         internal void ResetFromConfigSettings() {
579             EnableCacheMemoryTimer(_enableMemoryCollection);
580             _cacheInternal.EnableExpirationTimer(_enableExpiration);
581         }
582
583         internal void EnableCacheMemoryTimer(bool enable) {
584             lock (_timerLock) {
585 #if DBG
586                 if (Debug.IsTagPresent("Timer") && !Debug.IsTagEnabled("Timer")) {
587                     enable = false;
588                 }
589                 
590 #endif
591                 
592                 if (enable) {
593                     
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");
598                     }
599                     else {
600                         _timer.Change(_currentPollInterval, _currentPollInterval);
601                     }
602                 }
603                 else {
604                     Timer timer = _timer;
605                     if (timer != null && Interlocked.CompareExchange(ref _timer, null, timer) == timer) {
606                         timer.Dispose();
607                         Debug.Trace("Cache", "Stopped CacheMemoryTimers");
608                     }
609                 }
610             }
611
612             if (!enable) {
613                 // wait for CacheManagerTimerCallback to finish
614                 while(_inCacheManagerThread != 0) {
615                     Thread.Sleep(100);
616                 }
617             }
618         }
619
620         void AdjustTimer() {
621             lock (_timerLock) {
622
623                 if (_timer == null)
624                     return;
625
626                 // the order of these if statements is important
627                 
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);
633                     }
634                     return;
635                 }
636                 
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);
645                     }
646                     return;
647                 }
648                 
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);
653                 }
654             }
655         }
656
657         void CacheManagerTimerCallback(object state) {
658             CacheManagerThread(0);
659         }
660         
661         internal long CacheManagerThread(int minPercent) {
662             if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
663                 return 0;
664 #if DBG
665             Debug.Trace("CacheMemory", "**BEG** CacheManagerThread " + HttpRuntime.AppDomainAppId + ", " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
666 #endif
667             try {
668                 // Dev10 633335: if the timer has been disposed, return without doing anything
669                 if (_timer == null)
670                     return 0;
671
672                 // The timer thread must always call Update so that the CacheManager
673                 // knows the size of the cache.
674                 _cacheMemoryStats.Update();
675                 AdjustTimer();
676                 int percent = Math.Max(minPercent, _cacheMemoryStats.GetPercentToTrim());
677                 long beginTotalCount = _cacheInternal.TotalCount;
678                 Stopwatch sw = Stopwatch.StartNew();
679                 long trimmedOrExpired = _cacheInternal.TrimIfNecessary(percent);
680                 sw.Stop();
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);
685                 }
686
687 #if DBG
688                 Debug.Trace("CacheMemory", "**END** CacheManagerThread: " + HttpRuntime.AppDomainAppId
689                             + ", percent=" + percent
690                             + ", beginTotalCount=" + beginTotalCount
691                             + ", trimmed=" + trimmedOrExpired
692                             + ", Milliseconds=" + sw.ElapsedMilliseconds);
693 #endif
694
695 #if PERF
696                 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
697                                                     + " minPercent= " + minPercent
698                                                     + ", percent= " + percent
699                                                     + ", beginTotalCount=" + beginTotalCount
700                                                     + ", trimmed=" + trimmedOrExpired
701                                                     + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
702 #endif
703                 return trimmedOrExpired;
704             }
705             finally {
706                 Interlocked.Exchange(ref _inCacheManagerThread, 0);
707             }            
708         }
709     }
710
711     abstract class CacheInternal : IEnumerable, IDisposable {
712         // cache key prefixes - they keep cache keys short and prevent conflicts
713
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";
720
721         // Next prefix goes here, until we get to 'Z'
722
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";
747
748         protected CacheCommon _cacheCommon;
749         private int _disposed;
750
751         // virtual methods requiring implementation
752         internal abstract int PublicCount   {get;}
753
754         internal abstract long TotalCount   {get;}
755
756         internal abstract IDictionaryEnumerator CreateEnumerator();
757
758         internal abstract CacheEntry UpdateCache(
759                 CacheKey                cacheKey,
760                 CacheEntry              newEntry,
761                 bool                    replace,
762                 CacheItemRemovedReason  removedReason,
763                 out object              valueOld);
764
765         internal abstract long TrimIfNecessary(int percent);
766
767         internal abstract void EnableExpirationTimer(bool enable);
768
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).
773 #if USE_MEMORY_CACHE
774         private static bool _useMemoryCache;
775         private static volatile bool _useMemoryCacheInited;
776         internal static bool UseMemoryCache {
777             get {
778                 if (!_useMemoryCacheInited) {
779                     RegistryKey regKey = null;
780                     try {
781                         regKey = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\ASP.NET");
782                         if (regKey != null) {
783                             if ((int)regKey.GetValue("UseMemoryCache", 0)== 1) {
784                                 _useMemoryCache = true;
785                             }
786                         }
787                     }
788                     finally {
789                         if (regKey != null) {
790                             regKey.Close();
791                         }
792                     }
793                     _useMemoryCacheInited = true;
794                 }
795                 return _useMemoryCache;
796             }
797         }
798 #endif
799
800         // common implementation
801         static internal CacheInternal Create() {
802             CacheCommon cacheCommon = new CacheCommon();
803             CacheInternal cacheInternal;
804 #if USE_MEMORY_CACHE
805             if (UseMemoryCache) {
806                 cacheInternal = new MemCache(cacheCommon);
807                 cacheCommon.AddSRefTarget(cacheInternal);
808             }
809             else {
810 #endif
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
815                 numSubCaches = 1;
816                 numCPUs -= 1;
817                 while (numCPUs > 0) {
818                     numSubCaches <<= 1;
819                     numCPUs >>= 1;
820                 }
821                 if (numSubCaches == 1) {
822                     cacheInternal = new CacheSingle(cacheCommon, null, 0);
823                 }
824                 else {
825                     cacheInternal = new CacheMultiple(cacheCommon, numSubCaches);
826                 }
827 #if USE_MEMORY_CACHE
828             }
829 #endif
830             cacheCommon.SetCacheInternal(cacheInternal);
831             cacheCommon.ResetFromConfigSettings();
832
833             return cacheInternal;
834         }
835
836         protected CacheInternal(CacheCommon cacheCommon) {
837             _cacheCommon = cacheCommon;
838         }
839
840         protected virtual void Dispose(bool disposing) {
841             _cacheCommon.Dispose(disposing);
842         }
843
844         public void Dispose() {
845             _disposed = 1;
846             Dispose(true);
847             // no destructor, don't need it.
848             // System.GC.SuppressFinalize(this);
849         }
850
851         internal bool IsDisposed { get { return _disposed == 1; } }
852
853         internal void ReadCacheInternalConfig(CacheSection cacheSection) {
854             _cacheCommon.ReadCacheInternalConfig(cacheSection);
855         }
856
857         internal long TrimCache(int percent) {
858             return _cacheCommon.CacheManagerThread(percent);
859         }
860
861         internal Cache CachePublic {
862             get {return _cacheCommon._cachePublic;}
863         }
864
865         internal long EffectivePrivateBytesLimit {
866             get { return _cacheCommon._cacheMemoryStats.CacheSizePressure.MemoryLimit; }
867         }
868
869         internal long EffectivePercentagePhysicalMemoryLimit {
870             get { return _cacheCommon._cacheMemoryStats.TotalMemoryPressure.MemoryLimit; }
871         }
872
873         IEnumerator IEnumerable.GetEnumerator() {
874             return CreateEnumerator();
875         }
876
877         public IDictionaryEnumerator GetEnumerator() {
878             return CreateEnumerator();
879         }
880
881         internal object this[string key] {
882             get {
883                 return Get(key);
884             }
885         }
886
887         internal object Get(string key) {
888             return DoGet(false, key, CacheGetOptions.None);
889         }
890
891         internal object Get(string key, CacheGetOptions getOptions) {
892             return DoGet(false, key, getOptions);
893         }
894
895         internal object DoGet(bool isPublic, string key, CacheGetOptions getOptions) {
896             CacheEntry  entry;
897             CacheKey    cacheKey;
898             object      dummy;
899
900             cacheKey = new CacheKey(key, isPublic);
901             entry = UpdateCache(cacheKey, null, false, CacheItemRemovedReason.Removed, out dummy);
902             if (entry != null) {
903                 if ((getOptions & CacheGetOptions.ReturnCacheEntry) != 0) {
904                     return entry;
905                 }
906                 else {
907                     return entry.Value;
908                 }
909             }
910             else {
911                 return null;
912             }
913         }
914
915         internal void UtcInsert(string key, object value) {
916             DoInsert(false,
917                      key,
918                      value,
919                      null,
920                      Cache.NoAbsoluteExpiration,
921                      Cache.NoSlidingExpiration,
922                      CacheItemPriority.Default,
923                      null,
924                      true);
925
926         }
927
928         internal void UtcInsert(string key, object value, CacheDependency dependencies) {
929             DoInsert(false,
930                      key,
931                      value,
932                      dependencies,
933                      Cache.NoAbsoluteExpiration,
934                      Cache.NoSlidingExpiration,
935                      CacheItemPriority.Default,
936                      null,
937                      true);
938         }
939
940         internal void UtcInsert(
941                 string key,
942                 object value,
943                 CacheDependency dependencies,
944                 DateTime utcAbsoluteExpiration,
945                 TimeSpan slidingExpiration) {
946
947             DoInsert(false,
948                      key,
949                      value,
950                      dependencies,
951                      utcAbsoluteExpiration,
952                      slidingExpiration,
953                      CacheItemPriority.Default,
954                      null,
955                      true);
956         }
957
958         internal void UtcInsert(
959                 string key,
960                 object value,
961                 CacheDependency dependencies,
962                 DateTime utcAbsoluteExpiration,
963                 TimeSpan slidingExpiration,
964                 CacheItemPriority priority,
965                 CacheItemRemovedCallback onRemoveCallback) {
966
967             DoInsert(false,
968                      key,
969                      value,
970                      dependencies,
971                      utcAbsoluteExpiration,
972                      slidingExpiration,
973                      priority,
974                      onRemoveCallback,
975                      true);
976         }
977
978         internal object UtcAdd(
979                 string key,
980                 object value,
981                 CacheDependency dependencies,
982                 DateTime utcAbsoluteExpiration,
983                 TimeSpan slidingExpiration,
984                 CacheItemPriority priority,
985                 CacheItemRemovedCallback onRemoveCallback) {
986
987             return DoInsert(
988                         false,
989                         key,
990                         value,
991                         dependencies,
992                         utcAbsoluteExpiration,
993                         slidingExpiration,
994                         priority,
995                         onRemoveCallback,
996                         false);
997
998         }
999
1000         internal object DoInsert(
1001                 bool isPublic,
1002                 string key,
1003                 object value,
1004                 CacheDependency dependencies,
1005                 DateTime utcAbsoluteExpiration,
1006                 TimeSpan slidingExpiration,
1007                 CacheItemPriority priority,
1008                 CacheItemRemovedCallback onRemoveCallback,
1009                 bool replace) {
1010
1011
1012             /*
1013              * If we throw an exception, prevent a leak by a user who
1014              * writes the following:
1015              *
1016              *     Cache.Insert(key, value, new CacheDependency(file));
1017              */
1018             using (dependencies) {
1019                 CacheEntry      entry;
1020                 object          dummy;
1021
1022                 entry = new CacheEntry(
1023                         key,
1024                         value,
1025                         dependencies,
1026                         onRemoveCallback,
1027                         utcAbsoluteExpiration,
1028                         slidingExpiration,
1029                         priority,
1030                         isPublic);
1031
1032                 entry = UpdateCache(entry, entry, replace, CacheItemRemovedReason.Removed, out dummy);
1033
1034                 /*
1035                  * N.B. A set can fail if two or more threads set the same key
1036                  * at the same time.
1037                  */
1038 #if DBG
1039                 if (replace) {
1040                     string yesno = (entry != null) ? "succeeded" : "failed";
1041                     Debug.Trace("CacheAPIInsert", "Cache.Insert " + yesno + ": " + key);
1042                 }
1043                 else {
1044                     if (entry == null) {
1045                         Debug.Trace("CacheAPIAdd", "Cache.Add added new item: " + key);
1046                     }
1047                     else {
1048                         Debug.Trace("CacheAPIAdd", "Cache.Add returned existing item: " + key);
1049                     }
1050                 }
1051 #endif
1052
1053                 if (entry != null) {
1054                     return entry.Value;
1055                 }
1056                 else {
1057                     return null;
1058                 }
1059             }
1060         }
1061
1062         internal object Remove(string key) {
1063             CacheKey cacheKey = new CacheKey(key, false);
1064             return DoRemove(cacheKey, CacheItemRemovedReason.Removed);
1065         }
1066
1067         internal object Remove(CacheKey cacheKey, CacheItemRemovedReason reason)  {
1068             return DoRemove(cacheKey, reason);
1069         }
1070
1071         /*
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.
1075          *
1076          * @param key The key for the item.
1077          * @exception ArgumentException
1078          */
1079         internal object DoRemove(CacheKey cacheKey, CacheItemRemovedReason reason)  {
1080             object      valueOld;
1081
1082             UpdateCache(cacheKey, null, true, reason, out valueOld);
1083
1084 #if DBG
1085             if (valueOld != null) {
1086                 Debug.Trace("CacheAPIRemove", "Cache.Remove succeeded, reason=" + reason + ": " + cacheKey);
1087             }
1088             else {
1089                 Debug.Trace("CacheAPIRemove", "Cache.Remove failed, reason=" + reason + ": " + cacheKey);
1090             }
1091 #endif
1092
1093             return valueOld;
1094         }
1095     }
1096
1097     sealed class CacheKeyComparer : IEqualityComparer  {
1098         static CacheKeyComparer    s_comparerInstance;
1099
1100         static internal CacheKeyComparer GetInstance() {
1101             if (s_comparerInstance == null) {
1102                 s_comparerInstance = new CacheKeyComparer();
1103             }
1104
1105             return s_comparerInstance;
1106         }
1107
1108         private CacheKeyComparer()
1109         {
1110         }
1111
1112         bool IEqualityComparer.Equals(Object x, Object y)
1113         {
1114             return Compare(x, y) == 0;
1115         }
1116
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) {
1121             CacheKey  a, b;
1122
1123             Debug.Assert(x != null && x is CacheKey);
1124             Debug.Assert(y != null && y is CacheKey);
1125
1126             a = (CacheKey) x;
1127             b = (CacheKey) y;
1128
1129             if (a.IsPublic) {
1130                 if (b.IsPublic) {
1131                     return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1132                 }
1133                 else {
1134                     return 1;
1135                 }
1136             }
1137             else {
1138                 if (!b.IsPublic) {
1139                     return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1140                 }
1141                 else {
1142                     return -1;
1143                 }
1144             }
1145         }
1146         // Returns a hash code for the given object.
1147         //
1148         int IEqualityComparer.GetHashCode(Object obj) {
1149             Debug.Assert(obj != null && obj is CacheKey);
1150
1151             CacheKey cacheKey = (CacheKey) obj;
1152
1153             return cacheKey.GetHashCode();
1154         }
1155     }
1156
1157     /*
1158      * The cache.
1159      */
1160     sealed class CacheSingle : CacheInternal {
1161         // cache stats
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;
1165
1166
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 */
1179
1180         /*
1181          * Constructs a new Cache.
1182          */
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);
1192         }
1193
1194         /*
1195          * Dispose the cache.
1196          */
1197         protected override void Dispose(bool disposing) {
1198             if (disposing) {
1199                 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
1200                     if (_expires != null) {
1201                         _expires.EnableExpirationTimer(false);
1202                     }
1203
1204                     // close all items
1205                     CacheEntry[] entries = null;
1206
1207                     lock (_lock) {
1208                         entries = new CacheEntry[_entries.Count];
1209                         int i = 0;
1210                         foreach (DictionaryEntry d in _entries) {
1211                             entries[i++] = (CacheEntry) d.Value;
1212                         }
1213                     }
1214
1215                     foreach (CacheEntry entry in entries) {
1216                         Remove(entry, CacheItemRemovedReason.Removed);
1217                     }
1218
1219                     // force any waiters to complete their waits. Note
1220                     // that the insert block cannot be reacquired, as UseInsertBlock
1221                     // checks the _disposed field.
1222                     _insertBlock.Set();
1223
1224                     // release the block, causing it to be disposed when there
1225                     // are no more callers.
1226                     ReleaseInsertBlock();
1227
1228                     Debug.Trace("CacheDispose", "Cache disposed");
1229                 }
1230             }
1231
1232             base.Dispose(disposing);
1233         }
1234
1235         // Get the insert block manual reset event if it has not been disposed.
1236         ManualResetEvent UseInsertBlock() {
1237             for (;;) {
1238                 if (_disposed == 1)
1239                     return null;
1240
1241                 int n = _insertBlockCalls;
1242                 if (n < 0) {
1243                     return null;
1244                 }
1245
1246                 if (Interlocked.CompareExchange(ref _insertBlockCalls, n + 1, n) == n) {
1247                     return _insertBlock;
1248                 }
1249             }
1250         }
1251
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;
1258
1259                 // now close
1260                 e.Close();
1261             }
1262         }
1263
1264         // Set the insert block event.
1265         void SetInsertBlock() {
1266             ManualResetEvent e = null;
1267             try {
1268                 e = UseInsertBlock();
1269                 if (e != null) {
1270                     e.Set();
1271                 }
1272             }
1273             finally {
1274                 if (e != null) {
1275                     ReleaseInsertBlock();
1276                 }
1277             }
1278         }
1279
1280         // Reset the insert block event.
1281         void ResetInsertBlock() {
1282             ManualResetEvent e = null;
1283             try {
1284                 e = UseInsertBlock();
1285                 if (e != null) {
1286                     e.Reset();
1287                 }
1288             }
1289             finally {
1290                 if (e != null) {
1291                     ReleaseInsertBlock();
1292                 }
1293             }
1294         }
1295
1296         // Wait on the insert block event.
1297         bool WaitInsertBlock() {
1298             bool signaled = false;
1299             ManualResetEvent e = null;
1300             try {
1301                 e = UseInsertBlock();
1302                 if (e != null) {
1303                     Debug.Trace("CacheMemoryTrimInsertBlock", "WaitInsertBlock: Cache " + _iSubCache + ": _useInsertBlock=true");
1304                     signaled = e.WaitOne(INSERT_BLOCK_WAIT, false);
1305                     Debug.Trace("CacheMemoryTrimInsertBlock", "Done waiting");
1306                 }
1307             }
1308             finally {
1309                 if (e != null) {
1310                     ReleaseInsertBlock();
1311                 }
1312             }
1313
1314             return signaled;
1315         }
1316
1317         internal void BlockInsertIfNeeded() {
1318             if (_cacheCommon._cacheMemoryStats.IsAboveHighPressure()) {
1319                 Debug.Trace("CacheMemoryTrimInsertBlock", "BlockInsertIfNeeded: Cache " + _iSubCache + ": _useInsertBlock=true");
1320                 _useInsertBlock = true;
1321                 ResetInsertBlock();
1322             }
1323         }
1324
1325         internal void UnblockInsert() {
1326             if (_useInsertBlock) {
1327                 _useInsertBlock = false;
1328                 SetInsertBlock();
1329                 Debug.Trace("CacheMemoryTrimInsertBlock", "UnblockInsert: Cache " + _iSubCache + ": _useInsertBlock=false");
1330             }
1331         }
1332
1333
1334         internal override int PublicCount {
1335             get {return _publicCount;}
1336         }
1337
1338         internal override long TotalCount {
1339             get {return _totalCount;}
1340         }
1341
1342         internal override IDictionaryEnumerator CreateEnumerator() {
1343             Hashtable h = new Hashtable(_publicCount);
1344             DateTime utcNow = DateTime.UtcNow;
1345
1346             lock (_lock) {
1347                 foreach (DictionaryEntry d in _entries) {
1348                     CacheEntry entry = (CacheEntry) d.Value;
1349
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;
1356                     }
1357                 }
1358             }
1359
1360             return h.GetEnumerator();
1361         }
1362
1363         /*
1364          * Performs all operations on the cache, with the
1365          * exception of Clear. The arguments indicate the type of operation:
1366          *
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.
1371          */
1372         internal override CacheEntry UpdateCache(
1373                 CacheKey                cacheKey,
1374                 CacheEntry              newEntry,
1375                 bool                    replace,
1376                 CacheItemRemovedReason  removedReason,
1377                 out object              valueOld)
1378         {
1379             CacheEntry              entry = null;
1380             CacheEntry              oldEntry = null;
1381             bool                    expired = false;
1382             DateTime                utcNow;
1383             CacheDependency         newEntryDependency = null;
1384             bool                    isGet, isAdd;
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;
1391
1392             valueOld = null;
1393             isGet = !replace && newEntry == null;
1394             isAdd = !replace && newEntry != null;
1395
1396             /*
1397              * Perform update of cache data structures in a series to
1398              * avoid overlapping locks.
1399              *
1400              * First, update the hashtable. The hashtable is the place
1401              * that guarantees what is in or out of the cache.
1402              *
1403              * Loop here to remove expired items in a Get or Add, where
1404              * we can't otherwise delete an item.
1405              */
1406             for (;;) {
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;
1411                 }
1412
1413                 entry = null;
1414                 utcNow = DateTime.UtcNow;
1415
1416                 if (_useInsertBlock && newEntry != null && newEntry.HasUsage() /* HasUsage() means it's not NonRemovable */) {
1417                     bool insertBlockReleased = WaitInsertBlock();
1418
1419 #if DBG
1420                     if (!insertBlockReleased) {
1421                         Debug.Trace("CacheUpdateWaitFailed", "WaitInsertBlock failed.");
1422                     }
1423 #endif
1424                 }
1425
1426                 // the _entries hashtable supports multiple readers or one writer
1427                 bool isLockEntered = false;
1428                 if (!isGet) {
1429                     Monitor.Enter(_lock, ref isLockEntered);
1430                 }
1431                 try {
1432                     entry = (CacheEntry) _entries[cacheKey];
1433                     Debug.Trace("CacheUpdate", "Entry " + ((entry != null) ? "found" : "not found") + "in hashtable: " + cacheKey);
1434
1435                     if (entry != null) {
1436                         entryState = entry.State;
1437
1438                         // If isGet == true, we are not hold any lock and so entryState can be anything
1439                         Debug.Assert(
1440                             isGet ||
1441                             entryState == CacheEntry.EntryState.AddingToCache ||
1442                             entryState == CacheEntry.EntryState.AddedToCache,
1443                             "entryState == CacheEntry.EntryState.AddingToCache || entryState == CacheEntry.EntryState.AddedToCache");
1444
1445                         expired = (_cacheCommon._enableExpiration) && (entry.UtcExpires < utcNow);
1446                         if (expired) {
1447                             if (isGet) {
1448                                 /*
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.
1452                                  */
1453                                 if (entryState == CacheEntry.EntryState.AddedToCache) {
1454                                     removeExpired = true;
1455                                     continue;
1456                                 }
1457
1458                                 entry = null;
1459                             }
1460                             else {
1461                                 /*
1462                                  * If it's a call to Add, replace the item
1463                                  * when it has expired.
1464                                  */
1465                                 replace = true;
1466
1467                                 /*
1468                                  * Change the removed reason.
1469                                  */
1470                                 removedReason = CacheItemRemovedReason.Expired;
1471                             }
1472                         }
1473                         else {
1474                             updateExpires = (_cacheCommon._enableExpiration) && (entry.SlidingExpiration > TimeSpan.Zero);
1475                         }
1476                     }
1477
1478                     /*
1479                      * Avoid running unnecessary code in a Get request by this simple test:
1480                      */
1481                     if (!isGet) {
1482                         /*
1483                          * Remove an item from the hashtable.
1484                          */
1485                         if (replace && entry != null) {
1486                             bool doRemove = (entryState != CacheEntry.EntryState.AddingToCache);
1487                             if (doRemove) {
1488                                 oldEntry = entry;
1489
1490                                 oldEntry.State = CacheEntry.EntryState.RemovingFromCache;
1491
1492                                 _entries.Remove(oldEntry);
1493                                 Debug.Trace("CacheUpdate", "Entry removed from hashtable: " + cacheKey);
1494                             }
1495                             else {
1496                                 /*
1497                                  * If we're removing and couldn't remove the old item
1498                                  * because its state was AddingToCache, return null
1499                                  * to indicate failure.
1500                                  */
1501                                 if (newEntry == null) {
1502                                     Debug.Trace("CacheUpdate", "Removal from hashtable failed: " + cacheKey);
1503                                     entry = null;
1504                                 }
1505                             }
1506                         }
1507
1508                         /*
1509                          * Add an item to the hashtable.
1510                          */
1511                         if (newEntry != null) {
1512                             bool doAdd = true;
1513
1514                             if (entry != null) {
1515                                 if (oldEntry == null) {
1516                                     /*
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.
1521                                     */
1522                                     doAdd = false;
1523                                     newEntryRemovedReason = CacheItemRemovedReason.Removed;
1524                                 }
1525
1526 #if DBG
1527                                 if (!doAdd) {
1528                                     Debug.Trace("CacheUpdate", "Insertion into hashtable failed because old entry was not removed: " + cacheKey);
1529                                 }
1530 #endif
1531                             }
1532
1533
1534                             if (doAdd) {
1535                                 /* non-definitive check */
1536                                 newEntryDependency = newEntry.Dependency;
1537                                 if (newEntryDependency != null) {
1538                                     if (newEntryDependency.HasChanged) {
1539                                         doAdd = false;
1540                                         newEntryRemovedReason = CacheItemRemovedReason.DependencyChanged;
1541                                     }
1542
1543 #if DBG
1544                                     if (!doAdd) {
1545                                         Debug.Trace("CacheUpdate", "Insertion into hashtable failed because dependency changed: " + cacheKey);
1546                                     }
1547 #endif
1548                                 }
1549                             }
1550
1551                             if (doAdd) {
1552                                 newEntry.State = CacheEntry.EntryState.AddingToCache;
1553                                 _entries.Add(newEntry, newEntry);
1554
1555                                 /*
1556                                  * If this is an Add operation, indicate success
1557                                  * by returning null.
1558                                  */
1559                                 if (isAdd) {
1560                                     Debug.Assert(entry == null || expired, "entry == null || expired");
1561                                     entry = null;
1562                                 }
1563                                 else {
1564                                     /*
1565                                      * Indicate success by returning the inserted entry.
1566                                      */
1567                                     entry = newEntry;
1568                                 }
1569
1570                                 Debug.Trace("CacheUpdate", "Entry added to hashtable: " + cacheKey);
1571                             }
1572                             else {
1573                                 if (!isAdd) {
1574                                     /*
1575                                      * If we failed for an Insert, indicate failure by returning null.
1576                                      */
1577                                     entry = null;
1578                                     newEntryNeedsClose = true;
1579                                 }
1580                                 else {
1581                                     /*
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.
1586                                      */
1587                                     newEntryNeedsClose = (entry == null);
1588                                 }
1589
1590                                 /*
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
1594                                  * can close it.
1595                                  */
1596                                 if (!newEntryNeedsClose) {
1597                                     newEntry = null;
1598                                 }
1599
1600                             }
1601                         }
1602                     }
1603
1604                     break;
1605                 }
1606                 finally {
1607                     if (isLockEntered) {
1608                         Monitor.Exit(_lock);
1609                     }
1610                 }
1611             }
1612
1613             /*
1614              * Since we want Get to be fast, check here for a get without
1615              * alteration to cache.
1616              */
1617             if (isGet) {
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);
1623                         }
1624                     }
1625
1626                     UtcUpdateUsageRecursive(entry, utcNow);
1627                 }
1628
1629                 if (cacheKey.IsPublic) {
1630                     PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_RATIO_BASE);
1631                     if (entry != null) {
1632                         PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_HITS);
1633                     }
1634                     else {
1635                         PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_MISSES);
1636                     }
1637                 }
1638
1639                 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_RATIO_BASE);
1640                 if (entry != null) {
1641                     PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_HITS);
1642                 }
1643                 else {
1644                     PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_MISSES);
1645                 }
1646
1647 #if DBG
1648                 if (entry != null) {
1649                     Debug.Trace("CacheUpdate", "Cache hit: " + cacheKey);
1650                 }
1651                 else {
1652                     Debug.Trace("CacheUpdate", "Cache miss: " + cacheKey);
1653                 }
1654 #endif
1655
1656             }
1657             else {
1658                 int totalDelta = 0;
1659                 int publicDelta = 0;
1660                 int totalTurnover = 0;
1661                 int publicTurnover = 0;
1662
1663                 if (oldEntry != null) {
1664                     if (oldEntry.InExpires()) {
1665                         _expires.Remove(oldEntry);
1666                     }
1667
1668                     if (oldEntry.InUsage()) {
1669                         _usage.Remove(oldEntry);
1670                     }
1671
1672                     Debug.Assert(oldEntry.State == CacheEntry.EntryState.RemovingFromCache, "oldEntry.State == CacheEntry.EntryState.RemovingFromCache");
1673                     oldEntry.State = CacheEntry.EntryState.RemovedFromCache;
1674                     valueOld = oldEntry.Value;
1675
1676                     totalDelta--;
1677                     totalTurnover++;
1678                     if (oldEntry.IsPublic) {
1679                         publicDelta--;
1680                         publicTurnover++;
1681                     }
1682
1683 #if DBG
1684                     Debug.Trace("CacheUpdate", "Entry removed from cache, reason=" + removedReason + ": " + (CacheKey) oldEntry);
1685 #endif
1686                 }
1687
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);
1693                         newEntry = null;
1694                     }
1695                     else {
1696                         Debug.Assert(!newEntry.InExpires());
1697                         Debug.Assert(!newEntry.InUsage());
1698
1699                         if (_cacheCommon._enableExpiration && newEntry.HasExpiration()) {
1700                             _expires.Add(newEntry);
1701                         }
1702
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)) {
1708
1709                             _usage.Add(newEntry);
1710                         }
1711
1712                         newEntry.State = CacheEntry.EntryState.AddedToCache;
1713
1714                         Debug.Trace("CacheUpdate", "Entry added to cache: " + (CacheKey)newEntry);
1715
1716                         totalDelta++;
1717                         totalTurnover++;
1718                         if (newEntry.IsPublic) {
1719                             publicDelta++;
1720                             publicTurnover++;
1721                         }
1722                     }
1723                 }
1724
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);
1729                 }
1730
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();
1737
1738                     /*
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
1745                      * has changed.
1746                      */
1747                     if (newEntryDependency != null && newEntryDependency.HasChanged) {
1748                         Remove(newEntry, CacheItemRemovedReason.DependencyChanged);
1749                     }
1750                 }
1751                 
1752                 // update counts and counters
1753                 if (totalDelta == 1) {
1754                     Interlocked.Increment(ref _totalCount);
1755                     PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1756                 }
1757                 else if (totalDelta == -1) {
1758                     Interlocked.Decrement(ref _totalCount);
1759                     PerfCounters.DecrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1760                 }
1761
1762                 if (publicDelta == 1) {
1763                     Interlocked.Increment(ref _publicCount);
1764                     PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1765                 }
1766                 else if (publicDelta == -1) {
1767                     Interlocked.Decrement(ref _publicCount);
1768                     PerfCounters.DecrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1769                 }
1770
1771                 if (totalTurnover > 0) {
1772                     PerfCounters.IncrementCounterEx(AppPerfCounter.TOTAL_CACHE_TURNOVER_RATE, totalTurnover);
1773                 }
1774
1775                 if (publicTurnover > 0) {
1776                     PerfCounters.IncrementCounterEx(AppPerfCounter.API_CACHE_TURNOVER_RATE, publicTurnover);
1777                 }
1778             }
1779
1780             return entry;
1781         }
1782
1783         void UtcUpdateUsageRecursive(CacheEntry entry, DateTime utcNow) {
1784             CacheDependency dependency;
1785             CacheEntry[]    entries;
1786
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
1789             // update requests.
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) {
1795                         cacheSingle = this;
1796                     }
1797                     else {
1798                         cacheSingle = _cacheMultiple.GetCacheSingle(entry.Key.GetHashCode());
1799                     }
1800
1801                     cacheSingle._usage.Update(entry);
1802                 }
1803
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);
1810                         }
1811                     }
1812                 }
1813             }
1814         }
1815
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)
1819                 return 0;
1820
1821             int toTrim = 0;
1822             // do we need to drop a percentage of entries?
1823             if (percent > 0) {
1824                 toTrim = (int)(((long)_totalCount * (long)percent) / 100L);
1825             }
1826             // would this leave us above MAX_COUNT?
1827             int minTrim = _totalCount - MAX_COUNT;
1828             if (toTrim < minTrim) {
1829                 toTrim = minTrim;
1830             }
1831             // would this put us below MIN_COUNT?
1832             int maxTrim = _totalCount - MIN_COUNT;
1833             if (toTrim > maxTrim) {
1834                 toTrim = maxTrim;
1835             }
1836             // do we need to trim?
1837             if (toTrim <= 0 || HostingEnvironment.ShutdownInitiated) {
1838                 return 0;
1839             }
1840             
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;
1846
1847             try {
1848                 trimmedOrExpired = _expires.FlushExpiredItems(true);
1849                 if (trimmedOrExpired < toTrim) {
1850                     totalTrimmed = _usage.FlushUnderUsedItems(toTrim - trimmedOrExpired, ref publicEntriesTrimmed, ref ocEntriesTrimmed);
1851                     trimmedOrExpired += totalTrimmed;
1852                 }
1853                 
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);
1859                 }
1860             }
1861             catch {
1862             }
1863             
1864 #if DBG
1865             Debug.Trace("CacheMemory", "TrimIfNecessary: _iSubCache= " + _iSubCache 
1866                         + ", beginTotalCount=" + beginTotalCount
1867                         + ", endTotalCount=" + _totalCount
1868                         + ", percent=" + percent 
1869                         + ", trimmed=" + totalTrimmed);
1870 #endif
1871             return trimmedOrExpired;
1872         }
1873         
1874         internal override void EnableExpirationTimer(bool enable) {
1875             if (_expires != null) {
1876                 _expires.EnableExpirationTimer(enable);
1877             }
1878         }
1879     }
1880
1881     class CacheMultiple : CacheInternal {
1882         int             _disposed;
1883         CacheSingle[]   _caches;
1884         int             _cacheIndexMask;
1885
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);
1893             }
1894         }
1895
1896         protected override void Dispose(bool disposing) {
1897             if (disposing) {
1898                 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
1899                     foreach (CacheSingle cacheSingle in _caches) {
1900                         cacheSingle.Dispose();
1901                     }
1902                 }
1903             }
1904
1905             base.Dispose(disposing);
1906         }
1907
1908         internal override int PublicCount {
1909             get {
1910                 int count = 0;
1911                 foreach (CacheSingle cacheSingle in _caches) {
1912                     count += cacheSingle.PublicCount;
1913                 }
1914
1915                 return count;
1916             }
1917         }
1918
1919         internal override long TotalCount {
1920             get {
1921                 long count = 0;
1922                 foreach (CacheSingle cacheSingle in _caches) {
1923                     count += cacheSingle.TotalCount;
1924                 }
1925
1926                 return count;
1927             }
1928         }
1929
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();
1934             }
1935
1936             return new AggregateEnumerator(enumerators);
1937         }
1938
1939         internal CacheSingle GetCacheSingle(int hashCode) {
1940             Debug.Assert(_caches != null && _caches.Length != 0);
1941             // Dev10 865907: Math.Abs throws OverflowException for Int32.MinValue
1942             if (hashCode < 0) {
1943                 hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode;
1944             }
1945             int index = (hashCode & _cacheIndexMask);
1946             return _caches[index];
1947         }
1948
1949         internal override CacheEntry UpdateCache(
1950                 CacheKey cacheKey,
1951                 CacheEntry newEntry,
1952                 bool replace,
1953                 CacheItemRemovedReason removedReason,
1954                 out object valueOld) {
1955
1956             int hashCode = cacheKey.Key.GetHashCode();
1957             CacheSingle cacheSingle = GetCacheSingle(hashCode);
1958             return cacheSingle.UpdateCache(cacheKey, newEntry, replace, removedReason, out valueOld);
1959         }
1960
1961         internal override long TrimIfNecessary(int percent) {
1962             long count = 0;
1963             foreach (CacheSingle cacheSingle in _caches) {
1964                 count += cacheSingle.TrimIfNecessary(percent);
1965             }
1966             return count;
1967         }
1968
1969         internal override void EnableExpirationTimer(bool enable) {
1970             foreach (CacheSingle cacheSingle in _caches) {
1971                 cacheSingle.EnableExpirationTimer(enable);
1972             }
1973         }
1974     }
1975
1976     class AggregateEnumerator : IDictionaryEnumerator {
1977         IDictionaryEnumerator []    _enumerators;
1978         int                         _iCurrent;
1979
1980         internal AggregateEnumerator(IDictionaryEnumerator [] enumerators) {
1981             _enumerators = enumerators;
1982         }
1983
1984         public bool MoveNext() {
1985             bool more;
1986
1987             for (;;) {
1988                 more = _enumerators[_iCurrent].MoveNext();
1989                 if (more)
1990                     break;
1991
1992                 if (_iCurrent == _enumerators.Length - 1)
1993                     break;
1994
1995                 _iCurrent++;
1996             }
1997
1998             return more;
1999         }
2000
2001         public void Reset() {
2002             for (int i = 0; i <= _iCurrent; i++) {
2003                 _enumerators[i].Reset();
2004             }
2005
2006             _iCurrent = 0;
2007         }
2008
2009         public Object Current {
2010             get {
2011                 return _enumerators[_iCurrent].Current;
2012             }
2013         }
2014
2015         public Object Key {
2016             get {
2017                 return _enumerators[_iCurrent].Key;
2018             }
2019         }
2020
2021         public Object Value {
2022             get {
2023                 return _enumerators[_iCurrent].Value;
2024             }
2025         }
2026
2027         public DictionaryEntry Entry {
2028             get {
2029                 return _enumerators[_iCurrent].Entry;
2030             }
2031         }
2032     }
2033 }
2034