[bcl] Update Reference Source to .NET Framework 4.6.2
[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  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;
530
531         internal CacheCommon() {
532             _cachePublic = new Cache(0);
533             _srefMultiple = new SRefMultiple();
534             _cacheMemoryStats = new CacheMemoryStats(_srefMultiple);
535             _enableMemoryCollection = true;
536             _enableExpiration = true;
537         }
538
539         internal void Dispose(bool disposing) {
540             if (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();
545                 }
546             }
547         }
548
549         internal void AddSRefTarget(object o) {
550             _srefMultiple.AddSRefTarget(o);
551         }
552
553         internal void SetCacheInternal(CacheInternal cacheInternal) {
554             _cacheInternal = cacheInternal;
555             _cachePublic.SetCacheInternal(cacheInternal);
556         }
557
558         internal void ReadCacheInternalConfig(CacheSection cacheSection) {
559             if (_internalConfigRead) {
560                 return;
561             }
562
563             lock (this) {
564                 if (_internalConfigRead) {
565                     return;
566                 }
567
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;
571
572                 if (cacheSection != null) {
573                     _enableMemoryCollection = (!cacheSection.DisableMemoryCollection);
574                     _enableExpiration = (!cacheSection.DisableExpiration);
575                     _cacheMemoryStats.ReadConfig(cacheSection);
576                     _currentPollInterval = CacheMemorySizePressure.PollInterval;
577                     ResetFromConfigSettings();
578                 }
579             }
580         }
581
582         internal void ResetFromConfigSettings() {
583             EnableCacheMemoryTimer(_enableMemoryCollection);
584             _cacheInternal.EnableExpirationTimer(_enableExpiration);
585         }
586
587         internal void EnableCacheMemoryTimer(bool enable) {
588             lock (_timerLock) {
589 #if DBG
590                 if (Debug.IsTagPresent("Timer") && !Debug.IsTagEnabled("Timer")) {
591                     enable = false;
592                 }
593                 
594 #endif
595                 
596                 if (enable) {
597                     
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");
603                     }
604                     else {
605                         _timerHandleRef.Target.Change(_currentPollInterval, _currentPollInterval);
606                     }
607                 }
608                 else {
609                     var timerHandleRef = _timerHandleRef;
610                     if (timerHandleRef != null && Interlocked.CompareExchange(ref _timerHandleRef, null, timerHandleRef) == timerHandleRef) {
611                         timerHandleRef.Dispose();
612                         Debug.Trace("Cache", "Stopped CacheMemoryTimers");
613                     }
614                 }
615             }
616
617             if (!enable) {
618                 // wait for CacheManagerTimerCallback to finish
619                 while(_inCacheManagerThread != 0) {
620                     Thread.Sleep(100);
621                 }
622             }
623         }
624
625         void AdjustTimer() {
626             lock (_timerLock) {
627
628                 if (_timerHandleRef == null)
629                     return;
630
631                 // the order of these if statements is important
632                 
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);
638                     }
639                     return;
640                 }
641                 
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);
650                     }
651                     return;
652                 }
653                 
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);
658                 }
659             }
660         }
661
662         void CacheManagerTimerCallback(object state) {
663             CacheManagerThread(0);
664         }
665         
666         internal long CacheManagerThread(int minPercent) {
667             if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
668                 return 0;
669 #if DBG
670             Debug.Trace("CacheMemory", "**BEG** CacheManagerThread " + HttpRuntime.AppDomainAppId + ", " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
671 #endif
672             try {
673                 // Dev10 633335: if the timer has been disposed, return without doing anything
674                 if (_timerHandleRef == null)
675                     return 0;
676
677                 // The timer thread must always call Update so that the CacheManager
678                 // knows the size of the cache.
679                 _cacheMemoryStats.Update();
680                 AdjustTimer();
681                 int percent = Math.Max(minPercent, _cacheMemoryStats.GetPercentToTrim());
682                 long beginTotalCount = _cacheInternal.TotalCount;
683                 Stopwatch sw = Stopwatch.StartNew();
684                 long trimmedOrExpired = _cacheInternal.TrimIfNecessary(percent);
685                 sw.Stop();
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);
690                 }
691
692 #if DBG
693                 Debug.Trace("CacheMemory", "**END** CacheManagerThread: " + HttpRuntime.AppDomainAppId
694                             + ", percent=" + percent
695                             + ", beginTotalCount=" + beginTotalCount
696                             + ", trimmed=" + trimmedOrExpired
697                             + ", Milliseconds=" + sw.ElapsedMilliseconds);
698 #endif
699
700 #if PERF
701                 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
702                                                     + " minPercent= " + minPercent
703                                                     + ", percent= " + percent
704                                                     + ", beginTotalCount=" + beginTotalCount
705                                                     + ", trimmed=" + trimmedOrExpired
706                                                     + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
707 #endif
708                 return trimmedOrExpired;
709             }
710             finally {
711                 Interlocked.Exchange(ref _inCacheManagerThread, 0);
712             }            
713         }
714     }
715
716     abstract class CacheInternal : IEnumerable, IDisposable {
717         // cache key prefixes - they keep cache keys short and prevent conflicts
718
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";
725
726         // Next prefix goes here, until we get to 'Z'
727
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";
752
753         protected CacheCommon _cacheCommon;
754         private int _disposed;
755
756         // virtual methods requiring implementation
757         internal abstract int PublicCount   {get;}
758
759         internal abstract long TotalCount   {get;}
760
761         internal abstract IDictionaryEnumerator CreateEnumerator();
762
763         internal abstract CacheEntry UpdateCache(
764                 CacheKey                cacheKey,
765                 CacheEntry              newEntry,
766                 bool                    replace,
767                 CacheItemRemovedReason  removedReason,
768                 out object              valueOld);
769
770         internal abstract long TrimIfNecessary(int percent);
771
772         internal abstract void EnableExpirationTimer(bool enable);
773
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).
778 #if USE_MEMORY_CACHE
779         private static bool _useMemoryCache;
780         private static volatile bool _useMemoryCacheInited;
781         internal static bool UseMemoryCache {
782             get {
783                 if (!_useMemoryCacheInited) {
784                     RegistryKey regKey = null;
785                     try {
786                         regKey = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\ASP.NET");
787                         if (regKey != null) {
788                             if ((int)regKey.GetValue("UseMemoryCache", 0)== 1) {
789                                 _useMemoryCache = true;
790                             }
791                         }
792                     }
793                     finally {
794                         if (regKey != null) {
795                             regKey.Close();
796                         }
797                     }
798                     _useMemoryCacheInited = true;
799                 }
800                 return _useMemoryCache;
801             }
802         }
803 #endif
804
805         // common implementation
806         static internal CacheInternal Create() {
807             CacheCommon cacheCommon = new CacheCommon();
808             CacheInternal cacheInternal;
809 #if USE_MEMORY_CACHE
810             if (UseMemoryCache) {
811                 cacheInternal = new MemCache(cacheCommon);
812                 cacheCommon.AddSRefTarget(cacheInternal);
813             }
814             else {
815 #endif
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
820                 numSubCaches = 1;
821                 numCPUs -= 1;
822                 while (numCPUs > 0) {
823                     numSubCaches <<= 1;
824                     numCPUs >>= 1;
825                 }
826                 if (numSubCaches == 1) {
827                     cacheInternal = new CacheSingle(cacheCommon, null, 0);
828                 }
829                 else {
830                     cacheInternal = new CacheMultiple(cacheCommon, numSubCaches);
831                 }
832 #if USE_MEMORY_CACHE
833             }
834 #endif
835             cacheCommon.SetCacheInternal(cacheInternal);
836             cacheCommon.ResetFromConfigSettings();
837
838             return cacheInternal;
839         }
840
841         protected CacheInternal(CacheCommon cacheCommon) {
842             _cacheCommon = cacheCommon;
843         }
844
845         protected virtual void Dispose(bool disposing) {
846             _cacheCommon.Dispose(disposing);
847         }
848
849         public void Dispose() {
850             _disposed = 1;
851             Dispose(true);
852             // no destructor, don't need it.
853             // System.GC.SuppressFinalize(this);
854         }
855
856         internal bool IsDisposed { get { return _disposed == 1; } }
857
858         internal void ReadCacheInternalConfig(CacheSection cacheSection) {
859             _cacheCommon.ReadCacheInternalConfig(cacheSection);
860         }
861
862         internal long TrimCache(int percent) {
863             return _cacheCommon.CacheManagerThread(percent);
864         }
865
866         internal Cache CachePublic {
867             get {return _cacheCommon._cachePublic;}
868         }
869
870         internal long EffectivePrivateBytesLimit {
871             get { return _cacheCommon._cacheMemoryStats.CacheSizePressure.MemoryLimit; }
872         }
873
874         internal long EffectivePercentagePhysicalMemoryLimit {
875             get { return _cacheCommon._cacheMemoryStats.TotalMemoryPressure.MemoryLimit; }
876         }
877
878         IEnumerator IEnumerable.GetEnumerator() {
879             return CreateEnumerator();
880         }
881
882         public IDictionaryEnumerator GetEnumerator() {
883             return CreateEnumerator();
884         }
885
886         internal object this[string key] {
887             get {
888                 return Get(key);
889             }
890         }
891
892         internal object Get(string key) {
893             return DoGet(false, key, CacheGetOptions.None);
894         }
895
896         internal object Get(string key, CacheGetOptions getOptions) {
897             return DoGet(false, key, getOptions);
898         }
899
900         internal object DoGet(bool isPublic, string key, CacheGetOptions getOptions) {
901             CacheEntry  entry;
902             CacheKey    cacheKey;
903             object      dummy;
904
905             cacheKey = new CacheKey(key, isPublic);
906             entry = UpdateCache(cacheKey, null, false, CacheItemRemovedReason.Removed, out dummy);
907             if (entry != null) {
908                 if ((getOptions & CacheGetOptions.ReturnCacheEntry) != 0) {
909                     return entry;
910                 }
911                 else {
912                     return entry.Value;
913                 }
914             }
915             else {
916                 return null;
917             }
918         }
919
920         internal void UtcInsert(string key, object value) {
921             DoInsert(false,
922                      key,
923                      value,
924                      null,
925                      Cache.NoAbsoluteExpiration,
926                      Cache.NoSlidingExpiration,
927                      CacheItemPriority.Default,
928                      null,
929                      true);
930
931         }
932
933         internal void UtcInsert(string key, object value, CacheDependency dependencies) {
934             DoInsert(false,
935                      key,
936                      value,
937                      dependencies,
938                      Cache.NoAbsoluteExpiration,
939                      Cache.NoSlidingExpiration,
940                      CacheItemPriority.Default,
941                      null,
942                      true);
943         }
944
945         internal void UtcInsert(
946                 string key,
947                 object value,
948                 CacheDependency dependencies,
949                 DateTime utcAbsoluteExpiration,
950                 TimeSpan slidingExpiration) {
951
952             DoInsert(false,
953                      key,
954                      value,
955                      dependencies,
956                      utcAbsoluteExpiration,
957                      slidingExpiration,
958                      CacheItemPriority.Default,
959                      null,
960                      true);
961         }
962
963         internal void UtcInsert(
964                 string key,
965                 object value,
966                 CacheDependency dependencies,
967                 DateTime utcAbsoluteExpiration,
968                 TimeSpan slidingExpiration,
969                 CacheItemPriority priority,
970                 CacheItemRemovedCallback onRemoveCallback) {
971
972             DoInsert(false,
973                      key,
974                      value,
975                      dependencies,
976                      utcAbsoluteExpiration,
977                      slidingExpiration,
978                      priority,
979                      onRemoveCallback,
980                      true);
981         }
982
983         internal object UtcAdd(
984                 string key,
985                 object value,
986                 CacheDependency dependencies,
987                 DateTime utcAbsoluteExpiration,
988                 TimeSpan slidingExpiration,
989                 CacheItemPriority priority,
990                 CacheItemRemovedCallback onRemoveCallback) {
991
992             return DoInsert(
993                         false,
994                         key,
995                         value,
996                         dependencies,
997                         utcAbsoluteExpiration,
998                         slidingExpiration,
999                         priority,
1000                         onRemoveCallback,
1001                         false);
1002
1003         }
1004
1005         internal object DoInsert(
1006                 bool isPublic,
1007                 string key,
1008                 object value,
1009                 CacheDependency dependencies,
1010                 DateTime utcAbsoluteExpiration,
1011                 TimeSpan slidingExpiration,
1012                 CacheItemPriority priority,
1013                 CacheItemRemovedCallback onRemoveCallback,
1014                 bool replace) {
1015
1016
1017             /*
1018              * If we throw an exception, prevent a leak by a user who
1019              * writes the following:
1020              *
1021              *     Cache.Insert(key, value, new CacheDependency(file));
1022              */
1023             using (dependencies) {
1024                 CacheEntry      entry;
1025                 object          dummy;
1026
1027                 entry = new CacheEntry(
1028                         key,
1029                         value,
1030                         dependencies,
1031                         onRemoveCallback,
1032                         utcAbsoluteExpiration,
1033                         slidingExpiration,
1034                         priority,
1035                         isPublic);
1036
1037                 entry = UpdateCache(entry, entry, replace, CacheItemRemovedReason.Removed, out dummy);
1038
1039                 /*
1040                  * N.B. A set can fail if two or more threads set the same key
1041                  * at the same time.
1042                  */
1043 #if DBG
1044                 if (replace) {
1045                     string yesno = (entry != null) ? "succeeded" : "failed";
1046                     Debug.Trace("CacheAPIInsert", "Cache.Insert " + yesno + ": " + key);
1047                 }
1048                 else {
1049                     if (entry == null) {
1050                         Debug.Trace("CacheAPIAdd", "Cache.Add added new item: " + key);
1051                     }
1052                     else {
1053                         Debug.Trace("CacheAPIAdd", "Cache.Add returned existing item: " + key);
1054                     }
1055                 }
1056 #endif
1057
1058                 if (entry != null) {
1059                     return entry.Value;
1060                 }
1061                 else {
1062                     return null;
1063                 }
1064             }
1065         }
1066
1067         internal object Remove(string key) {
1068             CacheKey cacheKey = new CacheKey(key, false);
1069             return DoRemove(cacheKey, CacheItemRemovedReason.Removed);
1070         }
1071
1072         internal object Remove(CacheKey cacheKey, CacheItemRemovedReason reason)  {
1073             return DoRemove(cacheKey, reason);
1074         }
1075
1076         /*
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.
1080          *
1081          * @param key The key for the item.
1082          * @exception ArgumentException
1083          */
1084         internal object DoRemove(CacheKey cacheKey, CacheItemRemovedReason reason)  {
1085             object      valueOld;
1086
1087             UpdateCache(cacheKey, null, true, reason, out valueOld);
1088
1089 #if DBG
1090             if (valueOld != null) {
1091                 Debug.Trace("CacheAPIRemove", "Cache.Remove succeeded, reason=" + reason + ": " + cacheKey);
1092             }
1093             else {
1094                 Debug.Trace("CacheAPIRemove", "Cache.Remove failed, reason=" + reason + ": " + cacheKey);
1095             }
1096 #endif
1097
1098             return valueOld;
1099         }
1100     }
1101
1102     sealed class CacheKeyComparer : IEqualityComparer  {
1103         static CacheKeyComparer    s_comparerInstance;
1104
1105         static internal CacheKeyComparer GetInstance() {
1106             if (s_comparerInstance == null) {
1107                 s_comparerInstance = new CacheKeyComparer();
1108             }
1109
1110             return s_comparerInstance;
1111         }
1112
1113         private CacheKeyComparer()
1114         {
1115         }
1116
1117         bool IEqualityComparer.Equals(Object x, Object y)
1118         {
1119             return Compare(x, y) == 0;
1120         }
1121
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) {
1126             CacheKey  a, b;
1127
1128             Debug.Assert(x != null && x is CacheKey);
1129             Debug.Assert(y != null && y is CacheKey);
1130
1131             a = (CacheKey) x;
1132             b = (CacheKey) y;
1133
1134             if (a.IsPublic) {
1135                 if (b.IsPublic) {
1136                     return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1137                 }
1138                 else {
1139                     return 1;
1140                 }
1141             }
1142             else {
1143                 if (!b.IsPublic) {
1144                     return String.Compare(a.Key, b.Key, StringComparison.Ordinal);
1145                 }
1146                 else {
1147                     return -1;
1148                 }
1149             }
1150         }
1151         // Returns a hash code for the given object.
1152         //
1153         int IEqualityComparer.GetHashCode(Object obj) {
1154             Debug.Assert(obj != null && obj is CacheKey);
1155
1156             CacheKey cacheKey = (CacheKey) obj;
1157
1158             return cacheKey.GetHashCode();
1159         }
1160     }
1161
1162     /*
1163      * The cache.
1164      */
1165     sealed class CacheSingle : CacheInternal {
1166         // cache stats
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;
1170
1171
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 */
1184
1185         /*
1186          * Constructs a new Cache.
1187          */
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 });
1197         }
1198
1199         /*
1200          * Dispose the cache.
1201          */
1202         protected override void Dispose(bool disposing) {
1203             if (disposing) {
1204                 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
1205                     if (_expires != null) {
1206                         _expires.EnableExpirationTimer(false);
1207                     }
1208
1209                     // close all items
1210                     CacheEntry[] entries = null;
1211
1212                     lock (_lock) {
1213                         entries = new CacheEntry[_entries.Count];
1214                         int i = 0;
1215                         foreach (DictionaryEntry d in _entries) {
1216                             entries[i++] = (CacheEntry) d.Value;
1217                         }
1218                     }
1219
1220                     foreach (CacheEntry entry in entries) {
1221                         Remove(entry, CacheItemRemovedReason.Removed);
1222                     }
1223
1224                     // force any waiters to complete their waits. Note
1225                     // that the insert block cannot be reacquired, as UseInsertBlock
1226                     // checks the _disposed field.
1227                     _insertBlock.Set();
1228
1229                     // release the block, causing it to be disposed when there
1230                     // are no more callers.
1231                     ReleaseInsertBlock();
1232
1233                     Debug.Trace("CacheDispose", "Cache disposed");
1234                 }
1235             }
1236
1237             base.Dispose(disposing);
1238         }
1239
1240         // Get the insert block manual reset event if it has not been disposed.
1241         ManualResetEvent UseInsertBlock() {
1242             for (;;) {
1243                 if (_disposed == 1)
1244                     return null;
1245
1246                 int n = _insertBlockCalls;
1247                 if (n < 0) {
1248                     return null;
1249                 }
1250
1251                 if (Interlocked.CompareExchange(ref _insertBlockCalls, n + 1, n) == n) {
1252                     return _insertBlock;
1253                 }
1254             }
1255         }
1256
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;
1263
1264                 // now close
1265                 e.Close();
1266             }
1267         }
1268
1269         // Set the insert block event.
1270         void SetInsertBlock() {
1271             ManualResetEvent e = null;
1272             try {
1273                 e = UseInsertBlock();
1274                 if (e != null) {
1275                     e.Set();
1276                 }
1277             }
1278             finally {
1279                 if (e != null) {
1280                     ReleaseInsertBlock();
1281                 }
1282             }
1283         }
1284
1285         // Reset the insert block event.
1286         void ResetInsertBlock() {
1287             ManualResetEvent e = null;
1288             try {
1289                 e = UseInsertBlock();
1290                 if (e != null) {
1291                     e.Reset();
1292                 }
1293             }
1294             finally {
1295                 if (e != null) {
1296                     ReleaseInsertBlock();
1297                 }
1298             }
1299         }
1300
1301         // Wait on the insert block event.
1302         bool WaitInsertBlock() {
1303             bool signaled = false;
1304             ManualResetEvent e = null;
1305             try {
1306                 e = UseInsertBlock();
1307                 if (e != null) {
1308                     Debug.Trace("CacheMemoryTrimInsertBlock", "WaitInsertBlock: Cache " + _iSubCache + ": _useInsertBlock=true");
1309                     signaled = e.WaitOne(INSERT_BLOCK_WAIT, false);
1310                     Debug.Trace("CacheMemoryTrimInsertBlock", "Done waiting");
1311                 }
1312             }
1313             finally {
1314                 if (e != null) {
1315                     ReleaseInsertBlock();
1316                 }
1317             }
1318
1319             return signaled;
1320         }
1321
1322         internal void BlockInsertIfNeeded() {
1323             if (_cacheCommon._cacheMemoryStats.IsAboveHighPressure()) {
1324                 Debug.Trace("CacheMemoryTrimInsertBlock", "BlockInsertIfNeeded: Cache " + _iSubCache + ": _useInsertBlock=true");
1325                 _useInsertBlock = true;
1326                 ResetInsertBlock();
1327             }
1328         }
1329
1330         internal void UnblockInsert() {
1331             if (_useInsertBlock) {
1332                 _useInsertBlock = false;
1333                 SetInsertBlock();
1334                 Debug.Trace("CacheMemoryTrimInsertBlock", "UnblockInsert: Cache " + _iSubCache + ": _useInsertBlock=false");
1335             }
1336         }
1337
1338
1339         internal override int PublicCount {
1340             get {return _publicCount;}
1341         }
1342
1343         internal override long TotalCount {
1344             get {return _totalCount;}
1345         }
1346
1347         internal override IDictionaryEnumerator CreateEnumerator() {
1348             Hashtable h = new Hashtable(_publicCount);
1349             DateTime utcNow = DateTime.UtcNow;
1350
1351             lock (_lock) {
1352                 foreach (DictionaryEntry d in _entries) {
1353                     CacheEntry entry = (CacheEntry) d.Value;
1354
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;
1361                     }
1362                 }
1363             }
1364
1365             return h.GetEnumerator();
1366         }
1367
1368         /*
1369          * Performs all operations on the cache, with the
1370          * exception of Clear. The arguments indicate the type of operation:
1371          *
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.
1376          */
1377         internal override CacheEntry UpdateCache(
1378                 CacheKey                cacheKey,
1379                 CacheEntry              newEntry,
1380                 bool                    replace,
1381                 CacheItemRemovedReason  removedReason,
1382                 out object              valueOld)
1383         {
1384             CacheEntry              entry = null;
1385             CacheEntry              oldEntry = null;
1386             bool                    expired = false;
1387             DateTime                utcNow;
1388             CacheDependency         newEntryDependency = null;
1389             bool                    isGet, isAdd;
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;
1396
1397             valueOld = null;
1398             isGet = !replace && newEntry == null;
1399             isAdd = !replace && newEntry != null;
1400
1401             /*
1402              * Perform update of cache data structures in a series to
1403              * avoid overlapping locks.
1404              *
1405              * First, update the hashtable. The hashtable is the place
1406              * that guarantees what is in or out of the cache.
1407              *
1408              * Loop here to remove expired items in a Get or Add, where
1409              * we can't otherwise delete an item.
1410              */
1411             for (;;) {
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;
1416                 }
1417
1418                 entry = null;
1419                 utcNow = DateTime.UtcNow;
1420
1421                 if (_useInsertBlock && newEntry != null && newEntry.HasUsage() /* HasUsage() means it's not NonRemovable */) {
1422                     bool insertBlockReleased = WaitInsertBlock();
1423
1424 #if DBG
1425                     if (!insertBlockReleased) {
1426                         Debug.Trace("CacheUpdateWaitFailed", "WaitInsertBlock failed.");
1427                     }
1428 #endif
1429                 }
1430
1431                 // the _entries hashtable supports multiple readers or one writer
1432                 bool isLockEntered = false;
1433                 if (!isGet) {
1434                     Monitor.Enter(_lock, ref isLockEntered);
1435                 }
1436                 try {
1437                     entry = (CacheEntry) _entries[cacheKey];
1438                     Debug.Trace("CacheUpdate", "Entry " + ((entry != null) ? "found" : "not found") + "in hashtable: " + cacheKey);
1439
1440                     if (entry != null) {
1441                         entryState = entry.State;
1442
1443                         // If isGet == true, we are not hold any lock and so entryState can be anything
1444                         Debug.Assert(
1445                             isGet ||
1446                             entryState == CacheEntry.EntryState.AddingToCache ||
1447                             entryState == CacheEntry.EntryState.AddedToCache,
1448                             "entryState == CacheEntry.EntryState.AddingToCache || entryState == CacheEntry.EntryState.AddedToCache");
1449
1450                         expired = (_cacheCommon._enableExpiration) && (entry.UtcExpires < utcNow);
1451                         if (expired) {
1452                             if (isGet) {
1453                                 /*
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.
1457                                  */
1458                                 if (entryState == CacheEntry.EntryState.AddedToCache) {
1459                                     removeExpired = true;
1460                                     continue;
1461                                 }
1462
1463                                 entry = null;
1464                             }
1465                             else {
1466                                 /*
1467                                  * If it's a call to Add, replace the item
1468                                  * when it has expired.
1469                                  */
1470                                 replace = true;
1471
1472                                 /*
1473                                  * Change the removed reason.
1474                                  */
1475                                 removedReason = CacheItemRemovedReason.Expired;
1476                             }
1477                         }
1478                         else {
1479                             updateExpires = (_cacheCommon._enableExpiration) && (entry.SlidingExpiration > TimeSpan.Zero);
1480                         }
1481                     }
1482
1483                     /*
1484                      * Avoid running unnecessary code in a Get request by this simple test:
1485                      */
1486                     if (!isGet) {
1487                         /*
1488                          * Remove an item from the hashtable.
1489                          */
1490                         if (replace && entry != null) {
1491                             bool doRemove = (entryState != CacheEntry.EntryState.AddingToCache);
1492                             if (doRemove) {
1493                                 oldEntry = entry;
1494
1495                                 oldEntry.State = CacheEntry.EntryState.RemovingFromCache;
1496
1497                                 _entries.Remove(oldEntry);
1498                                 Debug.Trace("CacheUpdate", "Entry removed from hashtable: " + cacheKey);
1499                             }
1500                             else {
1501                                 /*
1502                                  * If we're removing and couldn't remove the old item
1503                                  * because its state was AddingToCache, return null
1504                                  * to indicate failure.
1505                                  */
1506                                 if (newEntry == null) {
1507                                     Debug.Trace("CacheUpdate", "Removal from hashtable failed: " + cacheKey);
1508                                     entry = null;
1509                                 }
1510                             }
1511                         }
1512
1513                         /*
1514                          * Add an item to the hashtable.
1515                          */
1516                         if (newEntry != null) {
1517                             bool doAdd = true;
1518
1519                             if (entry != null) {
1520                                 if (oldEntry == null) {
1521                                     /*
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.
1526                                     */
1527                                     doAdd = false;
1528                                     newEntryRemovedReason = CacheItemRemovedReason.Removed;
1529                                 }
1530
1531 #if DBG
1532                                 if (!doAdd) {
1533                                     Debug.Trace("CacheUpdate", "Insertion into hashtable failed because old entry was not removed: " + cacheKey);
1534                                 }
1535 #endif
1536                             }
1537
1538
1539                             if (doAdd) {
1540                                 /* non-definitive check */
1541                                 newEntryDependency = newEntry.Dependency;
1542                                 if (newEntryDependency != null) {
1543                                     if (newEntryDependency.HasChanged) {
1544                                         doAdd = false;
1545                                         newEntryRemovedReason = CacheItemRemovedReason.DependencyChanged;
1546                                     }
1547
1548 #if DBG
1549                                     if (!doAdd) {
1550                                         Debug.Trace("CacheUpdate", "Insertion into hashtable failed because dependency changed: " + cacheKey);
1551                                     }
1552 #endif
1553                                 }
1554                             }
1555
1556                             if (doAdd) {
1557                                 newEntry.State = CacheEntry.EntryState.AddingToCache;
1558                                 _entries.Add(newEntry, newEntry);
1559
1560                                 /*
1561                                  * If this is an Add operation, indicate success
1562                                  * by returning null.
1563                                  */
1564                                 if (isAdd) {
1565                                     Debug.Assert(entry == null || expired, "entry == null || expired");
1566                                     entry = null;
1567                                 }
1568                                 else {
1569                                     /*
1570                                      * Indicate success by returning the inserted entry.
1571                                      */
1572                                     entry = newEntry;
1573                                 }
1574
1575                                 Debug.Trace("CacheUpdate", "Entry added to hashtable: " + cacheKey);
1576                             }
1577                             else {
1578                                 if (!isAdd) {
1579                                     /*
1580                                      * If we failed for an Insert, indicate failure by returning null.
1581                                      */
1582                                     entry = null;
1583                                     newEntryNeedsClose = true;
1584                                 }
1585                                 else {
1586                                     /*
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.
1591                                      */
1592                                     newEntryNeedsClose = (entry == null);
1593                                 }
1594
1595                                 /*
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
1599                                  * can close it.
1600                                  */
1601                                 if (!newEntryNeedsClose) {
1602                                     newEntry = null;
1603                                 }
1604
1605                             }
1606                         }
1607                     }
1608
1609                     break;
1610                 }
1611                 finally {
1612                     if (isLockEntered) {
1613                         Monitor.Exit(_lock);
1614                     }
1615                 }
1616             }
1617
1618             /*
1619              * Since we want Get to be fast, check here for a get without
1620              * alteration to cache.
1621              */
1622             if (isGet) {
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);
1628                         }
1629                     }
1630
1631                     UtcUpdateUsageRecursive(entry, utcNow);
1632                 }
1633
1634                 if (cacheKey.IsPublic) {
1635                     PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_RATIO_BASE);
1636                     if (entry != null) {
1637                         PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_HITS);
1638                     }
1639                     else {
1640                         PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_MISSES);
1641                     }
1642                 }
1643
1644                 PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_RATIO_BASE);
1645                 if (entry != null) {
1646                     PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_HITS);
1647                 }
1648                 else {
1649                     PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_MISSES);
1650                 }
1651
1652 #if DBG
1653                 if (entry != null) {
1654                     Debug.Trace("CacheUpdate", "Cache hit: " + cacheKey);
1655                 }
1656                 else {
1657                     Debug.Trace("CacheUpdate", "Cache miss: " + cacheKey);
1658                 }
1659 #endif
1660
1661             }
1662             else {
1663                 int totalDelta = 0;
1664                 int publicDelta = 0;
1665                 int totalTurnover = 0;
1666                 int publicTurnover = 0;
1667
1668                 if (oldEntry != null) {
1669                     if (oldEntry.InExpires()) {
1670                         _expires.Remove(oldEntry);
1671                     }
1672
1673                     if (oldEntry.InUsage()) {
1674                         _usage.Remove(oldEntry);
1675                     }
1676
1677                     Debug.Assert(oldEntry.State == CacheEntry.EntryState.RemovingFromCache, "oldEntry.State == CacheEntry.EntryState.RemovingFromCache");
1678                     oldEntry.State = CacheEntry.EntryState.RemovedFromCache;
1679                     valueOld = oldEntry.Value;
1680
1681                     totalDelta--;
1682                     totalTurnover++;
1683                     if (oldEntry.IsPublic) {
1684                         publicDelta--;
1685                         publicTurnover++;
1686                     }
1687
1688 #if DBG
1689                     Debug.Trace("CacheUpdate", "Entry removed from cache, reason=" + removedReason + ": " + (CacheKey) oldEntry);
1690 #endif
1691                 }
1692
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);
1698                         newEntry = null;
1699                     }
1700                     else {
1701                         Debug.Assert(!newEntry.InExpires());
1702                         Debug.Assert(!newEntry.InUsage());
1703
1704                         if (_cacheCommon._enableExpiration && newEntry.HasExpiration()) {
1705                             _expires.Add(newEntry);
1706                         }
1707
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)) {
1713
1714                             _usage.Add(newEntry);
1715                         }
1716
1717                         newEntry.State = CacheEntry.EntryState.AddedToCache;
1718
1719                         Debug.Trace("CacheUpdate", "Entry added to cache: " + (CacheKey)newEntry);
1720
1721                         totalDelta++;
1722                         totalTurnover++;
1723                         if (newEntry.IsPublic) {
1724                             publicDelta++;
1725                             publicTurnover++;
1726                         }
1727                     }
1728                 }
1729
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);
1734                 }
1735
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();
1742
1743                     /*
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
1750                      * has changed.
1751                      */
1752                     if (newEntryDependency != null && newEntryDependency.HasChanged) {
1753                         Remove(newEntry, CacheItemRemovedReason.DependencyChanged);
1754                     }
1755                 }
1756                 
1757                 // update counts and counters
1758                 if (totalDelta == 1) {
1759                     Interlocked.Increment(ref _totalCount);
1760                     PerfCounters.IncrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1761                 }
1762                 else if (totalDelta == -1) {
1763                     Interlocked.Decrement(ref _totalCount);
1764                     PerfCounters.DecrementCounter(AppPerfCounter.TOTAL_CACHE_ENTRIES);
1765                 }
1766
1767                 if (publicDelta == 1) {
1768                     Interlocked.Increment(ref _publicCount);
1769                     PerfCounters.IncrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1770                 }
1771                 else if (publicDelta == -1) {
1772                     Interlocked.Decrement(ref _publicCount);
1773                     PerfCounters.DecrementCounter(AppPerfCounter.API_CACHE_ENTRIES);
1774                 }
1775
1776                 if (totalTurnover > 0) {
1777                     PerfCounters.IncrementCounterEx(AppPerfCounter.TOTAL_CACHE_TURNOVER_RATE, totalTurnover);
1778                 }
1779
1780                 if (publicTurnover > 0) {
1781                     PerfCounters.IncrementCounterEx(AppPerfCounter.API_CACHE_TURNOVER_RATE, publicTurnover);
1782                 }
1783             }
1784
1785             return entry;
1786         }
1787
1788         void UtcUpdateUsageRecursive(CacheEntry entry, DateTime utcNow) {
1789             CacheDependency dependency;
1790             CacheEntry[]    entries;
1791
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
1794             // update requests.
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) {
1800                         cacheSingle = this;
1801                     }
1802                     else {
1803                         cacheSingle = _cacheMultiple.GetCacheSingle(entry.Key.GetHashCode());
1804                     }
1805
1806                     cacheSingle._usage.Update(entry);
1807                 }
1808
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);
1815                         }
1816                     }
1817                 }
1818             }
1819         }
1820
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)
1824                 return 0;
1825
1826             int toTrim = 0;
1827             // do we need to drop a percentage of entries?
1828             if (percent > 0) {
1829                 toTrim = (int)(((long)_totalCount * (long)percent) / 100L);
1830             }
1831             // would this leave us above MAX_COUNT?
1832             int minTrim = _totalCount - MAX_COUNT;
1833             if (toTrim < minTrim) {
1834                 toTrim = minTrim;
1835             }
1836             // would this put us below MIN_COUNT?
1837             int maxTrim = _totalCount - MIN_COUNT;
1838             if (toTrim > maxTrim) {
1839                 toTrim = maxTrim;
1840             }
1841             // do we need to trim?
1842             if (toTrim <= 0 || HostingEnvironment.ShutdownInitiated) {
1843                 return 0;
1844             }
1845             
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;
1851
1852             try {
1853                 trimmedOrExpired = _expires.FlushExpiredItems(true);
1854                 if (trimmedOrExpired < toTrim) {
1855                     totalTrimmed = _usage.FlushUnderUsedItems(toTrim - trimmedOrExpired, ref publicEntriesTrimmed, ref ocEntriesTrimmed);
1856                     trimmedOrExpired += totalTrimmed;
1857                 }
1858                 
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);
1864                 }
1865             }
1866             catch {
1867             }
1868             
1869 #if DBG
1870             Debug.Trace("CacheMemory", "TrimIfNecessary: _iSubCache= " + _iSubCache 
1871                         + ", beginTotalCount=" + beginTotalCount
1872                         + ", endTotalCount=" + _totalCount
1873                         + ", percent=" + percent 
1874                         + ", trimmed=" + totalTrimmed);
1875 #endif
1876             return trimmedOrExpired;
1877         }
1878         
1879         internal override void EnableExpirationTimer(bool enable) {
1880             if (_expires != null) {
1881                 _expires.EnableExpirationTimer(enable);
1882             }
1883         }
1884     }
1885
1886     class CacheMultiple : CacheInternal {
1887         int             _disposed;
1888         DisposableGCHandleRef<CacheSingle>[] _cachesRefs;
1889         int             _cacheIndexMask;
1890
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;
1895
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));
1903             }
1904         }
1905
1906         protected override void Dispose(bool disposing) {
1907             if (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(); 
1914                     }
1915                 }
1916             }
1917
1918             base.Dispose(disposing);
1919         }
1920
1921         internal override int PublicCount {
1922             get {
1923                 int count = 0;
1924                 foreach (var cacheSingleRef in _cachesRefs) {
1925                     count += cacheSingleRef.Target.PublicCount;
1926                 }
1927
1928                 return count;
1929             }
1930         }
1931
1932         internal override long TotalCount {
1933             get {
1934                 long count = 0;
1935                 foreach (var cacheSingleRef in _cachesRefs) {
1936                     count += cacheSingleRef.Target.TotalCount;
1937                 }
1938
1939                 return count;
1940             }
1941         }
1942
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();
1947             }
1948
1949             return new AggregateEnumerator(enumerators);
1950         }
1951
1952         internal CacheSingle GetCacheSingle(int hashCode) {
1953             Debug.Assert(_cachesRefs != null && _cachesRefs.Length != 0);
1954             // Dev10 865907: Math.Abs throws OverflowException for Int32.MinValue
1955             if (hashCode < 0) {
1956                 hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode;
1957             }
1958             int index = (hashCode & _cacheIndexMask);
1959             Debug.Assert(_cachesRefs[index].Target != null);
1960             return _cachesRefs[index].Target;
1961         }
1962
1963         internal override CacheEntry UpdateCache(
1964                 CacheKey cacheKey,
1965                 CacheEntry newEntry,
1966                 bool replace,
1967                 CacheItemRemovedReason removedReason,
1968                 out object valueOld) {
1969
1970             int hashCode = cacheKey.Key.GetHashCode();
1971             CacheSingle cacheSingle = GetCacheSingle(hashCode);
1972             return cacheSingle.UpdateCache(cacheKey, newEntry, replace, removedReason, out valueOld);
1973         }
1974
1975         internal override long TrimIfNecessary(int percent) {
1976             long count = 0;
1977             foreach (var cacheSingleRef in _cachesRefs) {
1978                 count += cacheSingleRef.Target.TrimIfNecessary(percent);
1979             }
1980             return count;
1981         }
1982
1983         internal override void EnableExpirationTimer(bool enable) {
1984             foreach (var cacheSingleRef in _cachesRefs) {
1985                 cacheSingleRef.Target.EnableExpirationTimer(enable);
1986             }
1987         }
1988     }
1989
1990     class AggregateEnumerator : IDictionaryEnumerator {
1991         IDictionaryEnumerator []    _enumerators;
1992         int                         _iCurrent;
1993
1994         internal AggregateEnumerator(IDictionaryEnumerator [] enumerators) {
1995             _enumerators = enumerators;
1996         }
1997
1998         public bool MoveNext() {
1999             bool more;
2000
2001             for (;;) {
2002                 more = _enumerators[_iCurrent].MoveNext();
2003                 if (more)
2004                     break;
2005
2006                 if (_iCurrent == _enumerators.Length - 1)
2007                     break;
2008
2009                 _iCurrent++;
2010             }
2011
2012             return more;
2013         }
2014
2015         public void Reset() {
2016             for (int i = 0; i <= _iCurrent; i++) {
2017                 _enumerators[i].Reset();
2018             }
2019
2020             _iCurrent = 0;
2021         }
2022
2023         public Object Current {
2024             get {
2025                 return _enumerators[_iCurrent].Current;
2026             }
2027         }
2028
2029         public Object Key {
2030             get {
2031                 return _enumerators[_iCurrent].Key;
2032             }
2033         }
2034
2035         public Object Value {
2036             get {
2037                 return _enumerators[_iCurrent].Value;
2038             }
2039         }
2040
2041         public DictionaryEntry Entry {
2042             get {
2043                 return _enumerators[_iCurrent].Entry;
2044             }
2045         }
2046     }
2047 }
2048