Mercurial > pub > bltoolkit
diff Source/Aspects/CacheAspect.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Aspects/CacheAspect.cs Thu Mar 27 21:46:09 2014 +0400 @@ -0,0 +1,535 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; + +namespace BLToolkit.Aspects +{ + using Common; + + public delegate bool IsCacheableParameterType(Type parameterType); + + /// <summary> + /// http://www.bltoolkit.net/Doc/Aspects/index.htm + /// </summary> + [System.Diagnostics.DebuggerStepThrough] + public class CacheAspect : Interceptor + { + #region Init + + static CacheAspect() + { + MaxCacheTime = int.MaxValue; + IsEnabled = true; + + CleanupThread.Init(); + } + + public CacheAspect() + { + _registeredAspects.Add(this); + } + + private MethodInfo _methodInfo; + private int? _instanceMaxCacheTime; + private bool? _instanceIsWeak; + + public override void Init(CallMethodInfo info, string configString) + { + base.Init(info, configString); + + info.CacheAspect = this; + + _methodInfo = info.MethodInfo; + + var ps = configString.Split(';'); + + foreach (var p in ps) + { + var vs = p.Split('='); + + if (vs.Length == 2) + { + switch (vs[0].ToLower().Trim()) + { + case "maxcachetime": _instanceMaxCacheTime = int. Parse(vs[1].Trim()); break; + case "isweak": _instanceIsWeak = bool.Parse(vs[1].Trim()); break; + } + } + } + } + + private static readonly IList _registeredAspects = ArrayList.Synchronized(new ArrayList()); + protected static IList RegisteredAspects + { + get { return _registeredAspects; } + } + + public static CacheAspect GetAspect(MethodInfo methodInfo) + { + lock (RegisteredAspects.SyncRoot) + foreach (CacheAspect aspect in RegisteredAspects) + if (aspect._methodInfo == methodInfo) + return aspect; + + return null; + } + + #endregion + + #region Overrides + + protected override void BeforeCall(InterceptCallInfo info) + { + if (!IsEnabled) + return; + + var cache = Cache; + + lock (cache.SyncRoot) + { + var key = GetKey(info); + var item = GetItem(cache, key); + + if (item != null && !item.IsExpired) + { + info.InterceptResult = InterceptResult.Return; + info.ReturnValue = item.ReturnValue; + + if (item.RefValues != null) + { + var pis = info.CallMethodInfo.Parameters; + var n = 0; + + for (var i = 0; i < pis.Length; i++) + if (pis[i].ParameterType.IsByRef) + info.ParameterValues[i] = item.RefValues[n++]; + } + + info.Cached = true; + } + else + { + info.Items["CacheKey"] = key; + } + } + } + + protected override void AfterCall(InterceptCallInfo info) + { + if (!IsEnabled) + return; + + var cache = Cache; + + lock (cache.SyncRoot) + { + var key = (CompoundValue)info.Items["CacheKey"]; + + if (key == null) + return; + + var maxCacheTime = _instanceMaxCacheTime ?? MaxCacheTime; + var isWeak = _instanceIsWeak ?? IsWeak; + + var item = new CacheAspectItem + { + ReturnValue = info.ReturnValue, + MaxCacheTime = maxCacheTime == int.MaxValue || maxCacheTime < 0 ? + DateTime.MaxValue : + DateTime.Now.AddMilliseconds(maxCacheTime), + }; + + var pis = info.CallMethodInfo.Parameters; + var n = 0; + + foreach (var pi in pis) + if (pi.ParameterType.IsByRef) + n++; + + if (n > 0) + { + item.RefValues = new object[n]; + + n = 0; + + for (var i = 0; i < pis.Length; i++) + if (pis[i].ParameterType.IsByRef) + item.RefValues[n++] = info.ParameterValues[i]; + } + + cache[key] = isWeak? (object)new WeakReference(item): item; + } + } + + #endregion + + #region Global Parameters + + public static bool IsEnabled { get; set; } + public static int MaxCacheTime { get; set; } + public static bool IsWeak { get; set; } + + #endregion + + #region IsCacheableParameterType + + private static IsCacheableParameterType _isCacheableParameterType = + IsCacheableParameterTypeInternal; + + public static IsCacheableParameterType IsCacheableParameterType + { + get { return _isCacheableParameterType; } + set { _isCacheableParameterType = value ?? IsCacheableParameterTypeInternal; } + } + + private static bool IsCacheableParameterTypeInternal(Type parameterType) + { + return parameterType.IsValueType || parameterType == typeof(string); + } + + #endregion + + #region Cache + + private IDictionary _cache; + public IDictionary Cache + { + get { return _cache ?? (_cache = CreateCache()); } + } + + protected virtual CacheAspectItem CreateCacheItem(InterceptCallInfo info) + { + return new CacheAspectItem(); + } + + protected virtual IDictionary CreateCache() + { + return Hashtable.Synchronized(new Hashtable()); + } + + protected static CompoundValue GetKey(InterceptCallInfo info) + { + var parInfo = info.CallMethodInfo.Parameters; + var parValues = info.ParameterValues; + var keyValues = new object[parValues.Length]; + var cacheParams = info.CallMethodInfo.CacheableParameters; + + if (cacheParams == null) + { + info.CallMethodInfo.CacheableParameters = cacheParams = new bool[parInfo.Length]; + + for (var i = 0; i < parInfo.Length; i++) + cacheParams[i] = IsCacheableParameterType(parInfo[i].ParameterType); + } + + for (var i = 0; i < parValues.Length; i++) + keyValues[i] = cacheParams[i] ? parValues[i] : null; + + return new CompoundValue(keyValues); + } + + protected static CacheAspectItem GetItem(IDictionary cache, CompoundValue key) + { + var obj = cache[key]; + + if (obj == null) + return null; + + var wr = obj as WeakReference; + + if (wr == null) + return (CacheAspectItem)obj; + + obj = wr.Target; + + if (obj != null) + return (CacheAspectItem)obj; + + cache.Remove(key); + + return null; + } + + /// <summary> + /// Clear a method call cache. + /// </summary> + /// <param name="methodInfo">The <see cref="MethodInfo"/> representing cached method.</param> + public static void ClearCache(MethodInfo methodInfo) + { + if (methodInfo == null) + throw new ArgumentNullException("methodInfo"); + + var aspect = GetAspect(methodInfo); + + if (aspect != null) + CleanupThread.ClearCache(aspect.Cache); + } + + /// <summary> + /// Clear a method call cache. + /// </summary> + /// <param name="declaringType">The method declaring type.</param> + /// <param name="methodName">The method name.</param> + /// <param name="types">An array of <see cref="System.Type"/> objects representing + /// the number, order, and type of the parameters for the method to get.-or- + /// An empty array of the type <see cref="System.Type"/> (for example, <see cref="System.Type.EmptyTypes"/>) + /// to get a method that takes no parameters.</param> + public static void ClearCache(Type declaringType, string methodName, params Type[] types) + { + ClearCache(GetMethodInfo(declaringType, methodName, types)); + } + + /// <summary> + /// Clear a method call cache. + /// </summary> + /// <param name="declaringType">The method declaring type.</param> + /// <param name="methodName">The method name.</param> + /// <param name="types">An array of <see cref="System.Type"/> objects representing + /// the number, order, and type of the parameters for the method to get.-or- + /// An empty array of the type <see cref="System.Type"/> (for example, <see cref="System.Type.EmptyTypes"/>) + /// to get a method that takes no parameters.</param> + /// <param name="values">An array of values of the parameters for the method to get</param> + public static void ClearCache(Type declaringType, string methodName, Type[] types, object[] values) + { + var methodInfo = GetMethodInfo(declaringType, methodName, types); + + if (methodInfo == null) + throw new ArgumentNullException("methodInfo"); + + var aspect = GetAspect(methodInfo); + + if (aspect != null) + CleanupThread.ClearCache(aspect.Cache, new CompoundValue(values)); + } + + public static void ClearCache(Type declaringType) + { + if (declaringType == null) + throw new ArgumentNullException("declaringType"); + + if (declaringType.IsAbstract) + declaringType = TypeBuilder.TypeFactory.GetType(declaringType); + + lock (RegisteredAspects.SyncRoot) + foreach (CacheAspect aspect in RegisteredAspects) + if (aspect._methodInfo.DeclaringType == declaringType) + CleanupThread.ClearCache(aspect.Cache); + } + + public static MethodInfo GetMethodInfo(Type declaringType, string methodName, params Type[] parameterTypes) + { + if (declaringType == null) + throw new ArgumentNullException("declaringType"); + + if (declaringType.IsAbstract) + declaringType = TypeBuilder.TypeFactory.GetType(declaringType); + + if (parameterTypes == null) + parameterTypes = Type.EmptyTypes; + + var methodInfo = declaringType.GetMethod( + methodName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + parameterTypes, + null); + + if (methodInfo == null) + { + methodInfo = declaringType.GetMethod( + methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + if (methodInfo == null) + throw new ArgumentException(string.Format("Method '{0}.{1}' not found.", + declaringType.FullName, methodName)); + } + + return methodInfo; + } + + /// <summary> + /// Clear all cached method calls. + /// </summary> + public static void ClearCache() + { + CleanupThread.ClearCache(); + } + + #endregion + + #region Cleanup Thread + + public class CleanupThread + { + private CleanupThread() {} + + internal static void Init() + { + AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload; + Start(); + } + + static void CurrentDomain_DomainUnload(object sender, EventArgs e) + { + Stop(); + } + + static volatile Timer _timer; + static readonly object _syncTimer = new object(); + + private static void Start() + { + if (_timer == null) + lock (_syncTimer) + if (_timer == null) + { + var interval = TimeSpan.FromSeconds(10); + _timer = new Timer(Cleanup, null, interval, interval); + } + } + + private static void Stop() + { + if (_timer != null) + lock (_syncTimer) + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + + private static void Cleanup(object state) + { + if (!Monitor.TryEnter(RegisteredAspects.SyncRoot, 10)) + { + // The Cache is busy, skip this turn. + // + return; + } + + var start = DateTime.Now; + var objectsInCache = 0; + + try + { + _workTimes++; + + var list = new List<DictionaryEntry>(); + + foreach (CacheAspect aspect in RegisteredAspects) + { + var cache = aspect.Cache; + + lock (cache.SyncRoot) + { + foreach (DictionaryEntry de in cache) + { + var wr = de.Value as WeakReference; + + bool isExpired; + + if (wr != null) + { + var ca = wr.Target as CacheAspectItem; + + isExpired = ca == null || ca.IsExpired; + } + else + { + isExpired = ((CacheAspectItem)de.Value).IsExpired; + } + + if (isExpired) + list.Add(de); + } + + foreach (var de in list) + { + cache.Remove(de.Key); + _objectsExpired++; + } + + list.Clear(); + + objectsInCache += cache.Count; + } + } + + _objectsInCache = objectsInCache; + } + finally + { + _workTime += DateTime.Now - start; + + Monitor.Exit(RegisteredAspects.SyncRoot); + } + } + + private static int _workTimes; + public static int WorkTimes + { + get { return _workTimes; } + } + + private static TimeSpan _workTime; + public static TimeSpan WorkTime + { + get { return _workTime; } + } + + private static int _objectsExpired; + public static int ObjectsExpired + { + get { return _objectsExpired; } + } + + private static int _objectsInCache; + public static int ObjectsInCache + { + get { return _objectsInCache; } + } + + public static void UnregisterCache(IDictionary cache) + { + lock (RegisteredAspects.SyncRoot) + RegisteredAspects.Remove(cache); + } + + public static void ClearCache(IDictionary cache) + { + lock (RegisteredAspects.SyncRoot) lock (cache.SyncRoot) + { + _objectsExpired += cache.Count; + cache.Clear(); + } + } + + public static void ClearCache(IDictionary cache, CompoundValue key) + { + lock (RegisteredAspects.SyncRoot) + lock (cache.SyncRoot) + { + _objectsExpired += 1; + cache.Remove(key); + } + } + + public static void ClearCache() + { + lock (RegisteredAspects.SyncRoot) + { + foreach (CacheAspect aspect in RegisteredAspects) + { + _objectsExpired += aspect.Cache.Count; + aspect.Cache.Clear(); + } + } + } + } + + #endregion + } +}