A day in the life...

public class GeekEarth : Earth { }
posts - 26, comments - 8236, trackbacks - 0

Sunday, December 19, 2010

Creating a pooled lifetime manager for Unity

My application has a child container per request, this is because there are a lot of services used in each request which depend upon an IUnitOfWork.  I thought it would be nice if I could define a pool of these IUnitOfWork instances so that any cost involved in creating them is reduced, they can just be reused in a round-robin fashion.  Well, more accurately, the object space (EcoSpace) on which they depend can anyway.

A pool can now be registered like so…

1 //Must be called once, when the container is created 2 container.AddNewExtension<PooledLifetimeExtension>(); 3 //To register a pooled item 4 container.RegisterType 5 < 6 ISomeItemThatIsExpensiveToCreate, 7 SomeItemThatIsExpensiveToCreate 8 >(new PooledLifetimeManager(10));

The number 10 specifies how many instances at once may be stored in the pool.  If an instance requests an item from the pool when it is empty it will get a new instance, it’s only at the point where an instance is returned to the pool that the MaxPoolSize is honoured – if the pool is not full then the instance goes back into the pool, if the pool is already full then Dispose is called on it instead.  Of course you should be careful when pooling that your pooled items don’t hold onto other injected references which are also pooled as that will cause problems.

I have also added the following interface so that an instance can (optionally) specify whether or not its current state should permit it to go back into the pool, and two methods which indicate to the instance when it has been retrieved from a pool or is about to be returned to the pool – in my case I deactivated the object space upon returning in order to flush any object cache, and reactivated it again when retrieved from the pool.

 

1 public interface IPooledResource 2 { 3 bool CanReturnToPool { get; } 4 void RetrievedFromPool(); 5 void ReturningToPool(); 6 }

 

It’s important to note that this only works with child containers.  You should create your main controller as a kind of template by registering your request specific services (like IUnitOfWork) with HierarchicalLifetimeManager.  The PooledLifetimeManager will work in the same way as HierarchicalLifetimeManager except for the addition of pooling, and it will automatically attempt to return items to the pool once the child container is disposed of.

 

