Memory leak inside Castle

669 views
Skip to first unread message

erazem

unread,
Feb 20, 2017, 9:22:14 AM2/20/17
to Castle Project Users
In my MVC application there is Castle Windsor used as it is described here:
https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-part-2-plugging-windsor-in.md

When "container.Kernel.ReleaseComponent(controller)" is called, this controller is not disposed and remains in memory forever - it introduce memory leak.
The reason for this must be:

There is only one difference, when you get a controller instance, there is called extension method, which adds some login functionality to controller:

       protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
            {
                throw new HttpException(404,
                    $"The controller for path '{requestContext.HttpContext.Request.Path}' could not be found.");
            }

           return ((IController)container.Kernel.Resolve(controllerType)).AddControllerLoggingFunctionality();
           // if it is without extension methos, there is no memory leak:
           //return (IController)container.Kernel.Resolve(controllerType);
        }

The controller instance looks like this:


So, AddControllerLoggingFunctionality is responsible for memory leak.
This method is inside Logger class and use Castle DynamicProxy interceptor:

 public static class Logger
 {
        private static readonly Castle.DynamicProxy.ProxyGenerator proxyGenerator;

        static Logger()
        {
            proxyGenerator = new Castle.DynamicProxy.ProxyGenerator();
            Castle.DynamicProxy.Generators.AttributesToAvoidReplicating.Add(typeof(ServiceContractAttribute));
        }

       public static TInterface AddControllerLoggingFunctionality<TInterface>(this TInterface implementation) where TInterface : class
        {
            if (implementation == null)
            {
                throw new ArgumentNullException("implementation");
            }

            if (!typeof(TInterface).IsInterface)
            {
                throw new Exception("Type of 'TInterface' must be interface.");
            }

            Castle.DynamicProxy.ProxyGenerationOptions options = new Castle.DynamicProxy.ProxyGenerationOptions();

            var origAttribs = implementation.GetType().GetCustomAttributesData();
            if (origAttribs != null)
            {
                foreach (var origAttrib in origAttribs)
                {
                    options.AdditionalAttributes.Add(AttributeUtil.CreateBuilder(origAttrib));
                }
            }

            return (TInterface)proxyGenerator.CreateInterfaceProxyWithTarget<TInterface>(
                implementation,
                options,
                new ControllerLoggingInterceptor(implementation.GetType()));
        }

       private class ControllerLoggingInterceptor : Castle.DynamicProxy.IInterceptor
        {
            private readonly Type typeTarget = null;
            private readonly ILog logger = null;

            public ControllerLoggingInterceptor(Type type)
            {
                this.typeTarget = type;
                this.logger = LogManager.GetLogger(this.typeTarget);
            }

            public ControllerLoggingInterceptor()
            {
                this.logger = LogManager.GetLogger("NullTargetAutoLogger");
            }

            public void Intercept(Castle.DynamicProxy.IInvocation invocation)
            {
                bool success = false;
                string sbTraceMethod = null;
                long ticksBefore = 0;
                long ticksAfter = 0;

                try
                {
                    if ((this.logger != null) && this.logger.Logger.IsEnabledFor(log4net.Core.Level.Trace))
                    {
                        sbTraceMethod = GetControllerActionName(invocation);
                        logger.TraceFormat("Entering {0}", sbTraceMethod);
                    }

                    ticksBefore = DateTime.Now.Ticks;
                    invocation.Proceed();
                    ticksAfter = DateTime.Now.Ticks;
                    success = true;
                }
                catch (Exception)
                {
                    ticksAfter = DateTime.Now.Ticks;
                    success = false;
                    throw;
                }
                finally
                {
                    if (this.logger != null
                     && this.logger.Logger.IsEnabledFor(log4net.Core.Level.Trace))
                    {
                        logger.TraceFormat("Leaving {0} with success: {1} elapsed time: {2}ms", sbTraceMethod ?? "[no info]", success, TimeSpan.FromTicks(ticksAfter - ticksBefore).TotalMilliseconds);
                    }
                }
            }

            private static string GetControllerActionName(Castle.DynamicProxy.IInvocation invocation)
            {
                ParameterInfo[] methodParams = invocation.Method.GetParameters();
                ParameterInfo activityParameterInfo = methodParams.FirstOrDefault(x =>
                    x.ParameterType.Equals(typeof(System.Web.Routing.RequestContext)));

                if (invocation != null && invocation.Arguments != null)
                {
                    System.Web.Routing.RequestContext rq = invocation.Arguments[activityParameterInfo.Position] as System.Web.Routing.RequestContext;

                    if (rq != null && rq.RouteData != null && rq.RouteData.Values.Count >= 2)
                    {
                        return String.Format(
                            "{0}.{1}()",
                            rq.RouteData.Values["controller"],
                            rq.RouteData.Values["action"]);
                    }
                }
                return "UNKNOWN";
            }
        }
}
The controller object is like this:

