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 | |
parent | Patch #9159 (diff) | |
download | opensim-SC_OLD-c4eac71e54544fb3dbf7283969a6e229225d9621.zip opensim-SC_OLD-c4eac71e54544fb3dbf7283969a6e229225d9621.tar.gz opensim-SC_OLD-c4eac71e54544fb3dbf7283969a6e229225d9621.tar.bz2 opensim-SC_OLD-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.
-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 | } | ||