diff options
author | Melanie Thielker | 2008-08-07 16:40:50 +0000 |
---|---|---|
committer | Melanie Thielker | 2008-08-07 16:40:50 +0000 |
commit | c4eac71e54544fb3dbf7283969a6e229225d9621 (patch) | |
tree | 7b8c34ceee4eed59ecf185be52e52b3e7e5251e7 /OpenSim/Framework/Cache.cs | |
parent | Patch #9159 (diff) | |
download | opensim-SC-c4eac71e54544fb3dbf7283969a6e229225d9621.zip opensim-SC-c4eac71e54544fb3dbf7283969a6e229225d9621.tar.gz opensim-SC-c4eac71e54544fb3dbf7283969a6e229225d9621.tar.bz2 opensim-SC-c4eac71e54544fb3dbf7283969a6e229225d9621.tar.xz |
Committing first draft of the universal cache. This is by no means
finished, but it does work for memory caching items in aggressive
mode. Supports several paramters, including TTL.
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Framework/Cache.cs | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/OpenSim/Framework/Cache.cs b/OpenSim/Framework/Cache.cs new file mode 100644 index 0000000..88b86a3 --- /dev/null +++ b/OpenSim/Framework/Cache.cs | |||
@@ -0,0 +1,467 @@ | |||
1 | using System; | ||
2 | using System.Collections.Generic; | ||
3 | using libsecondlife; | ||
4 | |||
5 | namespace Opensim.Framework | ||
6 | { | ||
7 | // The delegate we will use for performing fetch from backing store | ||
8 | // | ||
9 | public delegate Object FetchDelegate(LLUUID index); | ||
10 | public delegate bool ExpireDelegate(LLUUID index); | ||
11 | |||
12 | // Strategy | ||
13 | // | ||
14 | // Conservative = Minimize memory. Expire items quickly. | ||
15 | // Balanced = Expire items with few hits quickly. | ||
16 | // Aggressive = Keep cache full. Expire only when over 90% and adding | ||
17 | // | ||
18 | public enum CacheStrategy | ||
19 | { | ||
20 | Conservative = 0, | ||
21 | Balanced = 1, | ||
22 | Aggressive = 2 | ||
23 | } | ||
24 | |||
25 | // Select classes to store data on different media | ||
26 | // | ||
27 | public enum CacheMedium | ||
28 | { | ||
29 | Memory = 0, | ||
30 | File = 1 | ||
31 | } | ||
32 | |||
33 | public enum CacheFlags | ||
34 | { | ||
35 | CacheMissing = 1, | ||
36 | AllowUpdate = 2 | ||
37 | } | ||
38 | |||
39 | // The base class of all cache objects. Implements comparison and sorting | ||
40 | // by the LLUUID member. | ||
41 | // | ||
42 | // This is not abstract because we need to instantiate it briefly as a | ||
43 | // method parameter | ||
44 | // | ||
45 | public class CacheItemBase : IEquatable<CacheItemBase>, IComparable<CacheItemBase> | ||
46 | { | ||
47 | public LLUUID uuid; | ||
48 | public DateTime entered; | ||
49 | public DateTime lastUsed; | ||
50 | public DateTime expires = new DateTime(0); | ||
51 | public int hits = 0; | ||
52 | |||
53 | public virtual Object Retrieve() | ||
54 | { | ||
55 | return null; | ||
56 | } | ||
57 | |||
58 | public virtual void Store(Object data) | ||
59 | { | ||
60 | } | ||
61 | |||
62 | public CacheItemBase(LLUUID index) | ||
63 | { | ||
64 | uuid = index; | ||
65 | entered = DateTime.Now; | ||
66 | lastUsed = entered; | ||
67 | } | ||
68 | |||
69 | public CacheItemBase(LLUUID index, DateTime ttl) | ||
70 | { | ||
71 | uuid = index; | ||
72 | entered = DateTime.Now; | ||
73 | lastUsed = entered; | ||
74 | expires = ttl; | ||
75 | } | ||
76 | |||
77 | public virtual bool Equals(CacheItemBase item) | ||
78 | { | ||
79 | return uuid == item.uuid; | ||
80 | } | ||
81 | |||
82 | public virtual int CompareTo(CacheItemBase item) | ||
83 | { | ||
84 | return uuid.CompareTo(item.uuid); | ||
85 | } | ||
86 | |||
87 | public virtual bool IsLocked() | ||
88 | { | ||
89 | return false; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | // Simple in-memory storage. Boxes the object and stores it in a variable | ||
94 | // | ||
95 | public class MemoryCacheItem : CacheItemBase | ||
96 | { | ||
97 | private Object m_Data; | ||
98 | |||
99 | public MemoryCacheItem(LLUUID index) : | ||
100 | base(index) | ||
101 | { | ||
102 | } | ||
103 | |||
104 | public MemoryCacheItem(LLUUID index, DateTime ttl) : | ||
105 | base(index, ttl) | ||
106 | { | ||
107 | } | ||
108 | |||
109 | public MemoryCacheItem(LLUUID index, Object data) : | ||
110 | base(index) | ||
111 | { | ||
112 | Store(data); | ||
113 | } | ||
114 | |||
115 | public MemoryCacheItem(LLUUID index, DateTime ttl, Object data) : | ||
116 | base(index, ttl) | ||
117 | { | ||
118 | Store(data); | ||
119 | } | ||
120 | |||
121 | public override Object Retrieve() | ||
122 | { | ||
123 | return m_Data; | ||
124 | } | ||
125 | |||
126 | public override void Store(Object data) | ||
127 | { | ||
128 | m_Data = data; | ||
129 | } | ||
130 | } | ||
131 | |||
132 | // Simple persistent file storage | ||
133 | // | ||
134 | public class FileCacheItem : CacheItemBase | ||
135 | { | ||
136 | public FileCacheItem(LLUUID index) : | ||
137 | base(index) | ||
138 | { | ||
139 | } | ||
140 | |||
141 | public FileCacheItem(LLUUID index, DateTime ttl) : | ||
142 | base(index, ttl) | ||
143 | { | ||
144 | } | ||
145 | |||
146 | public FileCacheItem(LLUUID index, Object data) : | ||
147 | base(index) | ||
148 | { | ||
149 | Store(data); | ||
150 | } | ||
151 | |||
152 | public FileCacheItem(LLUUID index, DateTime ttl, Object data) : | ||
153 | base(index, ttl) | ||
154 | { | ||
155 | Store(data); | ||
156 | } | ||
157 | |||
158 | public override Object Retrieve() | ||
159 | { | ||
160 | //TODO: Add file access code | ||
161 | return null; | ||
162 | } | ||
163 | |||
164 | public override void Store(Object data) | ||
165 | { | ||
166 | //TODO: Add file access code | ||
167 | } | ||
168 | } | ||
169 | |||
170 | // The main cache class. This is the class you instantiate to create | ||
171 | // a cache | ||
172 | // | ||
173 | public class Cache | ||
174 | { | ||
175 | private List<CacheItemBase> m_Index = new List<CacheItemBase>(); | ||
176 | |||
177 | private CacheStrategy m_Strategy; | ||
178 | private CacheMedium m_Medium; | ||
179 | private CacheFlags m_Flags = 0; | ||
180 | private int m_Size = 1024; | ||
181 | private TimeSpan m_DefaultTTL = new TimeSpan(0); | ||
182 | public ExpireDelegate OnExpire; | ||
183 | |||
184 | // Comparison interfaces | ||
185 | // | ||
186 | private class SortLRU : IComparer<CacheItemBase> | ||
187 | { | ||
188 | public int Compare(CacheItemBase a, CacheItemBase b) | ||
189 | { | ||
190 | if(a == null && b == null) | ||
191 | return 0; | ||
192 | if(a == null) | ||
193 | return -1; | ||
194 | if(b == null) | ||
195 | return 1; | ||
196 | |||
197 | return(a.lastUsed.CompareTo(b.lastUsed)); | ||
198 | } | ||
199 | } | ||
200 | |||
201 | // Convenience constructors | ||
202 | // | ||
203 | public Cache() | ||
204 | { | ||
205 | m_Strategy = CacheStrategy.Balanced; | ||
206 | m_Medium = CacheMedium.Memory; | ||
207 | m_Flags = 0; | ||
208 | } | ||
209 | |||
210 | public Cache(CacheMedium medium) : | ||
211 | this(medium, CacheStrategy.Balanced) | ||
212 | { | ||
213 | } | ||
214 | |||
215 | public Cache(CacheMedium medium, CacheFlags flags) : | ||
216 | this(medium, CacheStrategy.Balanced, flags) | ||
217 | { | ||
218 | } | ||
219 | |||
220 | public Cache(CacheMedium medium, CacheStrategy strategy) : | ||
221 | this(medium, strategy, 0) | ||
222 | { | ||
223 | } | ||
224 | |||
225 | public Cache(CacheStrategy strategy, CacheFlags flags) : | ||
226 | this(CacheMedium.Memory, strategy, flags) | ||
227 | { | ||
228 | } | ||
229 | |||
230 | public Cache(CacheFlags flags) : | ||
231 | this(CacheMedium.Memory, CacheStrategy.Balanced, flags) | ||
232 | { | ||
233 | } | ||
234 | |||
235 | public Cache(CacheMedium medium, CacheStrategy strategy, | ||
236 | CacheFlags flags) | ||
237 | { | ||
238 | m_Strategy = strategy; | ||
239 | m_Medium = medium; | ||
240 | m_Flags = flags; | ||
241 | } | ||
242 | |||
243 | // Count of the items currently in cache | ||
244 | // | ||
245 | public int Count | ||
246 | { | ||
247 | get { lock(m_Index) { return m_Index.Count; } } | ||
248 | } | ||
249 | |||
250 | // Maximum number of items this cache will hold | ||
251 | // | ||
252 | public int Size | ||
253 | { | ||
254 | get { return m_Size; } | ||
255 | set { SetSize(value); } | ||
256 | } | ||
257 | |||
258 | private void SetSize(int newSize) | ||
259 | { | ||
260 | lock(m_Index) | ||
261 | { | ||
262 | if(Count <= Size) | ||
263 | return; | ||
264 | |||
265 | m_Index.Sort(new SortLRU()); | ||
266 | m_Index.Reverse(); | ||
267 | |||
268 | m_Index.RemoveRange(newSize, Count - newSize); | ||
269 | m_Size = newSize; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | public TimeSpan DefaultTTL | ||
274 | { | ||
275 | get { return m_DefaultTTL; } | ||
276 | set { m_DefaultTTL = value; } | ||
277 | } | ||
278 | |||
279 | // Get an item from cache. Return the raw item, not it's data | ||
280 | // | ||
281 | protected virtual CacheItemBase GetItem(LLUUID index) | ||
282 | { | ||
283 | CacheItemBase item = null; | ||
284 | |||
285 | lock(m_Index) | ||
286 | { | ||
287 | item = m_Index.Find(delegate(CacheItemBase i) | ||
288 | { | ||
289 | if(i.uuid == index) | ||
290 | return true; | ||
291 | return false; | ||
292 | }); | ||
293 | } | ||
294 | |||
295 | if(item == null) | ||
296 | { | ||
297 | Expire(true); | ||
298 | return null; | ||
299 | } | ||
300 | |||
301 | item.hits++; | ||
302 | item.lastUsed = DateTime.Now; | ||
303 | |||
304 | Expire(true); | ||
305 | |||
306 | return item; | ||
307 | } | ||
308 | |||
309 | // Get an item from cache. Do not try to fetch from source if not | ||
310 | // present. Just return null | ||
311 | // | ||
312 | public virtual Object Get(LLUUID index) | ||
313 | { | ||
314 | CacheItemBase item = GetItem(index); | ||
315 | |||
316 | if(item == null) | ||
317 | return null; | ||
318 | |||
319 | return item.Retrieve(); | ||
320 | } | ||
321 | |||
322 | // Fetch an object from backing store if not cached, serve from | ||
323 | // cache if it is. | ||
324 | // | ||
325 | public virtual Object Get(LLUUID index, FetchDelegate fetch) | ||
326 | { | ||
327 | Object item = Get(index); | ||
328 | if(item != null) | ||
329 | return item; | ||
330 | |||
331 | Object data = fetch(index); | ||
332 | if(data == null) | ||
333 | { | ||
334 | if((m_Flags & CacheFlags.CacheMissing) != 0) | ||
335 | { | ||
336 | lock(m_Index) | ||
337 | { | ||
338 | CacheItemBase missing = new CacheItemBase(index); | ||
339 | if(!m_Index.Contains(missing)) | ||
340 | m_Index.Add(missing); | ||
341 | } | ||
342 | } | ||
343 | return null; | ||
344 | } | ||
345 | |||
346 | Store(index, data); | ||
347 | |||
348 | return data; | ||
349 | } | ||
350 | |||
351 | |||
352 | public virtual void Store(LLUUID index, Object data) | ||
353 | { | ||
354 | Type container; | ||
355 | |||
356 | switch(m_Medium) | ||
357 | { | ||
358 | case CacheMedium.Memory: | ||
359 | container = typeof(MemoryCacheItem); | ||
360 | break; | ||
361 | case CacheMedium.File: | ||
362 | return; | ||
363 | default: | ||
364 | return; | ||
365 | } | ||
366 | |||
367 | Store(index, data, container); | ||
368 | } | ||
369 | |||
370 | public virtual void Store(LLUUID index, Object data, Type container) | ||
371 | { | ||
372 | Store(index, data, container, new Object[] { index }); | ||
373 | } | ||
374 | |||
375 | public virtual void Store(LLUUID index, Object data, Type container, | ||
376 | Object[] parameters) | ||
377 | { | ||
378 | Expire(false); | ||
379 | |||
380 | CacheItemBase item; | ||
381 | |||
382 | lock(m_Index) | ||
383 | { | ||
384 | if(m_Index.Contains(new CacheItemBase(index))) | ||
385 | { | ||
386 | if((m_Flags & CacheFlags.AllowUpdate) != 0) | ||
387 | { | ||
388 | item = GetItem(index); | ||
389 | |||
390 | item.hits++; | ||
391 | item.lastUsed = DateTime.Now; | ||
392 | if(m_DefaultTTL.Ticks != 0) | ||
393 | item.expires = DateTime.Now + m_DefaultTTL; | ||
394 | |||
395 | item.Store(data); | ||
396 | } | ||
397 | return; | ||
398 | } | ||
399 | |||
400 | item = (CacheItemBase)Activator.CreateInstance(container, | ||
401 | parameters); | ||
402 | |||
403 | if(m_DefaultTTL.Ticks != 0) | ||
404 | item.expires = DateTime.Now + m_DefaultTTL; | ||
405 | |||
406 | m_Index.Add(item); | ||
407 | } | ||
408 | item.Store(data); | ||
409 | } | ||
410 | |||
411 | protected virtual void Expire(bool getting) | ||
412 | { | ||
413 | if(getting && (m_Strategy == CacheStrategy.Aggressive)) | ||
414 | return; | ||
415 | |||
416 | if(m_DefaultTTL.Ticks != 0) | ||
417 | { | ||
418 | DateTime now= System.DateTime.Now; | ||
419 | |||
420 | foreach (CacheItemBase item in new List<CacheItemBase>(m_Index)) | ||
421 | { | ||
422 | if(item.expires.Ticks == 0 || | ||
423 | item.expires <= now) | ||
424 | m_Index.Remove(item); | ||
425 | } | ||
426 | } | ||
427 | |||
428 | switch (m_Strategy) | ||
429 | { | ||
430 | case CacheStrategy.Aggressive: | ||
431 | if(Count < Size) | ||
432 | return; | ||
433 | |||
434 | lock(m_Index) | ||
435 | { | ||
436 | m_Index.Sort(new SortLRU()); | ||
437 | m_Index.Reverse(); | ||
438 | |||
439 | int target = (int)((float)Size * 0.9); | ||
440 | if(target == Count) // Cover ridiculous cache sizes | ||
441 | return; | ||
442 | |||
443 | ExpireDelegate doExpire = OnExpire; | ||
444 | |||
445 | if(doExpire != null) | ||
446 | { | ||
447 | List<CacheItemBase> candidates = | ||
448 | m_Index.GetRange(target, Count - target); | ||
449 | |||
450 | foreach (CacheItemBase i in candidates) | ||
451 | { | ||
452 | if(doExpire(i.uuid)) | ||
453 | m_Index.Remove(i); | ||
454 | } | ||
455 | } | ||
456 | else | ||
457 | { | ||
458 | m_Index.RemoveRange(target, Count - target); | ||
459 | } | ||
460 | } | ||
461 | break; | ||
462 | default: | ||
463 | break; | ||
464 | } | ||
465 | } | ||
466 | } | ||
467 | } | ||