What should I change to avoid memory leak and keep functionality as it is?

Thanks

Jonathon Rossi

unread,
Feb 20, 2017, 10:13:25 AM2/20/17
to Castle Project Users
Apologies for not coming back to your StackOverflow question (http://stackoverflow.com/questions/42272082/castle-windsor-proxy-generate-memory-leak) after you added the code for "AddControllerLoggingFunctionality".

Can you further explain what you mean by a memory leak, i.e. what exactly is leaking? The first thing I'd check is what proxy type is being returned by CreateInterfaceProxyWithTarget, i.e. is it the same type each time or is DynamicProxy generating a new type each time and therefore bloating its unclearable type cache. Since you are using ProxyGenerationOptions especially with AdditionalAttributes this is an area we've had a defect and some changes recently (https://github.com/castleproject/Core/blob/master/CHANGELOG.md), do you still get the memory leak if you comment out the line with "options.AdditionalAttributes.Add(...)". What version are you using?


P.S. you should use the Stopwatch class which uses the very quick high precision query performance timer rather than DateTime.Now.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

erazem

unread,
Feb 20, 2017, 10:52:27 AM2/20/17
to Castle Project Users
Memory leak - w3wp.exe memory goes up to 99% of server memory in couple of hours.
If I remove AddControllerLoggingFunctionality(), memory is constant at very low level.

If i override dispose method of some controller, I can see that it is called (in this second case without proxy) when this method is executed:
public override void ReleaseController(IController controller)
        {
            container.Kernel.ReleaseComponent(controller);
        }

If I have  AddControllerLoggingFunctionality, then dispose method is never called. So, I'm 99% sure that this method somehow prevent castle to dispose controllers from memory.

CreateInterfaceProxyWithTarget always return type : Castle.Proxies.IControllerProxy (System.Web.Mvc.IController{Castle.Proxies.IControllerProxy})
But inside this type, there is _target variable, which has always different type: SM.UI.Web.Controllers.DemoController, SM.UI.Web.Controllers.HomeController,..
It can been seen from original debugger picture I have attached how returned controller looks like.

I have commented out the options and now function looks like:

public static TInterface AddControllerLoggingFunctionality<TInterface>(this TInterface implementation) where TInterface : class{
            if (implementation == null)
            {
                throw new ArgumentNullException("implementation");
            }

            if (!typeof(TInterface).IsInterface)
            {
                throw new Exception("Type of 'TInterface' must be interface.");
            }
           
            return (TInterface)proxyGenerator.CreateInterfaceProxyWithTarget<TInterface>(
                implementation, new ControllerLoggingInterceptor(implementation.GetType())
            );
        }

but result is still the same, dispose of controller is never called.

The version is:
Castle.Windsor 3.0.0 for .NETFramework v4.0
Castle.Core 3.3.3 for .NETFramework v4.5

Thanks for help.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

erazem

unread,
Feb 20, 2017, 11:01:39 AM2/20/17
to Castle Project Users
I have just found out that type, which "CreateInterfaceProxyWithTarget" method return is different. It is:

Castle.Proxies.IControllerProxy_1
Castle.Proxies.IControllerProxy_2
Castle.Proxies.IControllerProxy_3

and so on.... 
But each type is returned randomly more than once.

Is this a reason for memory leak? Why it returns different types? Is it because there are different controllers?


On Monday, 20 February 2017 16:13:25 UTC+1, Jonathon Rossi wrote:
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

Jonathon Rossi

unread,
Feb 20, 2017, 11:57:14 PM2/20/17
to Castle Project Users
You'll get a different proxy type per interface, however it looks like your code is creating all proxies with the same IController interface. I can't remember what Castle Core 3.3.3 does with AdditionalAttributes whether it looks at them in the caching code since you are attaching different attributes based on controller implementation type, this is where we had a defect in 3.3.3 which was only half fixed in 4.0.0-alpha001 before being properly fixed in 4.0.0-beta002.

Apologies for not making my request clearer but "w3wp.exe memory goes up to 99% of server memory in couple of hours" isn't very useful to narrow things down, knowing where the memory has gone is what is more useful, i.e. is it leaking controllers or dynamic proxy types or something else, something like dotTrace would be the best tool but there are many other options.

Just taking a step backwards is there a reason you aren't using Windsor's functionality to set up an interceptor when you resolve a controller?

To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

erazem

unread,
Feb 21, 2017, 4:58:58 AM2/21/17
to Castle Project Users
I will try with new version and let you know.

I have opened dotMemory profiling and this is the result:




It shows that there some system collections and reflection increasing?

But If i open second snapshot and go to Largest retained Size I can see that Castle kernel is the one with most bytes:


If I go further, I can see this:



Can you see from this where is the problem?

The interceptor log start and end time for each controller method if trace is enabled in web config - that is all.

sbTraceMethod = GetControllerActionName(invocation);
logger.TraceFormat("Entering {0}", sbTraceMethod);

ticksBefore = DateTime.Now.Ticks;
invocation.Proceed();
ticksAfter = DateTime.Now.Ticks;
success = true;
logger.TraceFormat("Leaving {0} with success: {1} elapsed time: {2}ms", sbTraceMethod ?? "[no info]", success, TimeSpan.FromTicks(ticksAfter - ticksBefore).TotalMilliseconds);

I don't know the reason for dynamic proxy - it is not my code. I'm not that familiar with Castle, I have to read documentation.
I could use Windsor's functionality to set up an interceptor when resolve a controller. Do you have some example?

To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

erazem

unread,
Feb 21, 2017, 5:23:02 AM2/21/17
to Castle Project Users
Maybe this print screen is also of some help:

Jonathon Rossi

unread,
Feb 21, 2017, 8:12:33 AM2/21/17
to Castle Project Users
I don't have the time to really look through all the screenshots to try to guess what is going on, but I was surprised to see so many instances of the controllers living. Can you confirm that your application is calling Release() since it is explicitly calling Resolve(). I'm not sure it'll solve the problem though as you said without the add logging call you don't get the memory leak.

Here are the pages in Windsor's docs that talk about setting up interceptors that'll automatically be configured when you resolve a component, best to use the fluent registration API:

To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

erazem

unread,
Feb 21, 2017, 8:47:01 AM2/21/17
to Castle Project Users
Application is calling Release after each Resolve(). Only dispose on any controller is not called.

I have updated the library to 4.0.0.0. but get the following error:

Attempt by method 'Castle.MicroKernel.SubSystems.Configuration.DefaultConfigurationStore..ctor()' to access method 'System.Collections.Generic.Dictionary`2<System.__Canon,System.__Canon>..ctor()' failed.


public WindsorControllerFactory()
Line 26:         {
Line 27:             container = new WindsorContainer()
Line 28:                 .Install(FromAssembly.This())
Line 29:                 .AddFacility<WcfFacility>();

Any idea?

I will look for interceptors you have send.

thanks

To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

Jonathon Rossi

unread,
Feb 21, 2017, 8:54:18 AM2/21/17
to Castle Project Users
You can't use Castle Core 4.0.0 with Windsor as there is no version of Windsor that currently supports Castle Core 4.0.0.

To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

erazem

unread,
Feb 21, 2017, 9:27:30 AM2/21/17
to Castle Project Users
Thanks, I didn't know about supporting.

If I change extension method to return the same, then controllers are disposed:

public static TInterface AddControllerLoggingFunctionality<TInterface>(this TInterface implementation) where TInterface : class{
        return (TInterface) implementation;
}

If I change it to this:

public static TInterface AddControllerLoggingFunctionality<TInterface>(this TInterface implementation) where TInterface : class{
        return (TInterface)proxyGenerator.CreateInterfaceProxyWithTarget<TInterface>(implementation);
}

controllers are not disposed. So, the problem is in CreateInterfaceProxyWithTarget method.

When release is called:

public override void ReleaseController(IController controller){
            container.Kernel.ReleaseComponent(controller);
}

Is it possible to manually dispose the part of proxy and call release on controller base(_target) or something similar?
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

Jonathon Rossi

unread,
Feb 21, 2017, 9:54:35 AM2/21/17
to Castle Project Users
AH, that'll be it, you aren't call Release() on the controller, you are calling it on the proxy you created manually so Release() is just a no-op to Windsor as it doesn't know about that component and so keeps tracking the controller.

If you use Windsor's built-in interception support you don't have to worry about it as Windsor will know how to dispose of the component when passed its own internally managed proxy.

If you want to test this before changing to Windsor's built-in support, cast your proxy to Castle.DynamicProxy.IProxyTargetAccessor and call DynProxyGetTarget() to get your controller instance.

To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

erazem

unread,
Feb 21, 2017, 11:05:13 AM2/21/17
to Castle Project Users
Thanks, thats it, it works now :) Well I have to run test but it seems it works.

Just 2 questions more:

1.)Each time you create controller, also new Castle.DynamicProxy.IInterceptor is created and CreateInterfaceProxyWithTarget always create new proxy.
Who dispose this objects?

2.)How do you know I have to cast from IControllerProxy to IProxyTargetAccessor?
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.

Jonathon Rossi

unread,
Feb 21, 2017, 11:22:35 AM2/21/17
to Castle Project Users
1. I'm not sure if you are asking how it works now or how it will work under Windsor's management. Right now I suspect your interceptor doesn't implement IDisposable so can't be disposed, the same is true for proxies, they cannot be disposed, the .NET garbage collector will clean them up when nothing references them anymore. The reason you've got this memory leak is that Windsor tracks all components you resolve to manage their lifecycle, so whenever you explicitly call Resolve you must call Release so Windsor can perform decommission concerns including disposing it if it disposable and it'll do things in the right dependency order.

One note about interceptors when using Windsor to manage them is that the interceptor also gets a lifetime depending on how you register it as a component, if you register it as a singleton you'll have just one interceptor instance used for all controllers, if you flag it transient Windsor will create an interceptor for each component that uses it, same goes for other lifetimes like per web request. In your code the interceptor's constructor is passed the controller type, you really don't need to keep any state in your interceptor and can make it a singleton because DynamicProxy will pass you an IInvocation which provides the target type and a whole bunch of other info.

2. DynamicProxy internally generates proxy types that implement a bunch of DP infrastructure interfaces, IProxyTargetAccessor is one.

P.S. I only just looked at what your interceptor code is doing, I would have thought you'd be able to use an action filter or some other ASP.NET MVC hook to record the action duration, the first result in Google shows this: http://stackoverflow.com/questions/17069159/log-duration-of-an-asp-web-api-action

To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.
To post to this group, send email to castle-pro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.

To post to this group, send email to castle-pro...@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages