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.
0.6.0-stable
Melanie Thielker 2008-08-07 16:40:50 +00:00
parent 39e68833b2
commit c4eac71e54
1 changed files with 467 additions and 0 deletions

467
OpenSim/Framework/Cache.cs Normal file
View File

@ -0,0 +1,467 @@
using System;
using System.Collections.Generic;
using libsecondlife;
namespace Opensim.Framework
{
// The delegate we will use for performing fetch from backing store
//
public delegate Object FetchDelegate(LLUUID index);
public delegate bool ExpireDelegate(LLUUID index);
// Strategy
//
// Conservative = Minimize memory. Expire items quickly.
// Balanced = Expire items with few hits quickly.
// Aggressive = Keep cache full. Expire only when over 90% and adding
//
public enum CacheStrategy
{
Conservative = 0,
Balanced = 1,
Aggressive = 2
}
// Select classes to store data on different media
//
public enum CacheMedium
{
Memory = 0,
File = 1
}
public enum CacheFlags
{
CacheMissing = 1,
AllowUpdate = 2
}
// The base class of all cache objects. Implements comparison and sorting
// by the LLUUID member.
//
// This is not abstract because we need to instantiate it briefly as a
// method parameter
//
public class CacheItemBase : IEquatable<CacheItemBase>, IComparable<CacheItemBase>
{
public LLUUID uuid;
public DateTime entered;
public DateTime lastUsed;
public DateTime expires = new DateTime(0);
public int hits = 0;
public virtual Object Retrieve()
{
return null;
}
public virtual void Store(Object data)
{
}
public CacheItemBase(LLUUID index)
{
uuid = index;
entered = DateTime.Now;
lastUsed = entered;
}
public CacheItemBase(LLUUID index, DateTime ttl)
{
uuid = index;
entered = DateTime.Now;
lastUsed = entered;
expires = ttl;
}
public virtual bool Equals(CacheItemBase item)
{
return uuid == item.uuid;
}
public virtual int CompareTo(CacheItemBase item)
{
return uuid.CompareTo(item.uuid);
}
public virtual bool IsLocked()
{
return false;
}
}
// Simple in-memory storage. Boxes the object and stores it in a variable
//
public class MemoryCacheItem : CacheItemBase
{
private Object m_Data;
public MemoryCacheItem(LLUUID index) :
base(index)
{
}
public MemoryCacheItem(LLUUID index, DateTime ttl) :
base(index, ttl)
{
}
public MemoryCacheItem(LLUUID index, Object data) :
base(index)
{
Store(data);
}
public MemoryCacheItem(LLUUID index, DateTime ttl, Object data) :
base(index, ttl)
{
Store(data);
}
public override Object Retrieve()
{
return m_Data;
}
public override void Store(Object data)
{
m_Data = data;
}
}
// Simple persistent file storage
//
public class FileCacheItem : CacheItemBase
{
public FileCacheItem(LLUUID index) :
base(index)
{
}
public FileCacheItem(LLUUID index, DateTime ttl) :
base(index, ttl)
{
}
public FileCacheItem(LLUUID index, Object data) :
base(index)
{
Store(data);
}
public FileCacheItem(LLUUID index, DateTime ttl, Object data) :
base(index, ttl)
{
Store(data);
}
public override Object Retrieve()
{
//TODO: Add file access code
return null;
}
public override void Store(Object data)
{
//TODO: Add file access code
}
}
// The main cache class. This is the class you instantiate to create
// a cache
//
public class Cache
{
private List<CacheItemBase> m_Index = new List<CacheItemBase>();
private CacheStrategy m_Strategy;
private CacheMedium m_Medium;
private CacheFlags m_Flags = 0;
private int m_Size = 1024;
private TimeSpan m_DefaultTTL = new TimeSpan(0);
public ExpireDelegate OnExpire;
// Comparison interfaces
//
private class SortLRU : IComparer<CacheItemBase>
{
public int Compare(CacheItemBase a, CacheItemBase b)
{
if(a == null && b == null)
return 0;
if(a == null)
return -1;
if(b == null)
return 1;
return(a.lastUsed.CompareTo(b.lastUsed));
}
}
// Convenience constructors
//
public Cache()
{
m_Strategy = CacheStrategy.Balanced;
m_Medium = CacheMedium.Memory;
m_Flags = 0;
}
public Cache(CacheMedium medium) :
this(medium, CacheStrategy.Balanced)
{
}
public Cache(CacheMedium medium, CacheFlags flags) :
this(medium, CacheStrategy.Balanced, flags)
{
}
public Cache(CacheMedium medium, CacheStrategy strategy) :
this(medium, strategy, 0)
{
}
public Cache(CacheStrategy strategy, CacheFlags flags) :
this(CacheMedium.Memory, strategy, flags)
{
}
public Cache(CacheFlags flags) :
this(CacheMedium.Memory, CacheStrategy.Balanced, flags)
{
}
public Cache(CacheMedium medium, CacheStrategy strategy,
CacheFlags flags)
{
m_Strategy = strategy;
m_Medium = medium;
m_Flags = flags;
}
// Count of the items currently in cache
//
public int Count
{
get { lock(m_Index) { return m_Index.Count; } }
}
// Maximum number of items this cache will hold
//
public int Size
{
get { return m_Size; }
set { SetSize(value); }
}
private void SetSize(int newSize)
{
lock(m_Index)
{
if(Count <= Size)
return;
m_Index.Sort(new SortLRU());
m_Index.Reverse();
m_Index.RemoveRange(newSize, Count - newSize);
m_Size = newSize;
}
}
public TimeSpan DefaultTTL
{
get { return m_DefaultTTL; }
set { m_DefaultTTL = value; }
}
// Get an item from cache. Return the raw item, not it's data
//
protected virtual CacheItemBase GetItem(LLUUID index)
{
CacheItemBase item = null;
lock(m_Index)
{
item = m_Index.Find(delegate(CacheItemBase i)
{
if(i.uuid == index)
return true;
return false;
});
}
if(item == null)
{
Expire(true);
return null;
}
item.hits++;
item.lastUsed = DateTime.Now;
Expire(true);
return item;
}
// Get an item from cache. Do not try to fetch from source if not
// present. Just return null
//
public virtual Object Get(LLUUID index)
{
CacheItemBase item = GetItem(index);
if(item == null)
return null;
return item.Retrieve();
}
// Fetch an object from backing store if not cached, serve from
// cache if it is.
//
public virtual Object Get(LLUUID index, FetchDelegate fetch)
{
Object item = Get(index);
if(item != null)
return item;
Object data = fetch(index);
if(data == null)
{
if((m_Flags & CacheFlags.CacheMissing) != 0)
{
lock(m_Index)
{
CacheItemBase missing = new CacheItemBase(index);
if(!m_Index.Contains(missing))
m_Index.Add(missing);
}
}
return null;
}
Store(index, data);
return data;
}
public virtual void Store(LLUUID index, Object data)
{
Type container;
switch(m_Medium)
{
case CacheMedium.Memory:
container = typeof(MemoryCacheItem);
break;
case CacheMedium.File:
return;
default:
return;
}
Store(index, data, container);
}
public virtual void Store(LLUUID index, Object data, Type container)
{
Store(index, data, container, new Object[] { index });
}
public virtual void Store(LLUUID index, Object data, Type container,
Object[] parameters)
{
Expire(false);
CacheItemBase item;
lock(m_Index)
{
if(m_Index.Contains(new CacheItemBase(index)))
{
if((m_Flags & CacheFlags.AllowUpdate) != 0)
{
item = GetItem(index);
item.hits++;
item.lastUsed = DateTime.Now;
if(m_DefaultTTL.Ticks != 0)
item.expires = DateTime.Now + m_DefaultTTL;
item.Store(data);
}
return;
}
item = (CacheItemBase)Activator.CreateInstance(container,
parameters);
if(m_DefaultTTL.Ticks != 0)
item.expires = DateTime.Now + m_DefaultTTL;
m_Index.Add(item);
}
item.Store(data);
}
protected virtual void Expire(bool getting)
{
if(getting && (m_Strategy == CacheStrategy.Aggressive))
return;
if(m_DefaultTTL.Ticks != 0)
{
DateTime now= System.DateTime.Now;
foreach (CacheItemBase item in new List<CacheItemBase>(m_Index))
{
if(item.expires.Ticks == 0 ||
item.expires <= now)
m_Index.Remove(item);
}
}
switch (m_Strategy)
{
case CacheStrategy.Aggressive:
if(Count < Size)
return;
lock(m_Index)
{
m_Index.Sort(new SortLRU());
m_Index.Reverse();
int target = (int)((float)Size * 0.9);
if(target == Count) // Cover ridiculous cache sizes
return;
ExpireDelegate doExpire = OnExpire;
if(doExpire != null)
{
List<CacheItemBase> candidates =
m_Index.GetRange(target, Count - target);
foreach (CacheItemBase i in candidates)
{
if(doExpire(i.uuid))
m_Index.Remove(i);
}
}
else
{
m_Index.RemoveRange(target, Count - target);
}
}
break;
default:
break;
}
}
}
}