Skip to content

Instantly share code, notes, and snippets.

@Teun
Last active January 4, 2016 02:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Teun/2adf69c1890bc1b067f6 to your computer and use it in GitHub Desktop.
Save Teun/2adf69c1890bc1b067f6 to your computer and use it in GitHub Desktop.
This is part of the CacheManager code. Its main use is being a transparent interface to several cache stores (in-memory, memcached, protobuf files). Selecting the right store happens at instantiation of the CacheManager class and is based on information in .config file.
/// <summary>
/// This method should preferably not be used: try if you can use the overload specifying a fetchAction instead.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public T GetFromCache<T>(string key)
where T : class
{
object ret = GetFromCache(key);
return ret as T;
}
public T GetFromCache<T>(string key, Func<T> onNotFoundAction)
{
// Do not use stampedeProtection
return GetFromCache(key, onNotFoundAction, false);
}
public T GetFromCache<T>(string key, Func<T> onNotFoundAction, bool stampedeProtection)
{
// always needs storing
return GetFromCache(key, onNotFoundAction, stampedeProtection, obj => true);
}
[Serializable]
public class NullValue
{
}
/// <summary>
/// This is the recommended way of using the cache manager. With GetFromCache&lt;&gt; you leave the logic surrounding
/// caching in the cache manager, but provide the details of getting at the data when not in cache as a delegate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The unique key, make sure it doesn't overlap with other data keys</param>
/// <param name="fetchAction">This delegate is invoked when the cache is empty (a miss). The result will be
/// stored in cache for later use.</param>
/// <param name="stampedeProtection">If set to true (default = false) this will cause the cache manager to prevent multiple
/// threads from fetching the same data. Instead, the second thread experiencing a miss will wait for the first thread to return
/// with the data. This technique involves overhead, so should not be used by default.</param>
/// <param name="needsStoring">This delegate can be used if you want to decide based on what is fetched if it should be cache.
/// <example>If an NHibrnate queries results are requested, they are only cached when the result set contains less than 500 items.</example></param>
/// <returns></returns>
public T GetFromCache<T>(string key, Func<T> fetchAction, bool stampedeProtection, Func<T, Boolean> needsStoring)
{
// argh?
key = AddCacheVersionNumberToKey(key);
long startTime = 0;
long endTime = 0;
int timesSlept = 0;
if (_usingCounters)
{
NativeMethods.QueryPerformanceCounter(ref startTime);
}
_log.Debug("GetFromCache: [" + _durationGroup + "] " + key);
object obj = null;
while (obj == null)
{
obj = _store.Get(key);
if(obj is NotImplementedException)
{
// this is a nasty case where the store is not able to deserialize without knowing the type
obj = _store.Get<T>(key);
}
if (obj is NullValue)
{
return default(T);
}
if (CacheDisabledFromUrl(key) || obj is String && string.IsNullOrEmpty(obj.ToString()))
{
obj = null;
}
if (obj == null)
{
// Niet gevonden
if (timesSlept > Settings.Default.StampedeMaxSleepCycles)
{
_log.WarnFormat("Waiting for {0} x {1} ms for a locked cache key. Some other thread is taking very long to fill {2}. We will try it ourselves.", Settings.Default.StampedeMaxSleepCycles, Settings.Default.StampedeSleepTime, key);
stampedeProtection = false;
}
if (stampedeProtection)
{
// special case: we want to make sure that only one thread does the fetch after the miss.
if (_store.AcquireLock(key))
{
if (_usingCounters)
{
// log a miss only when we acquire the lock: other cases are just a slow hit
NativeMethods.QueryPerformanceCounter(ref endTime);
_instrumentation.RegisterMiss(_durationGroup, endTime - startTime, key);
startTime = endTime;
}
try
{
// We have to do the work
obj = fetchAction.Invoke();
if (_usingCounters)
{
NativeMethods.QueryPerformanceCounter(ref endTime);
_instrumentation.RegisterFetch(_durationGroup, obj, endTime - startTime, key);
}
if (obj == null)
{
_store.Store(key, new NullValue());
return default(T);
}
else if (needsStoring((T)obj))
{
if (_usingCounters)
{
NativeMethods.QueryPerformanceCounter(ref startTime);
}
_store.Store<T>(key, (T)obj);
if (_usingCounters)
{
NativeMethods.QueryPerformanceCounter(ref endTime);
_instrumentation.RegisterStore(_durationGroup, obj, endTime - startTime, key);
}
}
}
finally
{
_store.ReleaseLock(key);
}
}
else
{
// Wait and try again
// after 500 ms, hopefully, another thread will have filled the cache
Thread.Sleep(500);
timesSlept++;
}
}
else
{
// Standard flow for miss: fetch and store
if (_usingCounters)
{
// log a miss
NativeMethods.QueryPerformanceCounter(ref endTime);
_instrumentation.RegisterMiss(_durationGroup, endTime - startTime, key);
startTime = endTime;
}
obj = fetchAction.Invoke();
if (_usingCounters)
{
// log a fetch
NativeMethods.QueryPerformanceCounter(ref endTime);
_instrumentation.RegisterFetch(_durationGroup, obj, endTime - startTime, key);
}
if (obj == null)
{
_store.Store(key, new NullValue());
return default(T);
}
if (needsStoring((T)obj))
{
if (_usingCounters) NativeMethods.QueryPerformanceCounter(ref startTime);
_store.Store<T>(key, (T)obj);
if (_usingCounters)
{
NativeMethods.QueryPerformanceCounter(ref endTime);
_instrumentation.RegisterStore(_durationGroup, obj, endTime - startTime, key);
}
}
}
}
else
{
// Get is (uiteindelijk) gelukt. We loggen een Hit
if (_usingCounters)
{
NativeMethods.QueryPerformanceCounter(ref endTime);
_instrumentation.RegisterHit(_durationGroup, obj, endTime - startTime, key);
}
if (HttpContext.Current != null && HttpContext.Current.Request.Url.Query.Contains("queryinfo") && QueryTraceHttpContextAppender.Current != null)
{
string storeName = _store.GetType().Name.Replace("Store", "");
QueryTraceHttpContextAppender.Current.AppendDirect(String.Format("{0}: [Key] {1}", storeName, key));
}
}
}
return (T)obj;
}
public class Instrumentation : IDisposable
{
private readonly ReaderWriterLockSlim _lockForDataPerThread = new ReaderWriterLockSlim();
DateTime _timeToClear;
readonly Dictionary<int, InstrumentationData> _dataPerThread = new Dictionary<int, InstrumentationData>();
public Instrumentation()
{
_timeToClear = DateTime.Now.AddMinutes(10);
}
public int SecondsRunning
{
get { return 600 - (int)(_timeToClear - DateTime.Now).TotalSeconds; }
}
private InstrumentationData GetData()
{
if (DateTime.Now > _timeToClear)
{
try
{
_lockForDataPerThread.EnterWriteLock();
if (DateTime.Now > _timeToClear)
{
_timeToClear = DateTime.Now.AddMinutes(10);
_dataPerThread.Clear();
}
}
finally
{
_lockForDataPerThread.ExitWriteLock();
}
}
int id = Thread.CurrentThread.ManagedThreadId;
try
{
_lockForDataPerThread.EnterReadLock();
if (_dataPerThread.ContainsKey(id))
{
return _dataPerThread[id];
}
}
finally
{
_lockForDataPerThread.ExitReadLock();
}
try
{
_lockForDataPerThread.EnterWriteLock();
if (!_dataPerThread.ContainsKey(id))
{
_dataPerThread[id] = new InstrumentationData();
}
return _dataPerThread[id];
}
finally
{
_lockForDataPerThread.ExitWriteLock();
}
}
public void RegisterHit(string group, object val, long ticks, string key)
{
InstrumentationData d = GetData();
d.Get(group).AddHit(key, MakeTypeName(val), ticks);
}
public void RegisterMiss(string group, long ticks, string key)
{
InstrumentationData d = GetData();
d.Get(group).AddMiss(key, ticks);
}
public void RegisterStore(string group, object val, long ticks, string key)
{
InstrumentationData d = GetData();
d.Get(group).AddStore(key, MakeTypeName(val), ticks);
}
public void RegisterFetch(string group, object val, long ticks, string key)
{
InstrumentationData d = GetData();
d.Get(group).AddFetch(key, MakeTypeName(val), ticks);
}
public InstrumentationData GetAggregatedData()
{
List<InstrumentationData> all = new List<InstrumentationData>(_dataPerThread.Values);
InstrumentationData temp = new InstrumentationData();
foreach (InstrumentationData threadData in all)
{
temp += threadData;
}
return temp;
}
private static string MakeTypeName(object val)
{
if (val == null)
{
return "null";
}
string result = val.GetType().Name;
if (val is string)
{
result += GetSizeAppend(((string)val).Length);
}
if (val is IList && ((IList)val).Count > 0 && ((IList)val)[0] != null && !(val is IDictionary) && !(val is Array))
{
IList a = (IList)val;
result += "[" + a[0].GetType().Name + "]";
}
if (val is ICollection)
{
result += GetSizeAppend(((ICollection)val).Count);
}
return result;
}
private static string GetSizeAppend(int num)
{
if (num <= 0)
{
return " (x=0)";
}
if (num <= 10)
{
return " (0<x<=10)";
}
if (num <= 100)
{
return " (10<x<=100)";
}
if (num <= 1000)
{
return " (100<x<=1.000)";
}
if (num <= 10000)
{
return " (1.000<x<=10.000)";
}
return " (10.000<x)";
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if(_lockForDataPerThread != null)_lockForDataPerThread.Dispose();
}
// free native resources
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public class InstumentationDataLine
{
internal long TotalTicks { get; set; }
internal long MaxTicks { get; set; }
public InstumentationDataLine(string type) { Type = type; }
public int Count { get; internal set; }
public string Type { get; private set; }
public double AverageTime
{
get
{
return _tickLength * (TotalTicks / Count);
}
}
public double MaxTime
{
get
{
return _tickLength * MaxTicks;
}
}
public void AddRegistration(long ticks)
{
Count++;
TotalTicks += ticks;
if (ticks > MaxTicks) MaxTicks = ticks;
}
public static InstumentationDataLine operator +(InstumentationDataLine d1, InstumentationDataLine d2)
{
if (d1.Type != d2.Type) throw new InvalidOperationException("Cannot add two InstumentationDataLine instances with different types");
InstumentationDataLine temp = d1.Clone();
temp.Count += d2.Count;
temp.TotalTicks += d2.TotalTicks;
temp.MaxTicks = Math.Max(d1.MaxTicks, d2.MaxTicks);
return temp;
}
public InstumentationDataLine Clone()
{
InstumentationDataLine temp = new InstumentationDataLine(Type)
{
Count = Count,
TotalTicks = TotalTicks,
MaxTicks = MaxTicks
};
return temp;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment