From f6642ee2ba9cac2ddc6d163b80f45c9f6554df0a Mon Sep 17 00:00:00 2001 From: stakx Date: Wed, 20 Jun 2018 23:13:33 +0200 Subject: [PATCH 01/11] Replace `Lock` in `ProxyUtil` Instead of Castle's own `Lock` abstraction, just use the underlying `ReaderWriterLockSlim` directly. --- src/Castle.Core/DynamicProxy/ProxyUtil.cs | 39 ++++++++++++++--------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/ProxyUtil.cs b/src/Castle.Core/DynamicProxy/ProxyUtil.cs index d849d9c092..8216080a0f 100644 --- a/src/Castle.Core/DynamicProxy/ProxyUtil.cs +++ b/src/Castle.Core/DynamicProxy/ProxyUtil.cs @@ -19,17 +19,16 @@ namespace Castle.DynamicProxy using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; + using System.Threading; #if FEATURE_REMOTING using System.Runtime.Remoting; #endif - using Castle.Core.Internal; - public static class ProxyUtil { private static readonly IDictionary internalsVisibleToDynamicProxy = new Dictionary(); - private static readonly Lock internalsVisibleToDynamicProxyLock = Lock.Create(); + private static readonly ReaderWriterLockSlim internalsVisibleToDynamicProxyLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); public static object GetUnproxiedInstance(object instance) { @@ -131,30 +130,40 @@ public static bool IsAccessible(Type type) /// The assembly to inspect. internal static bool AreInternalsVisibleToDynamicProxy(Assembly asm) { - using (var locker = internalsVisibleToDynamicProxyLock.ForReading()) + bool result; + + internalsVisibleToDynamicProxyLock.EnterReadLock(); + try { - if (internalsVisibleToDynamicProxy.ContainsKey(asm)) + if (internalsVisibleToDynamicProxy.TryGetValue(asm, out result)) { - return internalsVisibleToDynamicProxy[asm]; + return result; } } + finally + { + internalsVisibleToDynamicProxyLock.ExitReadLock(); + } - using (var locker = internalsVisibleToDynamicProxyLock.ForReadingUpgradeable()) + internalsVisibleToDynamicProxyLock.EnterWriteLock(); + try { - if (internalsVisibleToDynamicProxy.ContainsKey(asm)) + if (internalsVisibleToDynamicProxy.TryGetValue(asm, out result)) { - return internalsVisibleToDynamicProxy[asm]; + return result; } - - // Upgrade the lock to a write lock. - using (locker.Upgrade()) + else { var internalsVisibleTo = asm.GetCustomAttributes(); - var found = internalsVisibleTo.Any(attr => attr.AssemblyName.Contains(ModuleScope.DEFAULT_ASSEMBLY_NAME)); - internalsVisibleToDynamicProxy.Add(asm, found); - return found; + result = internalsVisibleTo.Any(attr => attr.AssemblyName.Contains(ModuleScope.DEFAULT_ASSEMBLY_NAME)); + internalsVisibleToDynamicProxy.Add(asm, result); + return result; } } + finally + { + internalsVisibleToDynamicProxyLock.ExitWriteLock(); + } } internal static bool IsAccessibleType(Type target) From 0606a55555e5eede2b115a475919406061c8d17d Mon Sep 17 00:00:00 2001 From: stakx Date: Wed, 20 Jun 2018 23:24:21 +0200 Subject: [PATCH 02/11] Replace `Lock` in `InvocationHelper` Instead of Castle's own `Lock` abstraction, just use the underlying `ReaderWriterLockSlim` directly. --- .../DynamicProxy/Internal/InvocationHelper.cs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs b/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs index b8d846d1bd..13aea9da4d 100644 --- a/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs +++ b/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs @@ -18,8 +18,8 @@ namespace Castle.DynamicProxy.Internal using System.Collections.Generic; using System.Diagnostics; using System.Reflection; + using System.Threading; - using Castle.Core.Internal; using Castle.DynamicProxy.Generators; public static class InvocationHelper @@ -27,7 +27,8 @@ public static class InvocationHelper private static readonly Dictionary cache = new Dictionary(); - private static readonly Lock @lock = Lock.Create(); + private static readonly ReaderWriterLockSlim cacheLock = + new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); public static MethodInfo GetMethodOnObject(object target, MethodInfo proxiedMethod) { @@ -48,39 +49,42 @@ public static MethodInfo GetMethodOnType(Type type, MethodInfo proxiedMethod) Debug.Assert(proxiedMethod.DeclaringType.IsAssignableFrom(type), "proxiedMethod.DeclaringType.IsAssignableFrom(type)"); - using (var locker = @lock.ForReading()) + + var cacheKey = new CacheKey(proxiedMethod, type); + + MethodInfo methodOnTarget; + + cacheLock.EnterReadLock(); + try { - var methodOnTarget = GetFromCache(proxiedMethod, type); - if (methodOnTarget != null) + if (cache.TryGetValue(cacheKey, out methodOnTarget)) { return methodOnTarget; } } + finally + { + cacheLock.ExitReadLock(); + } - using (var locker = @lock.ForReadingUpgradeable()) + cacheLock.EnterWriteLock(); + try { - var methodOnTarget = GetFromCache(proxiedMethod, type); - if (methodOnTarget != null) + if (cache.TryGetValue(cacheKey, out methodOnTarget)) { return methodOnTarget; } - - // Upgrade the lock to a write lock. - using (locker.Upgrade()) + else { methodOnTarget = ObtainMethod(proxiedMethod, type); - PutToCache(proxiedMethod, type, methodOnTarget); + cache.Add(cacheKey, methodOnTarget); + return methodOnTarget; } - return methodOnTarget; } - } - - private static MethodInfo GetFromCache(MethodInfo methodInfo, Type type) - { - var key = new CacheKey(methodInfo, type); - MethodInfo method; - cache.TryGetValue(key, out method); - return method; + finally + { + cacheLock.ExitWriteLock(); + } } private static MethodInfo ObtainMethod(MethodInfo proxiedMethod, Type type) @@ -127,12 +131,6 @@ private static MethodInfo ObtainMethod(MethodInfo proxiedMethod, Type type) return methodOnTarget.MakeGenericMethod(genericArguments); } - private static void PutToCache(MethodInfo methodInfo, Type type, MethodInfo value) - { - var key = new CacheKey(methodInfo, type); - cache.Add(key, value); - } - private struct CacheKey : IEquatable { public CacheKey(MethodInfo method, Type type) From e0ca27288541cecd7006623f545fede431f31aaa Mon Sep 17 00:00:00 2001 From: stakx Date: Wed, 20 Jun 2018 23:29:36 +0200 Subject: [PATCH 03/11] Replace `Lock` in `DictionaryAdapterFactory` Instead of Castle's own `Lock` abstraction, just use the underlying `ReaderWriterLockSlim` directly. --- .../DictionaryAdapterFactory.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs b/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs index 3371cd1d82..451a646f66 100644 --- a/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs +++ b/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs @@ -38,7 +38,7 @@ public class DictionaryAdapterFactory : IDictionaryAdapterFactory { private readonly Dictionary interfaceToMeta = new Dictionary(); - private readonly Lock interfaceToMetaLock = Lock.Create(); + private readonly ReaderWriterLockSlim interfaceToMetaLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); #region IDictionaryAdapterFactory @@ -132,21 +132,32 @@ private DictionaryAdapterMeta InternalGetAdapterMeta(Type type, DictionaryAdapterMeta meta; - using (interfaceToMetaLock.ForReading()) + interfaceToMetaLock.EnterReadLock(); + try { if (interfaceToMeta.TryGetValue(type, out meta)) + { return meta; + } + } + finally + { + interfaceToMetaLock.ExitReadLock(); } - using (var heldLock = interfaceToMetaLock.ForReadingUpgradeable()) + interfaceToMetaLock.EnterWriteLock(); + try { if (interfaceToMeta.TryGetValue(type, out meta)) + { return meta; - - using (heldLock.Upgrade()) + } + else { if (descriptor == null && other != null) + { descriptor = other.CreateDescriptor(); + } #if FEATURE_LEGACY_REFLECTION_API var appDomain = Thread.GetDomain(); @@ -159,6 +170,10 @@ private DictionaryAdapterMeta InternalGetAdapterMeta(Type type, return meta; } } + finally + { + interfaceToMetaLock.ExitWriteLock(); + } } private object InternalGetAdapter(Type type, IDictionary dictionary, PropertyDescriptor descriptor) From 0b37d14c8bc69ae6f5a535389431475eb802a026 Mon Sep 17 00:00:00 2001 From: stakx Date: Thu, 21 Jun 2018 01:20:02 +0200 Subject: [PATCH 04/11] Replace `Lock` in `SingletonDispenser` --- .../Internal/Utilities/SingletonDispenser.cs | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs b/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs index 3d4cefdfa7..726b7adc8a 100644 --- a/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs +++ b/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs @@ -18,12 +18,11 @@ namespace Castle.Components.DictionaryAdapter.Xml using System; using System.Collections.Generic; using System.Threading; - using Castle.Core.Internal; public class SingletonDispenser where TItem : class { - private readonly Lock locker; + private readonly ReaderWriterLockSlim locker; private readonly Dictionary items; private readonly Func factory; @@ -32,7 +31,7 @@ public SingletonDispenser(Func factory) if (factory == null) throw Error.ArgumentNull("factory"); - this.locker = new SlimReadWriteLock(); + this.locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); this.items = new Dictionary(); this.factory = factory; } @@ -53,22 +52,36 @@ private TItem GetOrCreate(TKey key) private bool TryGetExistingItem(TKey key, out object item) { - using (locker.ForReading()) + locker.EnterReadLock(); + try { if (items.TryGetValue(key, out item)) + { return true; + } + } + finally + { + locker.ExitReadLock(); } - using (var hold = locker.ForReadingUpgradeable()) + locker.EnterWriteLock(); + try { if (items.TryGetValue(key, out item)) + { return true; - - using (hold.Upgrade()) + } + else + { items[key] = item = new ManualResetEvent(false); + return false; + } + } + finally + { + locker.ExitWriteLock(); } - - return false; } private TItem WaitForCreate(TKey key, object item) @@ -77,8 +90,15 @@ private TItem WaitForCreate(TKey key, object item) handle.WaitOne(); - using (locker.ForReading()) - return (TItem) items[key]; + locker.EnterReadLock(); + try + { + return (TItem)items[key]; + } + finally + { + locker.ExitReadLock(); + } } private TItem Create(TKey key, object item) @@ -87,8 +107,15 @@ private TItem Create(TKey key, object item) var result = factory(key); - using (locker.ForWriting()) + locker.EnterWriteLock(); + try + { items[key] = result; + } + finally + { + locker.ExitWriteLock(); + } handle.Set(); return result; From 88c80a26344b7922ea8167087279927d25be6a7c Mon Sep 17 00:00:00 2001 From: stakx Date: Thu, 21 Jun 2018 00:05:14 +0200 Subject: [PATCH 05/11] Replace internal use of `Lock` in `ModuleScope` Unfortunately, `ModuleScope` exposes a `Lock` for its proxy type cache publicly. This means we cannot just remove its `Lock` completely, but we can start replacing it with a `ReaderWriterLockSlim` internally. A new `Lock.CreateFor(ReaderWriterLockSlim)` method is needed so that a `Lock` can be exposed that is mapped to the `ReaderWriterLockSlim` that is used internally. --- src/Castle.Core/Core/Internal/Lock.cs | 7 +++++++ .../Core/Internal/SlimReadWriteLock.cs | 12 +++++++++++- src/Castle.Core/DynamicProxy/ModuleScope.cs | 15 +++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Castle.Core/Core/Internal/Lock.cs b/src/Castle.Core/Core/Internal/Lock.cs index 079420df80..96e26573c9 100644 --- a/src/Castle.Core/Core/Internal/Lock.cs +++ b/src/Castle.Core/Core/Internal/Lock.cs @@ -14,6 +14,8 @@ namespace Castle.Core.Internal { + using System.Threading; + public abstract class Lock { public abstract IUpgradeableLockHolder ForReadingUpgradeable(); @@ -32,5 +34,10 @@ public static Lock Create() { return new SlimReadWriteLock(); } + + internal static Lock CreateFor(ReaderWriterLockSlim underlyingLock) + { + return new SlimReadWriteLock(underlyingLock); + } } } diff --git a/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs b/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs index 3aa07c5651..3b854858ad 100644 --- a/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs +++ b/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs @@ -18,7 +18,17 @@ namespace Castle.Core.Internal internal class SlimReadWriteLock : Lock { - private readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + private readonly ReaderWriterLockSlim locker; + + public SlimReadWriteLock() + { + this.locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + } + + internal SlimReadWriteLock(ReaderWriterLockSlim underlyingLock) + { + this.locker = underlyingLock; + } public override IUpgradeableLockHolder ForReadingUpgradeable() { diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index 2dab5be652..8240fbc94a 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -16,10 +16,12 @@ namespace Castle.DynamicProxy { using System; using System.Collections.Generic; + using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Resources; + using System.Threading; using Castle.Core.Internal; using Castle.DynamicProxy.Generators; @@ -55,7 +57,8 @@ public class ModuleScope private readonly Dictionary typeCache = new Dictionary(); // Users of ModuleScope should use this lock when accessing the cache - private readonly Lock cacheLock = Lock.Create(); + private readonly Lock cacheLock; + private readonly ReaderWriterLockSlim typeCacheLock; // Used to lock the module builder creation private readonly object moduleLocker = new object(); @@ -142,6 +145,9 @@ public ModuleScope(bool savePhysicalAssembly, bool disableSignedModule, INamingS this.strongModulePath = strongModulePath; this.weakAssemblyName = weakAssemblyName; this.weakModulePath = weakModulePath; + + this.typeCacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + this.cacheLock = Lock.CreateFor(this.typeCacheLock); } public INamingScope NamingScope @@ -514,7 +520,8 @@ private void AddCacheMappings(AssemblyBuilder builder) { Dictionary mappings; - using (Lock.ForReading()) + this.typeCacheLock.EnterReadLock(); + try { mappings = new Dictionary(); foreach (var cacheEntry in typeCache) @@ -527,6 +534,10 @@ private void AddCacheMappings(AssemblyBuilder builder) } } } + finally + { + this.typeCacheLock.ExitReadLock(); + } CacheMappingsAttribute.ApplyTo(builder, mappings); } From 91b091cccf1eaae499094213198bd1daecae540e Mon Sep 17 00:00:00 2001 From: stakx Date: Thu, 21 Jun 2018 00:38:29 +0200 Subject: [PATCH 06/11] Replace `Scope.Lock` in `BaseProxyGenerator` This commit removes the last actual usage of a `Lock` inside the library. --- .../Generators/BaseProxyGenerator.cs | 37 ++++---------- src/Castle.Core/DynamicProxy/ModuleScope.cs | 51 +++++++++++++++++++ 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs index 1e76e725b9..af4d1b5d57 100644 --- a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs @@ -384,40 +384,25 @@ protected void InitializeStaticFields(Type builtType) protected Type ObtainProxyType(CacheKey cacheKey, Func factory) { - Type cacheType; - using (var locker = Scope.Lock.ForReading()) - { - cacheType = GetFromCache(cacheKey); - if (cacheType != null) - { - Logger.DebugFormat("Found cached proxy type {0} for target type {1}.", cacheType.FullName, targetType.FullName); - return cacheType; - } - } + bool notFoundInTypeCache = false; - // This is to avoid generating duplicate types under heavy multithreaded load. - using (var locker = Scope.Lock.ForWriting()) + var proxyType = Scope.GetOrAddToCache(cacheKey, _ => { - // Only one thread at a time may enter a write lock. - // See if an earlier lock holder populated the cache. - cacheType = GetFromCache(cacheKey); - if (cacheType != null) - { - Logger.DebugFormat("Found cached proxy type {0} for target type {1}.", cacheType.FullName, targetType.FullName); - return cacheType; - } - - // Log details about the cache miss + notFoundInTypeCache = true; Logger.DebugFormat("No cached proxy type was found for target type {0}.", targetType.FullName); + EnsureOptionsOverrideEqualsAndGetHashCode(ProxyGenerationOptions); var name = Scope.NamingScope.GetUniqueName("Castle.Proxies." + targetType.Name + "Proxy"); - var proxyType = factory.Invoke(name, Scope.NamingScope.SafeSubScope()); - - AddToCache(cacheKey, proxyType); + return factory.Invoke(name, Scope.NamingScope.SafeSubScope()); + }); - return proxyType; + if (!notFoundInTypeCache) + { + Logger.DebugFormat("Found cached proxy type {0} for target type {1}.", proxyType.FullName, targetType.FullName); } + + return proxyType; } private bool IsConstructorVisible(ConstructorInfo constructor) diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index 8240fbc94a..43a42b4c7d 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -185,6 +185,57 @@ public void RegisterInCache(CacheKey key, Type type) typeCache[key] = type; } + /// + /// Returns a type from this scope's type cache, or adds it to the cache if the key cannot be found. + /// + /// The key to be looked up in the cache. + /// A function producing the type to be added to the cache. + /// The type from this scope's type cache matching the key, or null if the key cannot be found + /// + /// This method synchronizes accesses to this scope's type cache using read/write locks. + /// If the specified key cannot be found, the factory function is invoked while a write lock is held. + /// The function must not access this scope's type cache. + /// + internal Type GetOrAddToCache(CacheKey key, Func valueFactory) + { + Type value; + + Debug.Assert(key != null); + Debug.Assert(valueFactory != null); + + typeCacheLock.EnterReadLock(); + try + { + if (typeCache.TryGetValue(key, out value)) + { + return value; + } + } + finally + { + typeCacheLock.ExitReadLock(); + } + + typeCacheLock.EnterWriteLock(); + try + { + if (typeCache.TryGetValue(key, out value)) + { + return value; + } + else + { + value = valueFactory.Invoke(key); + typeCache.Add(key, value); + return value; + } + } + finally + { + typeCacheLock.ExitWriteLock(); + } + } + /// /// Gets the key pair used to sign the strong-named assembly generated by this . /// From 41c0dd200b3bdf169ea26fe5a41107bb1bff0f60 Mon Sep 17 00:00:00 2001 From: stakx Date: Thu, 21 Jun 2018 01:01:16 +0200 Subject: [PATCH 07/11] Add non-locking variant of `GetOrAddToCache` Before `Lock` can be obsoleted, the APIs connected with it need some updating. This commit will replace most uses of `ModuleScope`'s `Get- FromCache` and `RegisterInCache.` This commit introduces an unsynchronized version of the previously added `GetOrAddToCache`. (These methods combine `GetFromCache` and `RegisterInCache` into a single operation. Their signatures are inspired by `ConcurrentDictionary<,>.GetOrAdd`.) It can be used in many code locations where both the existing methods were previously called separately. This leads to simpler code and to the possibility of making these type cache queries/updates truly atomic by changing a single method, if that need should arise. --- .../ClassProxyTargetContributor.cs | 15 ++----- .../ClassProxyWithTargetTargetContributor.cs | 26 ++---------- .../DelegateProxyTargetContributor.cs | 22 ++++------ .../InterfaceProxyTargetContributor.cs | 24 ++++------- .../InterfaceProxyWithoutTargetContributor.cs | 23 ++++------- .../Contributors/MixinContributor.cs | 23 ++++------- src/Castle.Core/DynamicProxy/ModuleScope.cs | 41 ++++++++++++++----- 7 files changed, 67 insertions(+), 107 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs index bcb0e75382..315cef0a3a 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs @@ -179,19 +179,10 @@ private Type GetDelegateType(MetaMethod method, ClassEmitter @class, ProxyGenera ToArray(), null); - var type = scope.GetFromCache(key); - if (type != null) - { - return type; - } - - type = new DelegateTypeGenerator(method, targetType) + return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + new DelegateTypeGenerator(method, targetType) .Generate(@class, options, namingScope) - .BuildType(); - - scope.RegisterInCache(key, type); - - return type; + .BuildType()); } private Type GetInvocationType(MetaMethod method, ClassEmitter @class, ProxyGenerationOptions options) diff --git a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs index df0dd08ef9..11e461b217 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs @@ -126,19 +126,10 @@ private Type GetDelegateType(MetaMethod method, ClassEmitter @class, ProxyGenera ToArray(), null); - var type = scope.GetFromCache(key); - if (type != null) - { - return type; - } - - type = new DelegateTypeGenerator(method, targetType) + return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + new DelegateTypeGenerator(method, targetType) .Generate(@class, options, namingScope) - .BuildType(); - - scope.RegisterInCache(key, type); - - return type; + .BuildType()); } private Type GetInvocationType(MetaMethod method, ClassEmitter @class, ProxyGenerationOptions options) @@ -150,16 +141,7 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter @class, ProxyGene // no locking required as we're already within a lock - var invocation = scope.GetFromCache(key); - if (invocation != null) - { - return invocation; - } - invocation = BuildInvocationType(method, @class, options); - - scope.RegisterInCache(key, invocation); - - return invocation; + return scope.GetOrAddToCacheWithoutTakingLock(key, _ => BuildInvocationType(method, @class, options)); } private MethodGenerator IndirectlyCalledMethodGenerator(MetaMethod method, ClassEmitter proxy, diff --git a/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs index b6db660c5d..d0e6a6249f 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs @@ -57,23 +57,15 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter, ProxyGen var key = new CacheKey(method.Method, CompositionInvocationTypeGenerator.BaseType, null, null); // no locking required as we're already within a lock - var invocation = scope.GetFromCache(key); - if (invocation != null) - { - return invocation; - } - invocation = new CompositionInvocationTypeGenerator(method.Method.DeclaringType, - method, - method.Method, - false, - null) + return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + new CompositionInvocationTypeGenerator(method.Method.DeclaringType, + method, + method.Method, + false, + null) .Generate(emitter, options, namingScope) - .BuildType(); - - scope.RegisterInCache(key, invocation); - - return invocation; + .BuildType()); } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs index b81be4bb47..7f2f9202a2 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs @@ -92,22 +92,14 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter @class, ProxyGene // no locking required as we're already within a lock - var invocation = scope.GetFromCache(key); - if (invocation != null) - { - return invocation; - } - - invocation = new CompositionInvocationTypeGenerator(method.Method.DeclaringType, - method, - method.Method, - canChangeTarget, - null) - .Generate(@class, options, namingScope).BuildType(); - - scope.RegisterInCache(key, invocation); - - return invocation; + return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + new CompositionInvocationTypeGenerator(method.Method.DeclaringType, + method, + method.Method, + canChangeTarget, + null) + .Generate(@class, options, namingScope) + .BuildType()); } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs index c537169df9..e70d0907a3 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs @@ -77,23 +77,14 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter, ProxyGen // no locking required as we're already within a lock - var invocation = scope.GetFromCache(key); - if (invocation != null) - { - return invocation; - } - - invocation = new CompositionInvocationTypeGenerator(method.Method.DeclaringType, - method, - method.Method, - canChangeTarget, - null) + return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + new CompositionInvocationTypeGenerator(method.Method.DeclaringType, + method, + method.Method, + canChangeTarget, + null) .Generate(emitter, options, namingScope) - .BuildType(); - - scope.RegisterInCache(key, invocation); - - return invocation; + .BuildType()); } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs index cbd7d39a05..af414557c5 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs @@ -132,23 +132,14 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter, ProxyGen // no locking required as we're already within a lock - var invocation = scope.GetFromCache(key); - if (invocation != null) - { - return invocation; - } - - invocation = new CompositionInvocationTypeGenerator(method.Method.DeclaringType, - method, - method.Method, - canChangeTarget, - null) + return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + new CompositionInvocationTypeGenerator(method.Method.DeclaringType, + method, + method.Method, + canChangeTarget, + null) .Generate(emitter, options, namingScope) - .BuildType(); - - scope.RegisterInCache(key, invocation); - - return invocation; + .BuildType()); } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index 43a42b4c7d..35d1de477f 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -185,6 +185,36 @@ public void RegisterInCache(CacheKey key, Type type) typeCache[key] = type; } + /// + /// Returns a type from this scope's type cache, or adds it to the cache if the key cannot be found. + /// + /// The key to be looked up in the cache. + /// A function producing the type to be added to the cache. + /// The type from this scope's type cache matching the key, or null if the key cannot be found + /// + /// This method does not synchronize access to this scope's type cache, i.e. no read/write locks are taken. + /// Only use this method when you know for sure that a method further up in the call stack already holds + /// a write lock. The function must not access this scope's type cache. + /// + internal Type GetOrAddToCacheWithoutTakingLock(CacheKey key, Func valueFactory) + { + Type value; + + Debug.Assert(key != null); + Debug.Assert(valueFactory != null); + + if (typeCache.TryGetValue(key, out value)) + { + return value; + } + else + { + value = valueFactory.Invoke(key); + typeCache.Add(key, value); + return value; + } + } + /// /// Returns a type from this scope's type cache, or adds it to the cache if the key cannot be found. /// @@ -219,16 +249,7 @@ internal Type GetOrAddToCache(CacheKey key, Func valueFactory) typeCacheLock.EnterWriteLock(); try { - if (typeCache.TryGetValue(key, out value)) - { - return value; - } - else - { - value = valueFactory.Invoke(key); - typeCache.Add(key, value); - return value; - } + return GetOrAddToCacheWithoutTakingLock(key, valueFactory); } finally { From 888555828660242b57fad13fea241f6ea03345c2 Mon Sep 17 00:00:00 2001 From: stakx Date: Thu, 21 Jun 2018 01:39:29 +0200 Subject: [PATCH 08/11] Deprecate `Lock` and all related members --- buildscripts/common.props | 1 + .../Core.Tests/Internal/SlimReadWriteLockTestCase.cs | 2 ++ src/Castle.Core/Core/Internal/ILockHolder.cs | 3 +++ .../Core/Internal/IUpgradeableLockHolder.cs | 5 +++++ src/Castle.Core/Core/Internal/Lock.cs | 4 ++++ src/Castle.Core/Core/Internal/NoOpLock.cs | 5 +++++ src/Castle.Core/Core/Internal/NoOpUpgradeableLock.cs | 5 +++++ src/Castle.Core/Core/Internal/SlimReadLockHolder.cs | 4 ++++ src/Castle.Core/Core/Internal/SlimReadWriteLock.cs | 4 ++++ .../Core/Internal/SlimUpgradeableReadLockHolder.cs | 4 ++++ src/Castle.Core/Core/Internal/SlimWriteLockHolder.cs | 4 ++++ .../DynamicProxy/Generators/BaseProxyGenerator.cs | 7 +++++++ src/Castle.Core/DynamicProxy/Generators/CacheKey.cs | 3 +++ src/Castle.Core/DynamicProxy/ModuleScope.cs | 11 ++++++++++- .../Serialization/CacheMappingsAttribute.cs | 5 +++++ 15 files changed, 66 insertions(+), 1 deletion(-) diff --git a/buildscripts/common.props b/buildscripts/common.props index 26cc063233..066ebb4646 100644 --- a/buildscripts/common.props +++ b/buildscripts/common.props @@ -2,6 +2,7 @@ $(NoWarn);CS1591;CS3014;CS3003;CS3001;CS3021 + $(NoWarn);CS0612;CS0618 git https://github.com/castleproject/Core 0.0.0 diff --git a/src/Castle.Core.Tests/Core.Tests/Internal/SlimReadWriteLockTestCase.cs b/src/Castle.Core.Tests/Core.Tests/Internal/SlimReadWriteLockTestCase.cs index b3fb92b116..79a381aeb5 100644 --- a/src/Castle.Core.Tests/Core.Tests/Internal/SlimReadWriteLockTestCase.cs +++ b/src/Castle.Core.Tests/Core.Tests/Internal/SlimReadWriteLockTestCase.cs @@ -14,11 +14,13 @@ namespace Castle.Core.Internal.Tests { + using System; using System.Threading; using NUnit.Framework; [TestFixture] + [Obsolete] public class SlimReadWriteLockTestCase { private SlimReadWriteLock @lock; diff --git a/src/Castle.Core/Core/Internal/ILockHolder.cs b/src/Castle.Core/Core/Internal/ILockHolder.cs index 01c99d9ea8..0e54dd03ca 100644 --- a/src/Castle.Core/Core/Internal/ILockHolder.cs +++ b/src/Castle.Core/Core/Internal/ILockHolder.cs @@ -15,7 +15,10 @@ namespace Castle.Core.Internal { using System; + using System.ComponentModel; + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] public interface ILockHolder:IDisposable { bool LockAcquired { get; } diff --git a/src/Castle.Core/Core/Internal/IUpgradeableLockHolder.cs b/src/Castle.Core/Core/Internal/IUpgradeableLockHolder.cs index f64b779281..cc1f3c5699 100644 --- a/src/Castle.Core/Core/Internal/IUpgradeableLockHolder.cs +++ b/src/Castle.Core/Core/Internal/IUpgradeableLockHolder.cs @@ -14,6 +14,11 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; + + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] public interface IUpgradeableLockHolder : ILockHolder { ILockHolder Upgrade(); diff --git a/src/Castle.Core/Core/Internal/Lock.cs b/src/Castle.Core/Core/Internal/Lock.cs index 96e26573c9..a33b401619 100644 --- a/src/Castle.Core/Core/Internal/Lock.cs +++ b/src/Castle.Core/Core/Internal/Lock.cs @@ -14,8 +14,12 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; using System.Threading; + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] public abstract class Lock { public abstract IUpgradeableLockHolder ForReadingUpgradeable(); diff --git a/src/Castle.Core/Core/Internal/NoOpLock.cs b/src/Castle.Core/Core/Internal/NoOpLock.cs index db24141d95..c38dd1654e 100644 --- a/src/Castle.Core/Core/Internal/NoOpLock.cs +++ b/src/Castle.Core/Core/Internal/NoOpLock.cs @@ -14,6 +14,11 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; + + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] internal class NoOpLock : ILockHolder { public static readonly ILockHolder Lock = new NoOpLock(); diff --git a/src/Castle.Core/Core/Internal/NoOpUpgradeableLock.cs b/src/Castle.Core/Core/Internal/NoOpUpgradeableLock.cs index c8bbd0f4dc..e350fcfbb2 100644 --- a/src/Castle.Core/Core/Internal/NoOpUpgradeableLock.cs +++ b/src/Castle.Core/Core/Internal/NoOpUpgradeableLock.cs @@ -14,6 +14,11 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; + + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] internal class NoOpUpgradeableLock : IUpgradeableLockHolder { public static readonly IUpgradeableLockHolder Lock = new NoOpUpgradeableLock(); diff --git a/src/Castle.Core/Core/Internal/SlimReadLockHolder.cs b/src/Castle.Core/Core/Internal/SlimReadLockHolder.cs index 698f956cc4..c21471c163 100644 --- a/src/Castle.Core/Core/Internal/SlimReadLockHolder.cs +++ b/src/Castle.Core/Core/Internal/SlimReadLockHolder.cs @@ -14,8 +14,12 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; using System.Threading; + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] internal class SlimReadLockHolder : ILockHolder { private readonly ReaderWriterLockSlim locker; diff --git a/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs b/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs index 3b854858ad..ed239fd185 100644 --- a/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs +++ b/src/Castle.Core/Core/Internal/SlimReadWriteLock.cs @@ -14,8 +14,12 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; using System.Threading; + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] internal class SlimReadWriteLock : Lock { private readonly ReaderWriterLockSlim locker; diff --git a/src/Castle.Core/Core/Internal/SlimUpgradeableReadLockHolder.cs b/src/Castle.Core/Core/Internal/SlimUpgradeableReadLockHolder.cs index 67ced743f3..593c95e690 100644 --- a/src/Castle.Core/Core/Internal/SlimUpgradeableReadLockHolder.cs +++ b/src/Castle.Core/Core/Internal/SlimUpgradeableReadLockHolder.cs @@ -14,8 +14,12 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; using System.Threading; + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] internal class SlimUpgradeableReadLockHolder : IUpgradeableLockHolder { private readonly ReaderWriterLockSlim locker; diff --git a/src/Castle.Core/Core/Internal/SlimWriteLockHolder.cs b/src/Castle.Core/Core/Internal/SlimWriteLockHolder.cs index ae86ffe957..c46018fed1 100644 --- a/src/Castle.Core/Core/Internal/SlimWriteLockHolder.cs +++ b/src/Castle.Core/Core/Internal/SlimWriteLockHolder.cs @@ -14,8 +14,12 @@ namespace Castle.Core.Internal { + using System; + using System.ComponentModel; using System.Threading; + [Obsolete("Consider using `System.Threading.ReaderWriterLockSlim` instead of `Lock` and related types.")] // TODO: Remove this type. + [EditorBrowsable(EditorBrowsableState.Never)] internal class SlimWriteLockHolder : ILockHolder { private readonly ReaderWriterLockSlim locker; diff --git a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs index af4d1b5d57..49d6c39e78 100644 --- a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs @@ -16,6 +16,7 @@ namespace Castle.DynamicProxy.Generators { using System; using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -111,6 +112,8 @@ protected void AddMappingNoCheck(Type @interface, ITypeContributor implementer, mapping.Add(@interface, implementer); } + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Remove this method. + [EditorBrowsable(EditorBrowsableState.Never)] protected void AddToCache(CacheKey key, Type type) { scope.RegisterInCache(key, type); @@ -335,6 +338,8 @@ protected ConstructorEmitter GenerateStaticConstructor(ClassEmitter emitter) return emitter.CreateTypeConstructor(); } + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Remove this method. + [EditorBrowsable(EditorBrowsableState.Never)] protected Type GetFromCache(CacheKey key) { return scope.GetFromCache(key); @@ -382,6 +387,8 @@ protected void InitializeStaticFields(Type builtType) builtType.SetStaticField("proxyGenerationOptions", BindingFlags.NonPublic, ProxyGenerationOptions); } + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Redeclare this method as `private protected`. + [EditorBrowsable(EditorBrowsableState.Never)] protected Type ObtainProxyType(CacheKey cacheKey, Func factory) { bool notFoundInTypeCache = false; diff --git a/src/Castle.Core/DynamicProxy/Generators/CacheKey.cs b/src/Castle.Core/DynamicProxy/Generators/CacheKey.cs index 43c3950471..e91241f015 100644 --- a/src/Castle.Core/DynamicProxy/Generators/CacheKey.cs +++ b/src/Castle.Core/DynamicProxy/Generators/CacheKey.cs @@ -15,8 +15,11 @@ namespace Castle.DynamicProxy.Generators { using System; + using System.ComponentModel; using System.Reflection; + [Obsolete("Intended for internal use only.")] // TODO: Redeclare this type as `internal`. + [EditorBrowsable(EditorBrowsableState.Never)] #if FEATURE_SERIALIZATION [Serializable] #endif diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index 35d1de477f..a9dc6b88c3 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -16,6 +16,7 @@ namespace Castle.DynamicProxy { using System; using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Reflection; @@ -57,7 +58,9 @@ public class ModuleScope private readonly Dictionary typeCache = new Dictionary(); // Users of ModuleScope should use this lock when accessing the cache + [Obsolete] // TODO: Remove this field together with the `Lock` property. private readonly Lock cacheLock; + private readonly ReaderWriterLockSlim typeCacheLock; // Used to lock the module builder creation @@ -158,6 +161,8 @@ public INamingScope NamingScope /// /// Users of this should use this lock when accessing the cache. /// + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Remove this property. + [EditorBrowsable(EditorBrowsableState.Never)] public Lock Lock { get { return cacheLock; } @@ -168,6 +173,8 @@ public Lock Lock /// /// The key to be looked up in the cache. /// The type from this scope's type cache matching the key, or null if the key cannot be found + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Remove this method. + [EditorBrowsable(EditorBrowsableState.Never)] public Type GetFromCache(CacheKey key) { Type type; @@ -180,6 +187,8 @@ public Type GetFromCache(CacheKey key) /// /// The key to be associated with the type. /// The type to be stored in the cache. + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Remove this method. + [EditorBrowsable(EditorBrowsableState.Never)] public void RegisterInCache(CacheKey key, Type type) { typeCache[key] = type; @@ -648,7 +657,7 @@ public void LoadAssemblyIntoCache(Assembly assembly) if (loadedType != null) { - RegisterInCache(mapping.Key, loadedType); + typeCache[mapping.Key] = loadedType; } } } diff --git a/src/Castle.Core/DynamicProxy/Serialization/CacheMappingsAttribute.cs b/src/Castle.Core/DynamicProxy/Serialization/CacheMappingsAttribute.cs index 183fab6b28..1626ac35a7 100644 --- a/src/Castle.Core/DynamicProxy/Serialization/CacheMappingsAttribute.cs +++ b/src/Castle.Core/DynamicProxy/Serialization/CacheMappingsAttribute.cs @@ -18,6 +18,7 @@ namespace Castle.DynamicProxy.Serialization { using System; using System.Collections.Generic; + using System.ComponentModel; using System.IO; using System.Reflection; using System.Reflection.Emit; @@ -47,6 +48,8 @@ public byte[] SerializedCacheMappings get { return serializedCacheMappings; } } + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Redeclare this method as `internal`. + [EditorBrowsable(EditorBrowsableState.Never)] public Dictionary GetDeserializedMappings() { using (var stream = new MemoryStream(SerializedCacheMappings)) @@ -56,6 +59,8 @@ public Dictionary GetDeserializedMappings() } } + [Obsolete("Exposes a component that is intended for internal use only.")] // TODO: Redeclare this method as `internal`. + [EditorBrowsable(EditorBrowsableState.Never)] public static void ApplyTo(AssemblyBuilder assemblyBuilder, Dictionary mappings) { using (var stream = new MemoryStream()) From f8e69e57c7c3b233157a114a379ed59c5bab3d29 Mon Sep 17 00:00:00 2001 From: stakx Date: Sat, 23 Jun 2018 13:09:26 +0200 Subject: [PATCH 09/11] Abstract away `ReaderWriterLockSlim` in most cases `ReaderWriterLockSlim` is used mostly in conjunction with regular dictionaries. The double-checked lock pattern is used in many places to query and update those dictionaries. Instead of repeating this same pattern over and over again, abstract it away with a dictionary-like type `SynchronizedDictionary<,>` that performs the required locking pattern. `ModuleScope`'s internal methods for accessing the type cache can now be replaced with a simple internal property `TypeCache`. --- .../DictionaryAdapterFactory.cs | 50 ++------ .../Core/Internal/SynchronizedDictionary.cs | 111 ++++++++++++++++++ .../ClassProxyTargetContributor.cs | 2 +- .../ClassProxyWithTargetTargetContributor.cs | 4 +- .../DelegateProxyTargetContributor.cs | 2 +- .../InterfaceProxyTargetContributor.cs | 2 +- .../InterfaceProxyWithoutTargetContributor.cs | 2 +- .../Contributors/MixinContributor.cs | 2 +- .../Generators/BaseProxyGenerator.cs | 2 +- .../DynamicProxy/Internal/InvocationHelper.cs | 42 +------ src/Castle.Core/DynamicProxy/ModuleScope.cs | 110 +++-------------- src/Castle.Core/DynamicProxy/ProxyUtil.cs | 42 ++----- 12 files changed, 154 insertions(+), 217 deletions(-) create mode 100644 src/Castle.Core/Core/Internal/SynchronizedDictionary.cs diff --git a/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs b/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs index 451a646f66..387f265ff5 100644 --- a/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs +++ b/src/Castle.Core/Components.DictionaryAdapter/DictionaryAdapterFactory.cs @@ -36,9 +36,8 @@ namespace Castle.Components.DictionaryAdapter /// public class DictionaryAdapterFactory : IDictionaryAdapterFactory { - private readonly Dictionary interfaceToMeta = new Dictionary(); - - private readonly ReaderWriterLockSlim interfaceToMetaLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + private readonly SynchronizedDictionary interfaceToMeta = + new SynchronizedDictionary(); #region IDictionaryAdapterFactory @@ -130,50 +129,21 @@ private DictionaryAdapterMeta InternalGetAdapterMeta(Type type, if (type.GetTypeInfo().IsInterface == false) throw new ArgumentException("Only interfaces can be adapted to a dictionary", "type"); - DictionaryAdapterMeta meta; - - interfaceToMetaLock.EnterReadLock(); - try - { - if (interfaceToMeta.TryGetValue(type, out meta)) - { - return meta; - } - } - finally - { - interfaceToMetaLock.ExitReadLock(); - } - - interfaceToMetaLock.EnterWriteLock(); - try + return interfaceToMeta.GetOrAdd(type, t => { - if (interfaceToMeta.TryGetValue(type, out meta)) + if (descriptor == null && other != null) { - return meta; + descriptor = other.CreateDescriptor(); } - else - { - if (descriptor == null && other != null) - { - descriptor = other.CreateDescriptor(); - } #if FEATURE_LEGACY_REFLECTION_API - var appDomain = Thread.GetDomain(); - var typeBuilder = CreateTypeBuilder(type, appDomain); + var appDomain = Thread.GetDomain(); + var typeBuilder = CreateTypeBuilder(type, appDomain); #else - var typeBuilder = CreateTypeBuilder(type); + var typeBuilder = CreateTypeBuilder(type); #endif - meta = CreateAdapterMeta(type, typeBuilder, descriptor); - interfaceToMeta.Add(type, meta); - return meta; - } - } - finally - { - interfaceToMetaLock.ExitWriteLock(); - } + return CreateAdapterMeta(type, typeBuilder, descriptor); + }); } private object InternalGetAdapter(Type type, IDictionary dictionary, PropertyDescriptor descriptor) diff --git a/src/Castle.Core/Core/Internal/SynchronizedDictionary.cs b/src/Castle.Core/Core/Internal/SynchronizedDictionary.cs new file mode 100644 index 0000000000..7b8d63a24d --- /dev/null +++ b/src/Castle.Core/Core/Internal/SynchronizedDictionary.cs @@ -0,0 +1,111 @@ +// Copyright 2004-2018 Castle Project - http://www.castleproject.org/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Castle.Core.Internal +{ + using System; + using System.Collections.Generic; + using System.Threading; + + internal sealed class SynchronizedDictionary : IDisposable + { + private Dictionary items; + private ReaderWriterLockSlim itemsLock; + + public SynchronizedDictionary() + { + items = new Dictionary(); + itemsLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + } + + [Obsolete] // TODO: Remove this property along with the `ModuleScope.Lock` property. + public ReaderWriterLockSlim Lock => itemsLock; + + public void AddOrUpdateWithoutTakingLock(TKey key, TValue value) + { + items[key] = value; + } + + public void Dispose() + { + itemsLock.Dispose(); + } + + public TValue GetOrAdd(TKey key, Func valueFactory) + { + TValue value; + + itemsLock.EnterReadLock(); + try + { + if (items.TryGetValue(key, out value)) + { + return value; + } + } + finally + { + itemsLock.ExitReadLock(); + } + + itemsLock.EnterWriteLock(); + try + { + return GetOrAddWithoutTakingLock(key, valueFactory); + } + finally + { + itemsLock.ExitWriteLock(); + } + } + + public TValue GetOrAddWithoutTakingLock(TKey key, Func valueFactory) + { + TValue value; + + if (items.TryGetValue(key, out value)) + { + return value; + } + else + { + value = valueFactory.Invoke(key); + items.Add(key, value); + return value; + } + } + + public void ForEach(Action action) + { + itemsLock.EnterReadLock(); + try + { + foreach (var item in items) + { + action.Invoke(item.Key, item.Value); + } + } + finally + { + itemsLock.ExitReadLock(); + } + } + + [Obsolete] // TODO: Remove this method along with the `ModuleScope.GetFromCache` method. + public bool TryGetValueWithoutTakingLock(TKey key, out TValue value) + { + return items.TryGetValue(key, out value); + } + } +} diff --git a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs index 315cef0a3a..cd20725586 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs @@ -179,7 +179,7 @@ private Type GetDelegateType(MetaMethod method, ClassEmitter @class, ProxyGenera ToArray(), null); - return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => new DelegateTypeGenerator(method, targetType) .Generate(@class, options, namingScope) .BuildType()); diff --git a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs index 11e461b217..47d7819d92 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs @@ -126,7 +126,7 @@ private Type GetDelegateType(MetaMethod method, ClassEmitter @class, ProxyGenera ToArray(), null); - return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => new DelegateTypeGenerator(method, targetType) .Generate(@class, options, namingScope) .BuildType()); @@ -141,7 +141,7 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter @class, ProxyGene // no locking required as we're already within a lock - return scope.GetOrAddToCacheWithoutTakingLock(key, _ => BuildInvocationType(method, @class, options)); + return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => BuildInvocationType(method, @class, options)); } private MethodGenerator IndirectlyCalledMethodGenerator(MetaMethod method, ClassEmitter proxy, diff --git a/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs index d0e6a6249f..a14e8b206e 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/DelegateProxyTargetContributor.cs @@ -58,7 +58,7 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter, ProxyGen // no locking required as we're already within a lock - return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => new CompositionInvocationTypeGenerator(method.Method.DeclaringType, method, method.Method, diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs index 7f2f9202a2..4c333be11c 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyTargetContributor.cs @@ -92,7 +92,7 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter @class, ProxyGene // no locking required as we're already within a lock - return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => new CompositionInvocationTypeGenerator(method.Method.DeclaringType, method, method.Method, diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs index e70d0907a3..317eda5821 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs @@ -77,7 +77,7 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter, ProxyGen // no locking required as we're already within a lock - return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => new CompositionInvocationTypeGenerator(method.Method.DeclaringType, method, method.Method, diff --git a/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs index af414557c5..8a6c49a463 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/MixinContributor.cs @@ -132,7 +132,7 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter, ProxyGen // no locking required as we're already within a lock - return scope.GetOrAddToCacheWithoutTakingLock(key, _ => + return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => new CompositionInvocationTypeGenerator(method.Method.DeclaringType, method, method.Method, diff --git a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs index 49d6c39e78..22fdaa0807 100644 --- a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs @@ -393,7 +393,7 @@ protected Type ObtainProxyType(CacheKey cacheKey, Func + var proxyType = Scope.TypeCache.GetOrAdd(cacheKey, _ => { notFoundInTypeCache = true; Logger.DebugFormat("No cached proxy type was found for target type {0}.", targetType.FullName); diff --git a/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs b/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs index 13aea9da4d..dc22c7e547 100644 --- a/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs +++ b/src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs @@ -20,15 +20,13 @@ namespace Castle.DynamicProxy.Internal using System.Reflection; using System.Threading; + using Castle.Core.Internal; using Castle.DynamicProxy.Generators; public static class InvocationHelper { - private static readonly Dictionary cache = - new Dictionary(); - - private static readonly ReaderWriterLockSlim cacheLock = - new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + private static readonly SynchronizedDictionary cache = + new SynchronizedDictionary(); public static MethodInfo GetMethodOnObject(object target, MethodInfo proxiedMethod) { @@ -52,39 +50,7 @@ public static MethodInfo GetMethodOnType(Type type, MethodInfo proxiedMethod) var cacheKey = new CacheKey(proxiedMethod, type); - MethodInfo methodOnTarget; - - cacheLock.EnterReadLock(); - try - { - if (cache.TryGetValue(cacheKey, out methodOnTarget)) - { - return methodOnTarget; - } - } - finally - { - cacheLock.ExitReadLock(); - } - - cacheLock.EnterWriteLock(); - try - { - if (cache.TryGetValue(cacheKey, out methodOnTarget)) - { - return methodOnTarget; - } - else - { - methodOnTarget = ObtainMethod(proxiedMethod, type); - cache.Add(cacheKey, methodOnTarget); - return methodOnTarget; - } - } - finally - { - cacheLock.ExitWriteLock(); - } + return cache.GetOrAdd(cacheKey, ck => ObtainMethod(proxiedMethod, type)); } private static MethodInfo ObtainMethod(MethodInfo proxiedMethod, Type type) diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index a9dc6b88c3..8cadbb829c 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -55,14 +55,12 @@ public class ModuleScope private readonly string weakModulePath; // Keeps track of generated types - private readonly Dictionary typeCache = new Dictionary(); + private readonly SynchronizedDictionary typeCache = new SynchronizedDictionary(); // Users of ModuleScope should use this lock when accessing the cache [Obsolete] // TODO: Remove this field together with the `Lock` property. private readonly Lock cacheLock; - private readonly ReaderWriterLockSlim typeCacheLock; - // Used to lock the module builder creation private readonly object moduleLocker = new object(); @@ -149,8 +147,7 @@ public ModuleScope(bool savePhysicalAssembly, bool disableSignedModule, INamingS this.weakAssemblyName = weakAssemblyName; this.weakModulePath = weakModulePath; - this.typeCacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - this.cacheLock = Lock.CreateFor(this.typeCacheLock); + this.cacheLock = Lock.CreateFor(typeCache.Lock); } public INamingScope NamingScope @@ -168,6 +165,8 @@ public Lock Lock get { return cacheLock; } } + internal SynchronizedDictionary TypeCache => typeCache; + /// /// Returns a type from this scope's type cache, or null if the key cannot be found. /// @@ -178,7 +177,7 @@ public Lock Lock public Type GetFromCache(CacheKey key) { Type type; - typeCache.TryGetValue(key, out type); + typeCache.TryGetValueWithoutTakingLock(key, out type); return type; } @@ -191,79 +190,7 @@ public Type GetFromCache(CacheKey key) [EditorBrowsable(EditorBrowsableState.Never)] public void RegisterInCache(CacheKey key, Type type) { - typeCache[key] = type; - } - - /// - /// Returns a type from this scope's type cache, or adds it to the cache if the key cannot be found. - /// - /// The key to be looked up in the cache. - /// A function producing the type to be added to the cache. - /// The type from this scope's type cache matching the key, or null if the key cannot be found - /// - /// This method does not synchronize access to this scope's type cache, i.e. no read/write locks are taken. - /// Only use this method when you know for sure that a method further up in the call stack already holds - /// a write lock. The function must not access this scope's type cache. - /// - internal Type GetOrAddToCacheWithoutTakingLock(CacheKey key, Func valueFactory) - { - Type value; - - Debug.Assert(key != null); - Debug.Assert(valueFactory != null); - - if (typeCache.TryGetValue(key, out value)) - { - return value; - } - else - { - value = valueFactory.Invoke(key); - typeCache.Add(key, value); - return value; - } - } - - /// - /// Returns a type from this scope's type cache, or adds it to the cache if the key cannot be found. - /// - /// The key to be looked up in the cache. - /// A function producing the type to be added to the cache. - /// The type from this scope's type cache matching the key, or null if the key cannot be found - /// - /// This method synchronizes accesses to this scope's type cache using read/write locks. - /// If the specified key cannot be found, the factory function is invoked while a write lock is held. - /// The function must not access this scope's type cache. - /// - internal Type GetOrAddToCache(CacheKey key, Func valueFactory) - { - Type value; - - Debug.Assert(key != null); - Debug.Assert(valueFactory != null); - - typeCacheLock.EnterReadLock(); - try - { - if (typeCache.TryGetValue(key, out value)) - { - return value; - } - } - finally - { - typeCacheLock.ExitReadLock(); - } - - typeCacheLock.EnterWriteLock(); - try - { - return GetOrAddToCacheWithoutTakingLock(key, valueFactory); - } - finally - { - typeCacheLock.ExitWriteLock(); - } + typeCache.AddOrUpdateWithoutTakingLock(key, type); } /// @@ -599,26 +526,17 @@ public string SaveAssembly(bool strongNamed) #if FEATURE_SERIALIZATION private void AddCacheMappings(AssemblyBuilder builder) { - Dictionary mappings; + var mappings = new Dictionary(); - this.typeCacheLock.EnterReadLock(); - try + typeCache.ForEach((key, value) => { - mappings = new Dictionary(); - foreach (var cacheEntry in typeCache) + // NOTE: using == returns invalid results. + // we need to use Equals here for it to work properly + if (builder.Equals(value.Assembly)) { - // NOTE: using == returns invalid results. - // we need to use Equals here for it to work properly - if(builder.Equals(cacheEntry.Value.Assembly)) - { - mappings.Add(cacheEntry.Key, cacheEntry.Value.FullName); - } + mappings.Add(key, value.FullName); } - } - finally - { - this.typeCacheLock.ExitReadLock(); - } + }); CacheMappingsAttribute.ApplyTo(builder, mappings); } @@ -657,7 +575,7 @@ public void LoadAssemblyIntoCache(Assembly assembly) if (loadedType != null) { - typeCache[mapping.Key] = loadedType; + typeCache.AddOrUpdateWithoutTakingLock(mapping.Key, loadedType); } } } diff --git a/src/Castle.Core/DynamicProxy/ProxyUtil.cs b/src/Castle.Core/DynamicProxy/ProxyUtil.cs index 8216080a0f..efb86044d3 100644 --- a/src/Castle.Core/DynamicProxy/ProxyUtil.cs +++ b/src/Castle.Core/DynamicProxy/ProxyUtil.cs @@ -25,10 +25,11 @@ namespace Castle.DynamicProxy using System.Runtime.Remoting; #endif + using Castle.Core.Internal; + public static class ProxyUtil { - private static readonly IDictionary internalsVisibleToDynamicProxy = new Dictionary(); - private static readonly ReaderWriterLockSlim internalsVisibleToDynamicProxyLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + private static readonly SynchronizedDictionary internalsVisibleToDynamicProxy = new SynchronizedDictionary(); public static object GetUnproxiedInstance(object instance) { @@ -130,40 +131,11 @@ public static bool IsAccessible(Type type) /// The assembly to inspect. internal static bool AreInternalsVisibleToDynamicProxy(Assembly asm) { - bool result; - - internalsVisibleToDynamicProxyLock.EnterReadLock(); - try - { - if (internalsVisibleToDynamicProxy.TryGetValue(asm, out result)) - { - return result; - } - } - finally - { - internalsVisibleToDynamicProxyLock.ExitReadLock(); - } - - internalsVisibleToDynamicProxyLock.EnterWriteLock(); - try - { - if (internalsVisibleToDynamicProxy.TryGetValue(asm, out result)) - { - return result; - } - else - { - var internalsVisibleTo = asm.GetCustomAttributes(); - result = internalsVisibleTo.Any(attr => attr.AssemblyName.Contains(ModuleScope.DEFAULT_ASSEMBLY_NAME)); - internalsVisibleToDynamicProxy.Add(asm, result); - return result; - } - } - finally + return internalsVisibleToDynamicProxy.GetOrAdd(asm, a => { - internalsVisibleToDynamicProxyLock.ExitWriteLock(); - } + var internalsVisibleTo = asm.GetCustomAttributes(); + return internalsVisibleTo.Any(attr => attr.AssemblyName.Contains(ModuleScope.DEFAULT_ASSEMBLY_NAME)); + }); } internal static bool IsAccessibleType(Type target) From aa7436bb18dd0ab5c5a2132ee1d720994a7fc2b8 Mon Sep 17 00:00:00 2001 From: stakx Date: Sat, 23 Jun 2018 14:02:09 +0200 Subject: [PATCH 10/11] Reinstate upgradeable read locks Based on a review comment, this reinstates the upgradeable read locks that were originally taken before entering write locks. The intent of this PR is to deprecate `Lock`, not to make functional changes. While forgoing the upgradeable read locks in favor of enter- ing write locks right away might *possibly* be an optimization, this is a different concern and would need to be properly established using careful benchmarking. Let's do this another time. --- .../Internal/Utilities/SingletonDispenser.cs | 16 +++++++++---- .../Core/Internal/SynchronizedDictionary.cs | 23 ++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs b/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs index 726b7adc8a..6aee638efc 100644 --- a/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs +++ b/src/Castle.Core/Components.DictionaryAdapter/Xml/Internal/Utilities/SingletonDispenser.cs @@ -65,7 +65,7 @@ private bool TryGetExistingItem(TKey key, out object item) locker.ExitReadLock(); } - locker.EnterWriteLock(); + locker.EnterUpgradeableReadLock(); try { if (items.TryGetValue(key, out item)) @@ -74,13 +74,21 @@ private bool TryGetExistingItem(TKey key, out object item) } else { - items[key] = item = new ManualResetEvent(false); - return false; + locker.EnterWriteLock(); + try + { + items[key] = item = new ManualResetEvent(false); + return false; + } + finally + { + locker.ExitWriteLock(); + } } } finally { - locker.ExitWriteLock(); + locker.ExitUpgradeableReadLock(); } } diff --git a/src/Castle.Core/Core/Internal/SynchronizedDictionary.cs b/src/Castle.Core/Core/Internal/SynchronizedDictionary.cs index 7b8d63a24d..59ab376cec 100644 --- a/src/Castle.Core/Core/Internal/SynchronizedDictionary.cs +++ b/src/Castle.Core/Core/Internal/SynchronizedDictionary.cs @@ -59,14 +59,31 @@ public TValue GetOrAdd(TKey key, Func valueFactory) itemsLock.ExitReadLock(); } - itemsLock.EnterWriteLock(); + itemsLock.EnterUpgradeableReadLock(); try { - return GetOrAddWithoutTakingLock(key, valueFactory); + if (items.TryGetValue(key, out value)) + { + return value; + } + else + { + itemsLock.EnterWriteLock(); + try + { + value = valueFactory.Invoke(key); + items.Add(key, value); + return value; + } + finally + { + itemsLock.ExitWriteLock(); + } + } } finally { - itemsLock.ExitWriteLock(); + itemsLock.ExitUpgradeableReadLock(); } } From 5bd3e48aa7f60d034da3ebaeb210475fe987b9fc Mon Sep 17 00:00:00 2001 From: stakx Date: Thu, 21 Jun 2018 01:57:06 +0200 Subject: [PATCH 11/11] Update the changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 666008f163..c5e7b6c4c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Castle Core Changelog +## Unreleased + +Deprecations: +- The API surrounding `Lock` has been deprecated. This consists of the members listed below. Consider using the Base Class Library's `System.Threading.ReaderWriterLockSlim` instead. (@stakx, #391) + - `Castle.Core.Internal.Lock` (class) + - `Castle.Core.Internal.ILockHolder` (interface) + - `Castle.Core.Internal.IUpgradeableLockHolder` (interface) +- The proxy type cache in `ModuleScope` should no longer be accessed directly. For this reason, the members listed below have been deprecated. (@stakx, #391) + - `Castle.DynamicProxy.ModuleScope.Lock` (property) + - `Castle.DynamicProxy.ModuleScope.GetFromCache` (method) + - `Castle.DynamicProxy.ModuleScope.RegisterInCache` (method) + - `Castle.DynamicProxy.Generators.BaseProxyGenerator.AddToCache` (method) + - `Castle.DynamicProxy.Generators.BaseProxyGenerator.GetFromCache` (method) + - `Castle.DynamicProxy.Generators.CacheKey` (class) + - `Castle.DynamicProxy.Serialization.CacheMappingsAttribute.ApplyTo` (method) + - `Castle.DynamicProxy.Serialization.CacheMappingsAttribute.GetDeserializedMappings` (method) + ## 4.3.1 (2018-06-21) Enhancements: