-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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<> 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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