1 //PooledLifetimeExtension.cs 2 using Microsoft.Practices.Unity; 3 using Microsoft.Practices.Unity.ObjectBuilder; 4 5 namespace YourNameHere.Infrastructure.Unity 6 { 7 public class PooledLifetimeExtension : UnityContainerExtension 8 { 9 protected override void Initialize() 10 { 11 // Add to type mapping stage so that it runs before the lifetime stage 12 Context.Strategies.AddNew<PooledLifetimeStrategy> 13 (UnityBuildStage.TypeMapping); 14 } 15 } 16 } 17 18 19 20 //PooledLifetimeManager.cs 21 using System; 22 using System.Collections.Generic; 23 using Microsoft.Practices.Unity; 24 using Microsoft.Practices.ObjectBuilder2; 25 26 namespace YourNameHere.Infrastructure.Unity 27 { 28 public class PooledLifetimeManager : LifetimeManager 29 { 30 readonly PooledLifetimeManagerPool Pool; 31 32 public PooledLifetimeManager(int maxPoolSize) 33 { 34 Pool = new PooledLifetimeManagerPool(maxPoolSize); 35 } 36 37 internal PooledLifetimeManagerPoolConsumer CreatePoolUserForChildContainer() 38 { 39 return new PooledLifetimeManagerPoolConsumer(Pool); 40 } 41 42 #region GetValue/SetValue/RemoveValue all redundant 43 public override object GetValue() 44 { 45 throw new InvalidOperationException(); 46 } 47 48 public override void SetValue(object newValue) 49 { 50 throw new InvalidOperationException(); 51 } 52 53 public override void RemoveValue() 54 { 55 throw new InvalidOperationException(); 56 } 57 #endregion 58 } 59 60 internal class PooledLifetimeManagerPool 61 { 62 readonly int MaxPoolSize; 63 readonly object SyncRoot = new object(); 64 readonly Queue<object> Queue = new Queue<object>(); 65 66 public PooledLifetimeManagerPool(int maxPoolSize) 67 { 68 if (maxPoolSize < 0) 69 throw new ArgumentOutOfRangeException( 70 "MaxPoolSize", "Cannot be less than zero"); 71 MaxPoolSize = maxPoolSize; 72 } 73 74 internal object RetrieveFromPool() 75 { 76 object result = null; 77 lock (SyncRoot) 78 if (Queue.Count > 0) 79 result = Queue.Dequeue(); 80 IPooledResource resultAsIPooledResource = result as IPooledResource; 81 if (resultAsIPooledResource != null) 82 resultAsIPooledResource.RetrievedFromPool(); 83 return result; 84 } 85 86 internal void ReturnToPool(object instance) 87 { 88 if (instance == null) 89 throw new ArgumentNullException("Instance"); 90 IPooledResource instanceAsIPooledResource = instance as IPooledResource; 91 if (instanceAsIPooledResource != null) 92 { 93 if (!instanceAsIPooledResource.CanReturnToPool) 94 return; 95 instanceAsIPooledResource.ReturningToPool(); 96 } 97 bool wentIntoPool = false; 98 lock (SyncRoot) 99 if (Queue.Count < MaxPoolSize) 100 { 101 Queue.Enqueue(instance); 102 wentIntoPool = true; 103 } 104 105 //Dispose any instances which did not go back into the pool 106 if (!wentIntoPool && instance is IDisposable) 107 ((IDisposable)instance).Dispose(); 108 } 109 } 110 111 public class PooledLifetimeManagerPoolConsumer : 112 ContainerControlledLifetimeManager, IDisposable 113 { 114 readonly PooledLifetimeManagerPool SharedPool; 115 object Instance; 116 117 internal PooledLifetimeManagerPoolConsumer( 118 PooledLifetimeManagerPool sharedPool) 119 { 120 if (sharedPool == null) 121 throw new ArgumentNullException("SharedPool"); 122 SharedPool = sharedPool; 123 } 124 125 protected override object SynchronizedGetValue() 126 { 127 if (Instance == null) 128 Instance = SharedPool.RetrieveFromPool(); 129 return Instance; 130 } 131 132 protected override void SynchronizedSetValue(object newValue) 133 { 134 if (Instance != null && newValue == null) 135 SharedPool.ReturnToPool(Instance); 136 Instance = newValue; 137 } 138 139 public override void RemoveValue() 140 { 141 throw new InvalidOperationException(); 142 } 143 144 void IDisposable.Dispose() 145 { 146 SetValue(null); 147 base.Dispose(); 148 } 149 } 150 } 151 152 153 154 //PooledLifetimeStrategy.cs 155 using Microsoft.Practices.ObjectBuilder2; 156 157 namespace YourNameHere.Infrastructure.Unity 158 { 159 public class PooledLifetimeStrategy : BuilderStrategy 160 { 161 public override void PreBuildUp(IBuilderContext context) 162 { 163 PooledLifetimeManager activeLifetime = 164 context.PersistentPolicies.Get<ILifetimePolicy>(context.BuildKey) 165 as PooledLifetimeManager; 166 if (activeLifetime != null) 167 { 168 // did this come from the local container or the parent? 169 var localLifetime = 170 context.PersistentPolicies.Get<ILifetimePolicy>( 171 context.BuildKey, true); 172 if (localLifetime == null) 173 { 174 // came from parent, add a ContainerControlledLifetime here 175 var newLifeTime = activeLifetime.CreatePoolUserForChildContainer(); 176 context.PersistentPolicies.Set<ILifetimePolicy>( 177 newLifeTime, context.BuildKey); 178 //Make sure it gets disposed 179 //so that it can return its reference to the pool 180 context.Lifetime.Add(newLifeTime); 181 } 182 } 183 } 184 } 185 } 186 187 188 189 //PooledResourceIntf.cs 190 namespace YourNameHere.Infrastructure.Unity 191 { 192 public interface IPooledResource 193 { 194 bool CanReturnToPool { get; } 195 void RetrievedFromPool(); 196 void ReturningToPool(); 197 } 198 } 199

posted @ Sunday, December 19, 2010 6:33 PM | Feedback (333) |

Unity child containers + ASP MVC = Memory leak

I was making some speed improvements to my current ASP MVC application.  One of the things I did was to change from creating a completely new IUnityContainer for each request over to creating one master (template) container with all the services registered with HierarchicalLifetimeManager.  Then whenever a controller is required my ControllerFactory does this

1 if (controllerType == null) 2 return null; 3 var requestContainer = Container.CreateChildContainer(); 4 return (IController) requestContainer.Resolve(controllerType);

That’s all nice and simple, however it causes a memory leak.  The reason is that the parent container holds a reference to all of its children.  The only way to resolve this is to Dispose of the child container.  Now obviously we cannot dispose of it in the controller factory because we need to return a fully working Controller.  So the next thing I tried was to dispose of the request container in IControllerFactory.ReleaseController.  The problem with this is that the controller is released before the view is rendered, so if your view depends on objects which retrieve their state from some kind of object space then this is too soon to dispose of the request container.  So ultimately I couldn’t find anywhere in the ASP MVC framework into which I could hook an event which gets fired after the request has finished.

To solve the problem my IControllerFactory no longer returns the controller type requested.  Instead at runtime it creates a descendant class which implements IController directly and then passes the request on to the original controller class.  The type returned stores a reference to the per-request IUnityContainer and then does this

1 void IController.Execute(RequestContext context) 2 { 3 try 4 { 5 base.Execute(context); 6 } 7 finally 8 { 9 RequestUnityContainer.Dispose(); 10 } 11 }

So now we have a bit of code in our controller which disposes of the per-request unity container after the entire request has been processed.  The ControllerFactory code only needs to be changed like so

1 protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) 2 { 3 if (controllerType == null) 4 return null; 5 IUnityContainer requestContainer = Container.CreateChildContainer(); 6 ControllerBase controller = ContainerDisposingControllerFactory.Create(requestContainer, controllerType); 7 return controller; 8 }

 

The code required is listed below…

1 //ContainerDisposingControllerIntf.cs 2 using Microsoft.Practices.Unity; 3 4 namespace YourNameHere.Infrastructure.Web 5 { 6 public interface IContainerDisposingController 7 { 8 IUnityContainer IContainerDisposingController_UnityContainer { get; set; } 9 } 10 } 11 12 13 //ContainerDisposingControllerFactory.cs 14 using System; 15 using System.Collections.Generic; 16 using System.Linq; 17 using System.Reflection; 18 using System.Reflection.Emit; 19 using System.Threading; 20 using System.Web.Mvc; 21 using Microsoft.Practices.Unity; 22 23 namespace YourNameHere.Infrastructure.Web 24 { 25 public static class ContainerDisposingControllerFactory 26 { 27 const string UnityContainerBackingFieldName = "IContainerDisposingController_BackingField"; 28 static MethodInfo DisposeMethodInfo = typeof(IDisposable).GetMethod("Dispose"); 29 static readonly Dictionary<Type, Type> InterceptingType_BySuperClass = new Dictionary<Type, Type>(); 30 static ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); 31 32 public static ControllerBase Create(IUnityContainer containerToDispose, Type controllerType) 33 { 34 try 35 { 36 return CreateTypeAndInstance(containerToDispose, controllerType); 37 } 38 catch 39 { 40 containerToDispose.Dispose(); 41 throw; 42 } 43 } 44 45 private static ControllerBase CreateTypeAndInstance(IUnityContainer containerToDispose, Type controllerType) 46 { 47 Type interceptingControllerType; 48 Locker.EnterUpgradeableReadLock(); 49 try 50 { 51 if (!InterceptingType_BySuperClass.TryGetValue(controllerType, out interceptingControllerType)) 52 { 53 Locker.EnterWriteLock(); 54 try 55 { 56 interceptingControllerType = CreateInterceptingControllerType(controllerType); 57 InterceptingType_BySuperClass[controllerType] = interceptingControllerType; 58 } 59 finally 60 { 61 Locker.ExitWriteLock(); 62 } 63 } 64 } 65 finally 66 { 67 Locker.ExitUpgradeableReadLock(); 68 } 69 70 var result = (Controller)containerToDispose.Resolve(interceptingControllerType); 71 var resultAsIUnityContainerController = (IContainerDisposingController)result; 72 resultAsIUnityContainerController.IContainerDisposingController_UnityContainer = containerToDispose; 73 return result; 74 } 75 76 static Type CreateInterceptingControllerType(Type controllerType) 77 { 78 if (!typeof(ControllerBase).IsAssignableFrom(controllerType)) 79 throw new ArgumentException("ControllerType does not descend from ControllerBase"); 80 81 string guid = Guid.NewGuid().ToString(); 82 var assemblyName = new AssemblyName(guid); 83 var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( 84 assemblyName, 85 AssemblyBuilderAccess.Run); 86 var moduleBuilder = assemblyBuilder.DefineDynamicModule(guid); 87 var typeBuilder = moduleBuilder.DefineType( 88 guid, 89 TypeAttributes.Class | TypeAttributes.Public, 90 controllerType); 91 CreateConstructor(controllerType, typeBuilder); 92 FieldBuilder unityContainerBackingFieldBuilder; 93 ImplementIContainerDisposingController(typeBuilder, out unityContainerBackingFieldBuilder); 94 ImplementIController(controllerType, typeBuilder, unityContainerBackingFieldBuilder); 95 return typeBuilder.CreateType(); 96 } 97 98 static void CreateConstructor(Type controllerType, TypeBuilder typeBuilder) 99 { 100 var constructorInfo = controllerType.GetConstructors() 101 .OrderByDescending(x => x.GetParameters().Count()) 102 .FirstOrDefault(); 103 if (constructorInfo == null) 104 return; 105 ParameterInfo[] constructorParameters = 106 constructorInfo.GetParameters().ToArray(); 107 Type[] parameterTypes = constructorParameters.Select(x => x.ParameterType).ToArray(); 108 109 var constructorBuilder = typeBuilder.DefineConstructor( 110 MethodAttributes.Public, 111 CallingConventions.Standard, 112 parameterTypes); 113 114 //Define the parameters for our new constructor 115 for (int argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++) 116 constructorBuilder.DefineParameter( 117 argumentIndex + 1, 118 constructorParameters[argumentIndex].Attributes, 119 constructorParameters[argumentIndex].Name); 120 121 var bodyGenerator = constructorBuilder.GetILGenerator(); 122 bodyGenerator.Emit(OpCodes.Ldarg_0); 123 for (int argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++) 124 bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + 1); 125 bodyGenerator.Emit(OpCodes.Call, constructorInfo); 126 bodyGenerator.Emit(OpCodes.Ret); 127 } 128 129 static void ImplementIContainerDisposingController(TypeBuilder typeBuilder, out FieldBuilder unityContainerBackingFieldBuilder) 130 { 131 typeBuilder.AddInterfaceImplementation(typeof(IContainerDisposingController)); 132 unityContainerBackingFieldBuilder = typeBuilder.DefineField( 133 UnityContainerBackingFieldName, 134 typeof(IUnityContainer), 135 FieldAttributes.Private); 136 137 var propertyAccessorAttributes = 138 MethodAttributes.Public | MethodAttributes.SpecialName | 139 MethodAttributes.HideBySig | MethodAttributes.Virtual; 140 141 var getterBuilder = typeBuilder.DefineMethod( 142 "get_IContainerDisposingController_UnityContainer", 143 propertyAccessorAttributes, 144 typeof(IUnityContainer), 145 Type.EmptyTypes); 146 var getterGenerator = getterBuilder.GetILGenerator(); 147 getterGenerator.Emit(OpCodes.Ldarg_0); 148 getterGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder); 149 getterGenerator.Emit(OpCodes.Ret); 150 151 var setterBuilder = typeBuilder.DefineMethod( 152 "set_IContainerDisposingController_UnityContainer", 153 propertyAccessorAttributes, 154 null, 155 new Type[] { typeof(IUnityContainer) }); 156 var setterGenerator = setterBuilder.GetILGenerator(); 157 setterGenerator.Emit(OpCodes.Ldarg_0); 158 setterGenerator.Emit(OpCodes.Ldarg_1); 159 setterGenerator.Emit(OpCodes.Stfld, unityContainerBackingFieldBuilder); 160 setterGenerator.Emit(OpCodes.Ret); 161 } 162 163 static void ImplementIController(Type controllerType, TypeBuilder typeBuilder, FieldBuilder unityContainerBackingFieldBuilder) 164 { 165 typeBuilder.AddInterfaceImplementation(typeof(IController)); 166 MethodInfo baseMethod = null; 167 Type implementingType = controllerType; 168 do 169 { 170 baseMethod = implementingType.GetMethod( 171 "Execute", 172 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 173 implementingType = implementingType.BaseType; 174 } 175 while (baseMethod == null && implementingType != null); 176 if (baseMethod == null) 177 throw new NotImplementedException(controllerType.Name + " does not implement IController.Execute"); 178 179 var methodParameters = baseMethod.GetParameters(); 180 var methodBuilder = typeBuilder.DefineMethod( 181 typeof(IController).Name + ".Execute", 182 MethodAttributes.Public | MethodAttributes.Virtual | 183 MethodAttributes.ReuseSlot | MethodAttributes.HideBySig, 184 null, 185 methodParameters.Select(x => x.ParameterType).ToArray()); 186 //Define the parameters for our new constructor 187 for (int argumentIndex = 0; argumentIndex < methodParameters.Length; argumentIndex++) 188 methodBuilder.DefineParameter( 189 argumentIndex + 1, 190 methodParameters[argumentIndex].Attributes, 191 methodParameters[argumentIndex].Name); 192 193 var bodyGenerator = methodBuilder.GetILGenerator(); 194 bodyGenerator.BeginExceptionBlock(); 195 bodyGenerator.Emit(OpCodes.Ldarg_0); 196 for (int argumentIndex = 0; argumentIndex < methodParameters.Length; argumentIndex++) 197 bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + 1); 198 bodyGenerator.Emit(OpCodes.Call, baseMethod); 199 bodyGenerator.BeginFinallyBlock(); 200 bodyGenerator.Emit(OpCodes.Ldarg_0); 201 bodyGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder); 202 bodyGenerator.Emit(OpCodes.Call, DisposeMethodInfo); 203 bodyGenerator.EndExceptionBlock(); 204 bodyGenerator.Emit(OpCodes.Ret); 205 typeBuilder.DefineMethodOverride(methodBuilder, baseMethod); 206 } 207 } 208 }

posted @ Sunday, December 19, 2010 1:33 PM | Feedback (299) | Filed Under [ Code ]

Powered by: