[bcl] Updates referencesource to 4.7.1
[mono-project.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Common / QueryCache / QueryCacheManager.cs
blobe4b471c2976e33784de8dfe980b2854a6358146e
1 //------------------------------------------------------------------------------
2 // <copyright file="QueryCacheManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //------------------------------------------------------------------------------
10 namespace System.Data.Common.QueryCache
12 using System;
13 using System.Collections.Generic;
14 using System.Data.EntityClient;
15 using System.Data.Metadata.Edm;
16 using System.Data.Objects.Internal;
17 using System.Diagnostics;
18 using System.Threading;
19 using System.Data.Common.Internal.Materialization;
20 using System.Data.Entity.Util;
22 /// <summary>
23 /// Provides Query Execution Plan Caching Service
24 /// </summary>
25 /// <remarks>
26 /// Thread safe.
27 /// Dispose <b>must</b> be called as there is no finalizer for this class
28 /// </remarks>
29 internal class QueryCacheManager : IDisposable
31 #region Constants/Default values for configuration parameters
33 /// <summary>
34 /// Default high mark for starting sweeping process
35 /// default value: 80% of MaxNumberOfEntries
36 /// </summary>
37 const float DefaultHighMarkPercentageFactor = 0.8f; // 80% of MaxLimit
39 /// <summary>
40 /// Recycler timer period
41 /// </summary>
42 const int DefaultRecyclerPeriodInMilliseconds = 60 * 1000;
44 #endregion
46 #region Fields
48 /// <summary>
49 /// cache lock object
50 /// </summary>
51 private readonly object _cacheDataLock = new object();
53 /// <summary>
54 /// cache data
55 /// </summary>
56 private readonly Dictionary<QueryCacheKey, QueryCacheEntry> _cacheData = new Dictionary<QueryCacheKey, QueryCacheEntry>(32);
58 /// <summary>
59 /// soft maximum number of entries in the cache
60 /// </summary>
61 private readonly int _maxNumberOfEntries;
63 /// <summary>
64 /// high mark of the number of entries to trigger the sweeping process
65 /// </summary>
66 private readonly int _sweepingTriggerHighMark;
68 /// <summary>
69 /// Eviction timer
70 /// </summary>
71 private readonly EvictionTimer _evictionTimer;
73 #endregion
75 #region Construction and Initialization
77 /// <summary>
78 /// Constructs a new Query Cache Manager instance, with default values for all 'configurable' parameters.
79 /// </summary>
80 /// <returns>A new instance of <see cref="QueryCacheManager"/> configured with default entry count, load factor and recycle period</returns>
81 internal static QueryCacheManager Create()
83 return new QueryCacheManager(AppSettings.QueryCacheSize, DefaultHighMarkPercentageFactor, DefaultRecyclerPeriodInMilliseconds);
86 /// <summary>
87 /// Cache Constructor
88 /// </summary>
89 /// <param name="maximumSize">
90 /// Maximum number of entries that the cache should contain.
91 /// </param>
92 /// <param name="loadFactor">
93 /// The number of entries that must be present, as a percentage, before entries should be removed
94 /// according to the eviction policy.
95 /// Must be greater than 0 and less than or equal to 1.0
96 /// </param>
97 /// <param name="recycleMillis">
98 /// The interval, in milliseconds, at which the number of entries will be compared to the load factor
99 /// and eviction carried out if necessary.
100 /// </param>
101 private QueryCacheManager(int maximumSize, float loadFactor, int recycleMillis)
103 Debug.Assert(maximumSize > 0, "Maximum size must be greater than zero");
104 Debug.Assert(loadFactor > 0 && loadFactor <= 1, "Load factor must be greater than 0.0 and less than or equal to 1.0");
105 Debug.Assert(recycleMillis >= 0, "Recycle period in milliseconds must not be negative");
108 // Load hardcoded defaults
110 this._maxNumberOfEntries = maximumSize;
113 // set sweeping high mark trigger value
115 this._sweepingTriggerHighMark = (int)(_maxNumberOfEntries * loadFactor);
118 // Initialize Recycler
120 this._evictionTimer = new EvictionTimer(this, recycleMillis);
123 #endregion
125 #region 'External' interface
126 /// <summary>
127 /// Adds new entry to the cache using "abstract" cache context and
128 /// value; returns an existing entry if the key is already in the
129 /// dictionary.
130 /// </summary>
131 /// <param name="inQueryCacheEntry"></param>
132 /// <param name="outQueryCacheEntry">
133 /// The existing entry in the dicitionary if already there;
134 /// inQueryCacheEntry if none was found and inQueryCacheEntry
135 /// was added instead.
136 /// </param>
137 /// <returns>true if the output entry was already found; false if it had to be added.</returns>
138 internal bool TryLookupAndAdd(QueryCacheEntry inQueryCacheEntry, out QueryCacheEntry outQueryCacheEntry)
140 Debug.Assert(null != inQueryCacheEntry, "qEntry must not be null");
142 outQueryCacheEntry = null;
144 lock (_cacheDataLock)
146 if (!_cacheData.TryGetValue(inQueryCacheEntry.QueryCacheKey, out outQueryCacheEntry))
149 // add entry to cache data
151 _cacheData.Add(inQueryCacheEntry.QueryCacheKey, inQueryCacheEntry);
152 if (_cacheData.Count > _sweepingTriggerHighMark)
154 _evictionTimer.Start();
157 return false;
159 else
161 outQueryCacheEntry.QueryCacheKey.UpdateHit();
163 return true;
168 /// <summary>
169 /// Lookup service for a cached value.
170 /// </summary>
171 internal bool TryCacheLookup<TK, TE>(TK key, out TE value)
172 where TK : QueryCacheKey
174 Debug.Assert(null != key, "key must not be null");
176 value = default(TE);
179 // invoke internal lookup
181 QueryCacheEntry qEntry = null;
182 bool bHit = TryInternalCacheLookup(key, out qEntry);
185 // if it is a hit, 'extract' the entry strong type cache value
187 if (bHit)
189 value = (TE)qEntry.GetTarget();
192 return bHit;
195 /// <summary>
196 /// Clears the Cache
197 /// </summary>
198 internal void Clear()
200 lock (_cacheDataLock)
202 _cacheData.Clear();
205 #endregion
207 #region Private Members
209 /// <summary>
210 /// lookup service
211 /// </summary>
212 /// <param name="queryCacheKey"></param>
213 /// <param name="queryCacheEntry"></param>
214 /// <returns>true if cache hit, false if cache miss</returns>
215 private bool TryInternalCacheLookup( QueryCacheKey queryCacheKey, out QueryCacheEntry queryCacheEntry )
217 Debug.Assert(null != queryCacheKey, "queryCacheKey must not be null");
219 queryCacheEntry = null;
221 bool bHit = false;
224 // lock the cache for the minimal possible period
226 lock (_cacheDataLock)
228 bHit = _cacheData.TryGetValue(queryCacheKey, out queryCacheEntry);
232 // if cache hit
234 if (bHit)
237 // update hit mark in cache key
239 queryCacheEntry.QueryCacheKey.UpdateHit();
242 return bHit;
246 /// <summary>
247 /// Recycler handler. This method is called directly by the eviction timer.
248 /// It should take no action beyond invoking the <see cref="SweepCache"/> method on the
249 /// cache manager instance passed as <paramref name="state"/>.
250 /// </summary>
251 /// <param name="state">The cache manager instance on which the 'recycle' handler should be invoked</param>
252 private static void CacheRecyclerHandler(object state)
254 ((QueryCacheManager)state).SweepCache();
257 /// <summary>
258 /// Aging factor
259 /// </summary>
260 private static readonly int[] _agingFactor = {1,1,2,4,8,16};
261 private static readonly int AgingMaxIndex = _agingFactor.Length - 1;
263 /// <summary>
264 /// Sweeps the cache removing old unused entries.
265 /// This method implements the query cache eviction policy.
266 /// </summary>
267 private void SweepCache()
269 if (!this._evictionTimer.Suspend())
271 // Return of false from .Suspend means that the manager and timer have been disposed.
272 return;
275 bool disabledEviction = false;
276 lock (_cacheDataLock)
279 // recycle only if entries exceeds the high mark factor
281 if (_cacheData.Count > _sweepingTriggerHighMark)
284 // sweep the cache
286 uint evictedEntriesCount = 0;
287 List<QueryCacheKey> cacheKeys = new List<QueryCacheKey>(_cacheData.Count);
288 cacheKeys.AddRange(_cacheData.Keys);
289 for (int i = 0; i < cacheKeys.Count; i++)
292 // if entry was not used in the last time window, then evict the entry
294 if (0 == cacheKeys[i].HitCount)
296 _cacheData.Remove(cacheKeys[i]);
297 evictedEntriesCount++;
300 // otherwise, age the entry in a progressive scheme
302 else
304 int agingIndex = unchecked(cacheKeys[i].AgingIndex + 1);
305 if (agingIndex > AgingMaxIndex)
307 agingIndex = AgingMaxIndex;
309 cacheKeys[i].AgingIndex = agingIndex;
310 cacheKeys[i].HitCount = cacheKeys[i].HitCount >> _agingFactor[agingIndex];
314 else
316 _evictionTimer.Stop();
317 disabledEviction = true;
321 if (!disabledEviction)
323 this._evictionTimer.Resume();
327 #endregion
329 #region IDisposable Members
331 /// <summary>
332 /// Dispose instance
333 /// </summary>
334 /// <remarks>Dispose <b>must</b> be called as there are no finalizers for this class</remarks>
335 public void Dispose()
337 // Technically, calling GC.SuppressFinalize is not required because the class does not
338 // have a finalizer, but it does no harm, protects against the case where a finalizer is added
339 // in the future, and prevents an FxCop warning.
340 GC.SuppressFinalize(this);
341 if (this._evictionTimer.Stop())
343 this.Clear();
347 #endregion
349 /// <summary>
350 /// Periodically invokes cache cleanup logic on a specified <see cref="QueryCacheManager"/> instance,
351 /// and allows this periodic callback to be suspended, resumed or stopped in a thread-safe way.
352 /// </summary>
353 private sealed class EvictionTimer
355 /// <summary>Used to control multi-threaded accesses to this instance</summary>
356 private readonly object _sync = new object();
358 /// <summary>The required interval between invocations of the cache cleanup logic</summary>
359 private readonly int _period;
361 /// <summary>The underlying QueryCacheManger that the callback will act on</summary>
362 private readonly QueryCacheManager _cacheManager;
364 /// <summary>The underlying <see cref="Timer"/> that implements the periodic callback</summary>
365 private Timer _timer;
367 internal EvictionTimer(QueryCacheManager cacheManager, int recyclePeriod)
369 this._cacheManager = cacheManager;
370 this._period = recyclePeriod;
373 internal void Start()
375 lock (_sync)
377 if (_timer == null)
379 this._timer = new Timer(QueryCacheManager.CacheRecyclerHandler, _cacheManager, _period, _period);
384 /// <summary>
385 /// Permanently stops the eviction timer.
386 /// It will no longer generate periodic callbacks and further calls to <see cref="Suspend"/>, <see cref="Resume"/>, or <see cref="Stop"/>,
387 /// though thread-safe, will have no effect.
388 /// </summary>
389 /// <returns>
390 /// If this eviction timer has already been stopped (using the <see cref="Stop"/> method), returns <c>false</c>;
391 /// otherwise, returns <c>true</c> to indicate that the call successfully stopped and cleaned up the underlying timer instance.
392 /// </returns>
393 /// <remarks>
394 /// Thread safe. May be called regardless of the current state of the eviction timer.
395 /// Once stopped, an eviction timer cannot be restarted with the <see cref="Resume"/> method.
396 /// </remarks>
397 internal bool Stop()
399 lock (_sync)
401 if (this._timer != null)
403 this._timer.Dispose();
404 this._timer = null;
405 return true;
407 else
409 return false;
414 /// <summary>
415 /// Pauses the operation of the eviction timer.
416 /// </summary>
417 /// <returns>
418 /// If this eviction timer has already been stopped (using the <see cref="Stop"/> method), returns <c>false</c>;
419 /// otherwise, returns <c>true</c> to indicate that the call successfully suspended the inderlying <see cref="Timer"/>
420 /// and no further periodic callbacks will be generated until the <see cref="Resume"/> method is called.
421 /// </returns>
422 /// <remarks>
423 /// Thread-safe. May be called regardless of the current state of the eviction timer.
424 /// Once suspended, an eviction timer may be resumed or stopped.
425 /// </remarks>
426 internal bool Suspend()
428 lock (_sync)
430 if (this._timer != null)
432 this._timer.Change(Timeout.Infinite, Timeout.Infinite);
433 return true;
435 else
437 return false;
442 /// <summary>
443 /// Causes this eviction timer to generate periodic callbacks, provided it has not been permanently stopped (using the <see cref="Stop"/> method).
444 /// </summary>
445 /// <remarks>
446 /// Thread-safe. May be called regardless of the current state of the eviction timer.
447 /// </remarks>
448 internal void Resume()
450 lock (_sync)
452 if (this._timer != null)
454 this._timer.Change(this._period, this._period);