3
votes

I ran into a problem where my program would run out of memory when using Castle Windsor and interceptors. It's reproducible using the following code:

public interface ITest{}
public class Test : ITest {}
class TestInterceptor:IInterceptor {
    public void Intercept(IInvocation invocation) {}
}
class Program {
    static void Main(string[] args) {
        while(true) {
            using(var container = new WindsorContainer()) {
                container.Register(Component.For<TestInterceptor>());
                container.Register(Component.
                    For<ITest>().
                    ImplementedBy<Test>().
                    Interceptors(
                        InterceptorReference.ForType<TestInterceptor>()
                    ).Anywhere);

                var tst = container.Resolve<ITest>();
            }
        }
    }
}

This is how memory usage develops:

memory usage

So the thing that threw me off was that I thought there was a non-managed code memory leak, but after a lot of debugging I found out the problem was with the interceptor prxoy generation: a new (dynamic) assembly with a proxy type is introduced to the runtime everytime it's resolved:

interceptor proxies inserted

Now, I guess you can solve this by

  1. using a global (static) container for the entire app, but at the moment this is not feasible for my app (I read this is the preferred way of doing this, not entirely clear why)
  2. generating the proxy yourself using a static ProxyGenerator and using the UsingFactoryMethod way of producing an instance (which I do now)

This leaves me to 3 questions:

  1. Am I using Castle Windsor correctly (docs are not entirely clear) and if so is there a way for Castle Windsor to cache the proxy type?
  2. Should Castle Windsor cache the proxy type automatically (or: is the current behavior a bug)?
  3. How would you debug (using perfmon, for example) that dynamically generated assemblies are eating all your memory?

Thank you.

3
Nonetheless, I would say this memory leak is a bug in Windsor. Altough it might drain the performance of your app, creating one instance of the container per request should never result in a memory leak.Steven

3 Answers

3
votes

I read this is the preferred way of doing this, not entirely clear why

Having one single container for the lifetime of the application is preferred, because:

  • Containers are highly optimized for this scenario. Often generation of delegates using Reflection.Emit and information is cached. This gives a one-time performance hit (per container instance) and speeds up all following requests. Creating a new instance per request can drain the performance of your application, since all this reflection and code emitting is happing again and again.
  • But besides this optimization, the registration process itself takes time. This could be a one-time cost, but you're doing it over and over again.
  • Configuring the container can become much harder. Registering instances that should outlive the the request are are much harder. There are ways around this, but this will often lead to a container configuration that is hard to grasp, hard to maintain and has bugs. Castle Windsor contains a container diagnostics feature that allows to verify the container, but it can't do cross-container verification.
1
votes

I just ran into this same issue today. To answer #3 from the original post, the performance counter [.NET Clr Loading -> Current Assemblies] would have shown a linearly increasing number of assemblies when running your snippet of code due to the dynamic proxy types being loaded.

0
votes

Read the documentation carefuly. Make interceptors always transient.

Correct code is:

public interface ITest{}
public class Test : ITest {}
class TestInterceptor:IInterceptor {
     public void Intercept(IInvocation invocation) {}
}
class Program {
static void Main(string[] args) {
    while(true) {
        using(var container = new WindsorContainer()) {
            container.Register(Component.For<TestInterceptor>().LifestyleTransient());
            container.Register(Component.
                For<ITest>().
                ImplementedBy<Test>().
                Interceptors(
                    InterceptorReference.ForType<TestInterceptor>()
                ).Anywhere);

            var tst = container.Resolve<ITest>();
        }
    }
}
}