[coop] Temporarily restore MonoThreadInfo when TLS destructor runs. Fixes #43099
[mono.git] / mcs / class / referencesource / System.Runtime.Caching / System / Caching / MemoryCacheStatistics.cs
1 // <copyright file="MemoryCacheStats.cs" company="Microsoft">
2 //   Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
3 // </copyright>
4 using System.Collections.Specialized;
5 using System.Configuration;
6 using System.Diagnostics;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Runtime.Caching.Configuration;
10 using System.Runtime.InteropServices;
11 using System.Security;
12 using System.Threading;
13
14 namespace System.Runtime.Caching {
15     internal sealed class MemoryCacheStatistics : IDisposable {
16         const int MEMORYSTATUS_INTERVAL_5_SECONDS = 5 * 1000;
17         const int MEMORYSTATUS_INTERVAL_30_SECONDS = 30 * 1000;
18
19         private int _configCacheMemoryLimitMegabytes;
20         private int _configPhysicalMemoryLimitPercentage;
21         private int _configPollingInterval;
22         private int _inCacheManagerThread;
23         private int _disposed;
24         private long _lastTrimCount;
25         private long _lastTrimDurationTicks; // used only for debugging
26         private int _lastTrimGen2Count;
27         private int _lastTrimPercent;
28         private DateTime _lastTrimTime;        
29         private int _pollingInterval;
30         private Timer _timer;
31         private Object _timerLock;
32         private long _totalCountBeforeTrim;
33
34         private CacheMemoryMonitor _cacheMemoryMonitor;
35         private MemoryCache _memoryCache;
36         private PhysicalMemoryMonitor _physicalMemoryMonitor;        
37
38         // private
39
40         private MemoryCacheStatistics() {
41             //hide default ctor
42         }
43
44         private void AdjustTimer() {
45             lock (_timerLock) {
46
47                 if (_timer == null)
48                     return;
49
50                 // the order of these if statements is important
51
52                 // When above the high pressure mark, interval should be 5 seconds or less
53                 if (_physicalMemoryMonitor.IsAboveHighPressure() || _cacheMemoryMonitor.IsAboveHighPressure()) {
54                     if (_pollingInterval > MEMORYSTATUS_INTERVAL_5_SECONDS) {
55                         _pollingInterval = MEMORYSTATUS_INTERVAL_5_SECONDS;
56                         _timer.Change(_pollingInterval, _pollingInterval);
57                     }
58                     return;
59                 }
60
61                 // When above half the low pressure mark, interval should be 30 seconds or less
62                 if ((_cacheMemoryMonitor.PressureLast > _cacheMemoryMonitor.PressureLow / 2)
63                     || (_physicalMemoryMonitor.PressureLast > _physicalMemoryMonitor.PressureLow / 2)) {
64                     // DevDivBugs 104034: allow interval to fall back down when memory pressure goes away
65                     int newPollingInterval = Math.Min(_configPollingInterval, MEMORYSTATUS_INTERVAL_30_SECONDS);
66                     if (_pollingInterval != newPollingInterval) {
67                         _pollingInterval = newPollingInterval;
68                         _timer.Change(_pollingInterval, _pollingInterval);
69                     }
70                     return;
71                 }
72
73                 // there is no pressure, interval should be the value from config
74                 if (_pollingInterval != _configPollingInterval) {
75                     _pollingInterval = _configPollingInterval;
76                     _timer.Change(_pollingInterval, _pollingInterval);
77                 }
78             }
79         }
80
81         // timer callback
82         private void CacheManagerTimerCallback(object state) {
83             CacheManagerThread(0);
84         }
85
86         private int GetPercentToTrim() {
87             int gen2Count = GC.CollectionCount(2);
88             // has there been a Gen 2 Collection since the last trim?
89             if (gen2Count != _lastTrimGen2Count) {
90                 return Math.Max(_physicalMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent), _cacheMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent));
91             }
92             else {
93                 return 0;
94             }
95         }
96
97         private void InitializeConfiguration(NameValueCollection config) {
98             MemoryCacheElement element = null;
99             if (!_memoryCache.ConfigLess) {
100                 MemoryCacheSection section = ConfigurationManager.GetSection("system.runtime.caching/memoryCache") as MemoryCacheSection;
101                 if (section != null) {
102                     element = section.NamedCaches[_memoryCache.Name];
103                 }
104             }
105
106             if (element != null) {
107                 _configCacheMemoryLimitMegabytes = element.CacheMemoryLimitMegabytes;
108                 _configPhysicalMemoryLimitPercentage = element.PhysicalMemoryLimitPercentage;
109                 double milliseconds = element.PollingInterval.TotalMilliseconds;
110                 _configPollingInterval = (milliseconds < (double)Int32.MaxValue) ? (int) milliseconds : Int32.MaxValue;
111             }
112             else {
113                 _configPollingInterval = ConfigUtil.DefaultPollingTimeMilliseconds;
114                 _configCacheMemoryLimitMegabytes = 0;
115                 _configPhysicalMemoryLimitPercentage = 0;
116             }
117
118             if (config != null) {
119                 _configPollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
120                 _configCacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
121                 _configPhysicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
122             }
123         }
124
125         private void InitDisposableMembers() {
126             bool dispose = true;
127             try {
128                 _cacheMemoryMonitor = new CacheMemoryMonitor(_memoryCache, _configCacheMemoryLimitMegabytes);
129                 _timer = new Timer(new TimerCallback(CacheManagerTimerCallback), null, _configPollingInterval, _configPollingInterval);
130                 dispose = false;
131             }
132             finally {
133                 if (dispose) {
134                     Dispose();
135                 }
136             }
137         }
138
139         private void SetTrimStats(long trimDurationTicks, long totalCountBeforeTrim, long trimCount) {
140             _lastTrimDurationTicks = trimDurationTicks;
141
142             int gen2Count = GC.CollectionCount(2);
143             // has there been a Gen 2 Collection since the last trim?
144             if (gen2Count != _lastTrimGen2Count) {
145                 _lastTrimTime = DateTime.UtcNow;
146                 _totalCountBeforeTrim = totalCountBeforeTrim;
147                 _lastTrimCount = trimCount;
148             }
149             else {
150                 // we've done multiple trims between Gen 2 collections, so only add to the trim count
151                 _lastTrimCount += trimCount;
152             }
153             _lastTrimGen2Count = gen2Count;
154
155             _lastTrimPercent = (int)((_lastTrimCount * 100L) / _totalCountBeforeTrim);
156         }
157
158         private void Update() {
159             _physicalMemoryMonitor.Update();
160             _cacheMemoryMonitor.Update();
161         }
162
163
164
165         // public/internal
166
167         internal long CacheMemoryLimit {
168             get {
169                 return _cacheMemoryMonitor.MemoryLimit;
170             }
171         }
172         
173         internal long PhysicalMemoryLimit {
174             get {
175                 return _physicalMemoryMonitor.MemoryLimit;
176             }
177         }
178
179         internal TimeSpan PollingInterval {
180             get {
181                 return TimeSpan.FromMilliseconds(_configPollingInterval);
182             }
183         }
184
185         internal MemoryCacheStatistics(MemoryCache memoryCache, NameValueCollection config) {
186             _memoryCache = memoryCache;
187             _lastTrimGen2Count = -1;
188             _lastTrimTime = DateTime.MinValue;
189             _timerLock = new Object();
190             InitializeConfiguration(config);
191             _pollingInterval = _configPollingInterval;
192             _physicalMemoryMonitor = new PhysicalMemoryMonitor(_configPhysicalMemoryLimitPercentage);
193             InitDisposableMembers();
194         }
195
196         [SecuritySafeCritical]
197         internal long CacheManagerThread(int minPercent) {
198             if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
199                 return 0;
200             try {
201                 if (_disposed == 1) {
202                     return 0;
203                 }
204 #if DBG
205                 Dbg.Trace("MemoryCacheStats", "**BEG** CacheManagerThread " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
206 #endif
207                 // The timer thread must always call Update so that the CacheManager
208                 // knows the size of the cache.
209                 Update();
210                 AdjustTimer();
211
212                 int percent = Math.Max(minPercent, GetPercentToTrim());
213                 long beginTotalCount = _memoryCache.GetCount();
214                 Stopwatch sw = Stopwatch.StartNew();
215                 long trimmedOrExpired = _memoryCache.Trim(percent);
216                 sw.Stop();
217                 // 1) don't update stats if the trim happend because MAX_COUNT was exceeded
218                 // 2) don't update stats unless we removed at least one entry
219                 if (percent > 0 && trimmedOrExpired > 0) {
220                     SetTrimStats(sw.Elapsed.Ticks, beginTotalCount, trimmedOrExpired);
221                 }
222
223 #if DBG
224                 Dbg.Trace("MemoryCacheStats", "**END** CacheManagerThread: "
225                             + ", percent=" + percent
226                             + ", beginTotalCount=" + beginTotalCount
227                             + ", trimmed=" + trimmedOrExpired
228                             + ", Milliseconds=" + sw.ElapsedMilliseconds);
229 #endif
230
231 #if PERF
232                 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
233                                                     + " minPercent= " + minPercent
234                                                     + ", percent= " + percent
235                                                     + ", beginTotalCount=" + beginTotalCount
236                                                     + ", trimmed=" + trimmedOrExpired
237                                                     + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
238 #endif
239                 return trimmedOrExpired;
240             }
241             finally {
242                 Interlocked.Exchange(ref _inCacheManagerThread, 0);
243             }
244         }
245
246         public void Dispose() {
247             if (Interlocked.Exchange(ref _disposed, 1) == 0) {
248                 lock (_timerLock) {
249                     Timer timer = _timer;
250                     if (timer != null && Interlocked.CompareExchange(ref _timer, null, timer) == timer) {
251                         timer.Dispose();
252                         Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
253                     }
254                 }
255                 while (_inCacheManagerThread != 0) {
256                     Thread.Sleep(100);
257                 }
258                 if (_cacheMemoryMonitor != null) {
259                     _cacheMemoryMonitor.Dispose();
260                 }
261                 // Don't need to call GC.SuppressFinalize(this) for sealed types without finalizers.
262             }
263         }
264
265         [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Grandfathered suppression from original caching code checkin")]
266         internal void UpdateConfig(NameValueCollection config) {
267             int pollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
268             int cacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
269             int physicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
270
271             if (pollingInterval != _configPollingInterval) {
272                 lock (_timerLock) {
273                     _configPollingInterval = pollingInterval;
274                 }
275             }
276
277             if (cacheMemoryLimitMegabytes == _configCacheMemoryLimitMegabytes 
278                 && physicalMemoryLimitPercentage == _configPhysicalMemoryLimitPercentage) {
279                 return;
280             }
281             
282             try {
283                 try {
284                 }
285                 finally {
286                     // prevent ThreadAbortEx from interrupting
287                     while (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0) {
288                         Thread.Sleep(100);
289                     }
290                 }
291                 if (_disposed == 0) {
292                     if (cacheMemoryLimitMegabytes != _configCacheMemoryLimitMegabytes) {
293                         _cacheMemoryMonitor.SetLimit(cacheMemoryLimitMegabytes);
294                         _configCacheMemoryLimitMegabytes = cacheMemoryLimitMegabytes;
295                     }
296                     if (physicalMemoryLimitPercentage != _configPhysicalMemoryLimitPercentage) {
297                         _physicalMemoryMonitor.SetLimit(physicalMemoryLimitPercentage);
298                         _configPhysicalMemoryLimitPercentage = physicalMemoryLimitPercentage;
299                     }
300                 }
301             }
302             finally {
303                 Interlocked.Exchange(ref _inCacheManagerThread, 0);
304             }
305         }
306     }
307 }
308