2012-08-29

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

if (controllerType == null)
return null;
var requestContainer
= Container.CreateChildContainer();
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



void IController.Execute(RequestContext context)
{
try
{
base.Execute(context);
}
finally
{
RequestUnityContainer.Dispose();
}


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



protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return null;
IUnityContainer requestContainer
= Container.CreateChildContainer();
ControllerBase controller
= ContainerDisposingControllerFactory.Create(requestContainer, controllerType);
return controller;
}


The code required is listed below…



//ContainerDisposingControllerIntf.cs
using Microsoft.Practices.Unity;

namespace YourNameHere.Infrastructure.Web
{
public interface IContainerDisposingController
{
IUnityContainer IContainerDisposingController_UnityContainer {
get; set; }
}
}


//ContainerDisposingControllerFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using System.Web.Mvc;
using Microsoft.Practices.Unity;

namespace YourNameHere.Infrastructure.Web
{
public static class ContainerDisposingControllerFactory
{
const string UnityContainerBackingFieldName = "IContainerDisposingController_BackingField";
static MethodInfo DisposeMethodInfo = typeof(IDisposable).GetMethod("Dispose");
static readonly Dictionary<Type, Type> InterceptingType_BySuperClass = new Dictionary<Type, Type>();
static ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();

public static ControllerBase Create(IUnityContainer containerToDispose, Type controllerType)
{
try
{
return CreateTypeAndInstance(containerToDispose, controllerType);
}
catch
{
containerToDispose.Dispose();
throw;
}
}

private static ControllerBase CreateTypeAndInstance(IUnityContainer containerToDispose, Type controllerType)
{
Type interceptingControllerType;
Locker.EnterUpgradeableReadLock();
try
{
if (!InterceptingType_BySuperClass.TryGetValue(controllerType, out interceptingControllerType))
{
Locker.EnterWriteLock();
try
{
interceptingControllerType
= CreateInterceptingControllerType(controllerType);
InterceptingType_BySuperClass[controllerType]
= interceptingControllerType;
}
finally
{
Locker.ExitWriteLock();
}
}
}
finally
{
Locker.ExitUpgradeableReadLock();
}

var result
= (Controller)containerToDispose.Resolve(interceptingControllerType);
var resultAsIUnityContainerController
= (IContainerDisposingController)result;
resultAsIUnityContainerController.IContainerDisposingController_UnityContainer
= containerToDispose;
return result;
}

static Type CreateInterceptingControllerType(Type controllerType)
{
if (!typeof(ControllerBase).IsAssignableFrom(controllerType))
throw new ArgumentException("ControllerType does not descend from ControllerBase");

string guid = Guid.NewGuid().ToString();
var assemblyName
= new AssemblyName(guid);
var assemblyBuilder
= AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Run);
var moduleBuilder
= assemblyBuilder.DefineDynamicModule(guid);
var typeBuilder
= moduleBuilder.DefineType(
guid,
TypeAttributes.Class
| TypeAttributes.Public,
controllerType);
CreateConstructor(controllerType, typeBuilder);
FieldBuilder unityContainerBackingFieldBuilder;
ImplementIContainerDisposingController(typeBuilder,
out unityContainerBackingFieldBuilder);
ImplementIController(controllerType, typeBuilder, unityContainerBackingFieldBuilder);
return typeBuilder.CreateType();
}

static void CreateConstructor(Type controllerType, TypeBuilder typeBuilder)
{
var constructorInfo
= controllerType.GetConstructors()
.OrderByDescending(x
=> x.GetParameters().Count())
.FirstOrDefault();
if (constructorInfo == null)
return;
ParameterInfo[] constructorParameters
=
constructorInfo.GetParameters().ToArray();
Type[] parameterTypes
= constructorParameters.Select(x => x.ParameterType).ToArray();

var constructorBuilder
= typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
parameterTypes);

//Define the parameters for our new constructor
for (int argumentIndex = ; argumentIndex < parameterTypes.Length; argumentIndex++)
constructorBuilder.DefineParameter(
argumentIndex
+ ,
constructorParameters[argumentIndex].Attributes,
constructorParameters[argumentIndex].Name);

var bodyGenerator
= constructorBuilder.GetILGenerator();
bodyGenerator.Emit(OpCodes.Ldarg_);
for (int argumentIndex = ; argumentIndex < parameterTypes.Length; argumentIndex++)
bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex
+ );
bodyGenerator.Emit(OpCodes.Call, constructorInfo);
bodyGenerator.Emit(OpCodes.Ret);
}

static void ImplementIContainerDisposingController(TypeBuilder typeBuilder, out FieldBuilder unityContainerBackingFieldBuilder)
{
typeBuilder.AddInterfaceImplementation(
typeof(IContainerDisposingController));
unityContainerBackingFieldBuilder
= typeBuilder.DefineField(
UnityContainerBackingFieldName,
typeof(IUnityContainer),
FieldAttributes.Private);

var propertyAccessorAttributes
=
MethodAttributes.Public
| MethodAttributes.SpecialName |
MethodAttributes.HideBySig
| MethodAttributes.Virtual;

var getterBuilder
= typeBuilder.DefineMethod(
"get_IContainerDisposingController_UnityContainer",
propertyAccessorAttributes,
typeof(IUnityContainer),
Type.EmptyTypes);
var getterGenerator
= getterBuilder.GetILGenerator();
getterGenerator.Emit(OpCodes.Ldarg_);
getterGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder);
getterGenerator.Emit(OpCodes.Ret);

var setterBuilder
= typeBuilder.DefineMethod(
"set_IContainerDisposingController_UnityContainer",
propertyAccessorAttributes,
null,
new Type[] { typeof(IUnityContainer) });
var setterGenerator
= setterBuilder.GetILGenerator();
setterGenerator.Emit(OpCodes.Ldarg_);
setterGenerator.Emit(OpCodes.Ldarg_);
setterGenerator.Emit(OpCodes.Stfld, unityContainerBackingFieldBuilder);
setterGenerator.Emit(OpCodes.Ret);
}

static void ImplementIController(Type controllerType, TypeBuilder typeBuilder, FieldBuilder unityContainerBackingFieldBuilder)
{
typeBuilder.AddInterfaceImplementation(
typeof(IController));
MethodInfo baseMethod
= null;
Type implementingType
= controllerType;
do
{
baseMethod
= implementingType.GetMethod(
"Execute",
BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance);
implementingType
= implementingType.BaseType;
}
while (baseMethod == null && implementingType != null);
if (baseMethod == null)
throw new NotImplementedException(controllerType.Name + " does not implement IController.Execute");

var methodParameters
= baseMethod.GetParameters();
var methodBuilder
= typeBuilder.DefineMethod(
typeof(IController).Name + ".Execute",
MethodAttributes.Public
| MethodAttributes.Virtual |
MethodAttributes.ReuseSlot
| MethodAttributes.HideBySig,
null,
methodParameters.Select(x
=> x.ParameterType).ToArray());
//Define the parameters for our new constructor
for (int argumentIndex = ; argumentIndex < methodParameters.Length; argumentIndex++)
methodBuilder.DefineParameter(
argumentIndex
+ ,
methodParameters[argumentIndex].Attributes,
methodParameters[argumentIndex].Name);

var bodyGenerator
= methodBuilder.GetILGenerator();
bodyGenerator.BeginExceptionBlock();
bodyGenerator.Emit(OpCodes.Ldarg_);
for (int argumentIndex = ; argumentIndex < methodParameters.Length; argumentIndex++)
bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex
+ );
bodyGenerator.Emit(OpCodes.Call, baseMethod);
bodyGenerator.BeginFinallyBlock();
bodyGenerator.Emit(OpCodes.Ldarg_);
bodyGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder);
bodyGenerator.Emit(OpCodes.Call, DisposeMethodInfo);
bodyGenerator.EndExceptionBlock();
bodyGenerator.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(methodBuilder, baseMethod);
}
}
}

2 comments:

N. Harebottle III said...

Another option you might look into for solving this problem is making use of the EndRequest event on the HttpApplication context the way that Unity.MVC3 is doing. (Although in looking at that now, it seems like that might keep the child containers around for much too long!)

The relevant file from that project is referenced here:

http://unitymvc3.codeplex.com/SourceControl/changeset/view/580f45a682a5#Unity.Mvc3/RequestLifetimeHttpModule.cs

erikbra said...

Am I being completely daft, or could you solve this by simply using a variable?

IController controller;

using(var requestContainer = Container.CreateChildContainer()) {
controller = (IController) requestContainer.Resolve(controllerType);
}
return controller;

This will dispose the child container, and return a fully working container. Or am I missing the whole point?