2 // MemoryCacheContainer.cs
5 // Marek Habersack <mhabersack@novell.com>
7 // Copyright (C) 2010 Novell, Inc. (http://novell.com/)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
32 using System.Configuration;
33 using System.Diagnostics;
34 using System.Reflection;
35 using System.Runtime.Caching.Configuration;
36 using System.Threading;
38 namespace System.Runtime.Caching
40 sealed class MemoryCacheContainer : IDisposable
42 ReaderWriterLockSlim cache_lock = new ReaderWriterLockSlim ();
44 SortedDictionary <string, MemoryCacheEntry> cache;
46 MemoryCachePerformanceCounters perfCounters;
47 MemoryCacheEntryPriorityQueue timedItems;
48 Timer expirationTimer;
55 get { return (long)cache.Count; }
58 public MemoryCacheContainer (MemoryCache owner, int id, MemoryCachePerformanceCounters perfCounters)
61 throw new ArgumentNullException ("owner");
65 this.perfCounters = perfCounters;
66 cache = new SortedDictionary <string, MemoryCacheEntry> ();
69 bool ExpireIfNeeded (string key, MemoryCacheEntry entry, bool needsLock = true, CacheEntryRemovedReason reason = CacheEntryRemovedReason.Expired)
74 if (entry.IsExpired) {
76 cache_lock.EnterWriteLock ();
81 perfCounters.Decrement (MemoryCachePerformanceCounters.CACHE_ENTRIES);
82 entry.Removed (owner, CacheEntryRemovedReason.Expired);
88 cache_lock.ExitWriteLock ();
94 public void Dispose ()
96 if (expirationTimer != null) {
97 expirationTimer.Dispose ();
98 expirationTimer = null;
102 public void CopyEntries (IDictionary dict)
106 cache_lock.EnterWriteLock ();
109 MemoryCacheEntry entry;
110 foreach (var de in cache) {
116 dict.Add (de.Key, entry.Value);
120 cache_lock.ExitWriteLock ();
124 public bool ContainsKey (string key)
126 bool readLocked = false;
128 cache_lock.EnterUpgradeableReadLock ();
131 MemoryCacheEntry entry;
132 if (cache.TryGetValue (key, out entry)) {
133 if (ExpireIfNeeded (key, entry))
142 cache_lock.ExitUpgradeableReadLock ();
146 // NOTE: this method _MUST_ be called with the write lock held
147 void AddToCache (string key, MemoryCacheEntry entry, bool update = false)
152 cache.Add (key, entry);
155 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_ENTRIES);
157 if (entry.IsExpirable)
158 UpdateExpirable (entry);
161 // NOTE: this method _MUST_ be called with the write lock held
162 void UpdateExpirable (MemoryCacheEntry entry)
164 if (timedItems == null)
165 timedItems = new MemoryCacheEntryPriorityQueue ();
167 timedItems.Enqueue (entry);
169 if (expirationTimer == null)
170 expirationTimer = new Timer (RemoveExpiredItems, null, owner.TimerPeriod, owner.TimerPeriod);
173 void RemoveExpiredItems (object state)
175 DoRemoveExpiredItems (true);
178 long DoRemoveExpiredItems (bool needLock)
184 cache_lock.EnterWriteLock ();
188 if (timedItems == null)
191 long now = DateTime.Now.Ticks;
192 MemoryCacheEntry entry = timedItems.Peek ();
194 while (entry != null) {
195 if (entry.Disabled) {
196 timedItems.Dequeue ();
197 entry = timedItems.Peek ();
201 if (entry.ExpiresAt > now)
204 timedItems.Dequeue ();
206 DoRemoveEntry (entry, entry.Key, CacheEntryRemovedReason.Expired);
207 entry = timedItems.Peek ();
212 expirationTimer.Dispose ();
213 expirationTimer = null;
219 cache_lock.ExitWriteLock ();
223 public object AddOrGetExisting (string key, object value, CacheItemPolicy policy)
225 bool readLocked = false, writeLocked = false;
227 cache_lock.EnterUpgradeableReadLock ();
230 MemoryCacheEntry entry;
231 if (cache.TryGetValue (key, out entry) && entry != null) {
232 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_HITS);
235 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_MISSES);
237 cache_lock.EnterWriteLock ();
241 entry = new MemoryCacheEntry (owner, key, value);
243 entry = new MemoryCacheEntry (owner, key, value,
244 policy.AbsoluteExpiration,
245 policy.ChangeMonitors,
247 policy.RemovedCallback,
248 policy.SlidingExpiration,
249 policy.UpdateCallback);
251 AddToCache (key, entry);
255 cache_lock.ExitWriteLock ();
257 cache_lock.ExitUpgradeableReadLock ();
261 public MemoryCacheEntry GetEntry (string key)
265 cache_lock.EnterReadLock ();
268 MemoryCacheEntry entry;
269 if (cache.TryGetValue (key, out entry)) {
270 if (ExpireIfNeeded (key, entry))
279 cache_lock.ExitReadLock ();
283 public object Get (string key)
285 bool readLocked = false;
287 cache_lock.EnterUpgradeableReadLock ();
290 MemoryCacheEntry entry;
291 if (cache.TryGetValue (key, out entry)) {
292 if (ExpireIfNeeded (key, entry))
295 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_HITS);
299 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_MISSES);
303 cache_lock.ExitUpgradeableReadLock ();
307 public object Remove (string key)
309 bool writeLocked = false, readLocked = false;
311 cache_lock.EnterUpgradeableReadLock ();
314 MemoryCacheEntry entry;
315 if (!cache.TryGetValue (key, out entry))
318 cache_lock.EnterWriteLock ();
320 object ret = entry.Value;
321 DoRemoveEntry (entry, key);
325 cache_lock.ExitWriteLock ();
327 cache_lock.ExitUpgradeableReadLock ();
331 // NOTE: this must be called with the write lock held
332 void DoRemoveEntry (MemoryCacheEntry entry, string key = null, CacheEntryRemovedReason reason = CacheEntryRemovedReason.Removed)
338 perfCounters.Decrement (MemoryCachePerformanceCounters.CACHE_ENTRIES);
339 entry.Removed (owner, reason);
342 public void Set (string key, object value, CacheItemPolicy policy)
346 cache_lock.EnterWriteLock ();
349 MemoryCacheEntry mce;
351 if (cache.TryGetValue (key, out mce)) {
353 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_HITS);
355 mce.SetPolicy (policy);
357 UpdateExpirable (mce);
363 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_MISSES);
365 mce = new MemoryCacheEntry (owner, key, value,
366 policy.AbsoluteExpiration,
367 policy.ChangeMonitors,
369 policy.RemovedCallback,
370 policy.SlidingExpiration,
371 policy.UpdateCallback);
373 mce = new MemoryCacheEntry (owner, key, value);
374 AddToCache (key, mce, update);
377 cache_lock.ExitWriteLock ();
381 public long Trim (int percent)
383 int count = cache.Count;
387 long goal = (long)((count * percent) / 100);
392 cache_lock.EnterWriteLock ();
394 ret = DoRemoveExpiredItems (false);
398 // TODO: perform LRU removal
402 cache_lock.ExitWriteLock ();