ff424089b1147963d268f22e2344a6fbb3c31fea
[mono.git] / mcs / class / System.Runtime.Caching / System.Runtime.Caching / MemoryCacheContainer.cs
1 //
2 // MemoryCacheContainer.cs
3 //
4 // Authors:
5 //      Marek Habersack <mhabersack@novell.com>
6 //
7 // Copyright (C) 2010 Novell, Inc. (http://novell.com/)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28 using System;
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;
37
38 namespace System.Runtime.Caching
39 {
40         sealed class MemoryCacheContainer : IDisposable
41         {
42                 ReaderWriterLockSlim cache_lock = new ReaderWriterLockSlim ();
43                 
44                 SortedDictionary <string, MemoryCacheEntry> cache;
45                 MemoryCache owner;
46                 MemoryCachePerformanceCounters perfCounters;
47                 MemoryCacheEntryPriorityQueue timedItems;
48                 Timer expirationTimer;
49                 
50                 public int ID {
51                         get; private set;
52                 }
53
54                 public long Count {
55                         get { return (long)cache.Count; }
56                 }
57                 
58                 public MemoryCacheContainer (MemoryCache owner, int id, MemoryCachePerformanceCounters perfCounters)
59                 {
60                         if (owner == null)
61                                 throw new ArgumentNullException ("owner");
62                         
63                         this.owner = owner;
64                         this.ID = id;
65                         this.perfCounters = perfCounters;
66                         cache = new SortedDictionary <string, MemoryCacheEntry> ();
67                 }
68
69                 bool ExpireIfNeeded (string key, MemoryCacheEntry entry, bool needsLock = true, CacheEntryRemovedReason reason = CacheEntryRemovedReason.Expired)
70                 {
71                         bool locked = false;
72                         
73                         try {
74                                 if (entry.IsExpired) {
75                                         if (needsLock) {
76                                                 cache_lock.EnterWriteLock ();
77                                                 locked = true;
78                                         }
79                                         
80                                         cache.Remove (key);
81                                         perfCounters.Decrement (MemoryCachePerformanceCounters.CACHE_ENTRIES);
82                                         entry.Removed (owner, CacheEntryRemovedReason.Expired);
83                                         
84                                         return true;
85                                 }
86                         } finally {
87                                 if (locked)
88                                         cache_lock.ExitWriteLock ();
89                         }
90                         
91                         return false;
92                 }
93
94                 public void Dispose ()
95                 {
96                         if (expirationTimer != null) {
97                                 expirationTimer.Dispose ();
98                                 expirationTimer = null;
99                         }
100                 }
101                 
102                 public void CopyEntries (IDictionary dict)
103                 {
104                         bool locked = false;
105                         try {
106                                 cache_lock.EnterWriteLock ();
107                                 locked = true;
108
109                                 MemoryCacheEntry entry;
110                                 foreach (var de in cache) {
111                                         entry = de.Value;
112
113                                         if (entry.IsExpired)
114                                                 continue;
115
116                                         dict.Add (de.Key, entry.Value);
117                                 }
118                         } finally {
119                                 if (locked)
120                                         cache_lock.ExitWriteLock ();
121                         }
122                 }
123                 
124                 public bool ContainsKey (string key)
125                 {
126                         bool readLocked = false;
127                         try {
128                                 cache_lock.EnterUpgradeableReadLock ();
129                                 readLocked = true;
130
131                                 MemoryCacheEntry entry;
132                                 if (cache.TryGetValue (key, out entry)) {
133                                         if (ExpireIfNeeded (key, entry))
134                                                 return false;
135
136                                         return true;
137                                 }
138
139                                 return false;
140                         } finally {
141                                 if (readLocked)
142                                         cache_lock.ExitUpgradeableReadLock ();
143                         }
144                 }
145
146                 // NOTE: this method _MUST_ be called with the write lock held
147                 void AddToCache (string key, MemoryCacheEntry entry, bool update = false)
148                 {
149                         if (update)
150                                 cache [key] = entry;
151                         else
152                                 cache.Add (key, entry);
153                         entry.Added ();
154                         if (!update)
155                                 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_ENTRIES);
156
157                         if (entry.IsExpirable)
158                                 UpdateExpirable (entry);
159                 }
160
161                 // NOTE: this method _MUST_ be called with the write lock held
162                 void UpdateExpirable (MemoryCacheEntry entry)
163                 {
164                         if (timedItems == null)
165                                 timedItems = new MemoryCacheEntryPriorityQueue ();
166
167                         timedItems.Enqueue (entry);
168
169                         if (expirationTimer == null)
170                                 expirationTimer = new Timer (RemoveExpiredItems, null, owner.TimerPeriod, owner.TimerPeriod);
171                 }
172
173                 void RemoveExpiredItems (object state)
174                 {
175                         DoRemoveExpiredItems (true);
176                 }
177
178                 long DoRemoveExpiredItems (bool needLock)
179                 {
180                         long count = 0;
181                         bool locked = false;
182                         try {
183                                 if (needLock) {
184                                         cache_lock.EnterWriteLock ();
185                                         locked = true;
186                                 }
187
188                                 if (timedItems == null)
189                                         return 0;
190                                 
191                                 long now = DateTime.Now.Ticks;
192                                 MemoryCacheEntry entry = timedItems.Peek ();
193
194                                 while (entry != null) {
195                                         if (entry.Disabled) {
196                                                 timedItems.Dequeue ();
197                                                 entry = timedItems.Peek ();
198                                                 continue;
199                                         }
200
201                                         if (entry.ExpiresAt > now)
202                                                 break;
203
204                                         timedItems.Dequeue ();
205                                         count++;
206                                         DoRemoveEntry (entry, entry.Key, CacheEntryRemovedReason.Expired);
207                                         entry = timedItems.Peek ();
208                                 }
209
210                                 if (entry == null) {
211                                         timedItems = null;
212                                         expirationTimer.Dispose ();
213                                         expirationTimer = null;
214                                 }
215
216                                 return count;
217                         } finally {
218                                 if (locked)
219                                         cache_lock.ExitWriteLock ();
220                         }
221                 }
222                 
223                 public object AddOrGetExisting (string key, object value, CacheItemPolicy policy)
224                 {
225                         bool readLocked = false, writeLocked = false;
226                         try {
227                                 cache_lock.EnterUpgradeableReadLock ();
228                                 readLocked = true;
229
230                                 MemoryCacheEntry entry;
231                                 if (cache.TryGetValue (key, out entry) && entry != null) {
232                                         perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_HITS);
233                                         return entry.Value;
234                                 }
235                                 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_MISSES);
236                                 
237                                 cache_lock.EnterWriteLock ();
238                                 writeLocked = true;
239
240                                 if (policy == null)
241                                         entry = new MemoryCacheEntry (owner, key, value);
242                                 else
243                                         entry = new MemoryCacheEntry (owner, key, value,
244                                                                       policy.AbsoluteExpiration,
245                                                                       policy.ChangeMonitors,
246                                                                       policy.Priority,
247                                                                       policy.RemovedCallback,
248                                                                       policy.SlidingExpiration,
249                                                                       policy.UpdateCallback);
250
251                                 AddToCache (key, entry);
252                                 return null;
253                         } finally {
254                                 if (writeLocked)
255                                         cache_lock.ExitWriteLock ();
256                                 if (readLocked)
257                                         cache_lock.ExitUpgradeableReadLock ();
258                         }
259                 }
260
261                 public MemoryCacheEntry GetEntry (string key)
262                 {
263                         bool locked = false;
264                         try {
265                                 cache_lock.EnterReadLock ();
266                                 locked = true;
267
268                                 MemoryCacheEntry entry;
269                                 if (cache.TryGetValue (key, out entry)) {
270                                         if (ExpireIfNeeded (key, entry))
271                                                 return null;
272                                         
273                                         return entry;
274                                 }
275                                 
276                                 return null;
277                         } finally {
278                                 if (locked)
279                                         cache_lock.ExitReadLock ();
280                         }
281                 }
282                 
283                 public object Get (string key)
284                 {
285                         bool readLocked = false;
286                         try {
287                                 cache_lock.EnterUpgradeableReadLock ();
288                                 readLocked = true;
289
290                                 MemoryCacheEntry entry;
291                                 if (cache.TryGetValue (key, out entry)) {
292                                         if (ExpireIfNeeded (key, entry))
293                                                 return null;
294                                         
295                                         perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_HITS);
296                                         return entry.Value;
297                                 }
298
299                                 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_MISSES);
300                                 return null;
301                         } finally {
302                                 if (readLocked)
303                                         cache_lock.ExitUpgradeableReadLock ();
304                         }
305                 }
306
307                 public object Remove (string key)
308                 {
309                         bool writeLocked = false, readLocked = false;
310                         try {
311                                 cache_lock.EnterUpgradeableReadLock ();
312                                 readLocked = true;
313
314                                 MemoryCacheEntry entry;
315                                 if (!cache.TryGetValue (key, out entry))
316                                         return null;
317                                 
318                                 cache_lock.EnterWriteLock ();
319                                 writeLocked = true;
320                                 object ret = entry.Value;
321                                 DoRemoveEntry (entry, key);
322                                 return ret;
323                         } finally {
324                                 if (writeLocked)
325                                         cache_lock.ExitWriteLock ();
326                                 if (readLocked)
327                                         cache_lock.ExitUpgradeableReadLock ();
328                         }
329                 }
330
331                 // NOTE: this must be called with the write lock held
332                 void DoRemoveEntry (MemoryCacheEntry entry, string key = null, CacheEntryRemovedReason reason = CacheEntryRemovedReason.Removed)
333                 {
334                         if (key == null)
335                                 key = entry.Key;
336
337                         cache.Remove (key);
338                         perfCounters.Decrement (MemoryCachePerformanceCounters.CACHE_ENTRIES);
339                         entry.Removed (owner, reason);
340                 }
341                 
342                 public void Set (string key, object value, CacheItemPolicy policy)
343                 {
344                         bool locked = false;
345                         try {
346                                 cache_lock.EnterWriteLock ();
347                                 locked = true;
348
349                                 MemoryCacheEntry mce;
350                                 bool update = false;
351                                 if (cache.TryGetValue (key, out mce)) {
352                                         if (mce != null) {
353                                                 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_HITS);
354                                                 mce.Value = value;
355                                                 mce.SetPolicy (policy);
356                                                 if (mce.IsExpirable)
357                                                         UpdateExpirable (mce);
358                                                 return;
359                                         }
360
361                                         update = true;
362                                 }
363                                 perfCounters.Increment (MemoryCachePerformanceCounters.CACHE_MISSES);
364                                 if (policy != null)
365                                         mce = new MemoryCacheEntry (owner, key, value,
366                                                                     policy.AbsoluteExpiration,
367                                                                     policy.ChangeMonitors,
368                                                                     policy.Priority,
369                                                                     policy.RemovedCallback,
370                                                                     policy.SlidingExpiration,
371                                                                     policy.UpdateCallback);
372                                 else
373                                         mce = new MemoryCacheEntry (owner, key, value);
374                                 AddToCache (key, mce, update);
375                         } finally {
376                                 if (locked)
377                                         cache_lock.ExitWriteLock ();
378                         }
379                 }
380
381                 public long Trim (int percent)
382                 {
383                         int count = cache.Count;
384                         if (count == 0)
385                                 return 0;
386
387                         long goal = (long)((count * percent) / 100);
388                         bool locked = false;
389                         long ret = 0;
390
391                         try {
392                                 cache_lock.EnterWriteLock ();
393                                 locked = true;
394                                 ret = DoRemoveExpiredItems (false);
395
396                                 goal -= ret;
397                                 if (goal > 0) {
398                                         // TODO: perform LRU removal
399                         }
400                         } finally {
401                                 if (locked)
402                                         cache_lock.ExitWriteLock ();
403                         }
404                         
405                         return ret;
406                 }
407         }
408 }