If you’re using NServiceBus together with your own instance of Castle Windsor, there is one thing you really need to look out for. NServiceBus was originally developed with Spring.NET as its IoC container, but it’s been changed to support multiple containers in a similar manner as Agatha does it. Agatha however, was originally developed with Castle Windsor as its IoC container, and as such is well aware of Windsor’s need to explicitly release resolved components. NServiceBus unfortunately was not aware of this need, and a workaround that they have introduced is to set Windsor’s ReleasePolicy to the NoTrackingReleasePolicy (which doesn’t hold any instances in memory, but doesn’t provide any cleanup either) if you configure NServiceBus to use its own instance of Castle Windsor. However, if you’re integrating NServiceBus into a project that is already using Castle Windsor, then you probably want NServiceBus to use your instance of Castle Windsor.
And that is when problems might appear. If you’re using Castle Windsor with the default ReleasePolicy (which is the LifecycledComponentsReleasePolicy) then each resolved transient instance will be stored in memory by that policy until the instance is explicitly released. The benefit of this policy is that the container can automatically dispose any disposable transient dependency of a resolved component. In my case, i’ve come to rely on that feature to achieve deterministic disposal behavior throughout my code base.
Now, when you configure NServiceBus and pass it your instance of Castle Windsor, it obviously doesn’t change the ReleasePolicy like it does when it creates its own instance of Castle Windsor. This is good because changing the policy of a passed in container would almost certainly have a huge behavioral implication for the application and such an action would be unacceptable when integrating a new framework into your project. But since NServiceBus doesn’t have the notion of needing to release resolved components, every single transient instance it resolves through your container will be stored in memory until the application’s process is terminated. Which means that you’ll leak instances of the following types for each message that your system needs to handle:
- NServiceBus.Grid.MessageHandlers.GridInterceptingMessageHandler
- NServiceBus.Sagas.Impl.SagaMessageHandler
- Your own MessageHandlers (and their transient dependencies as well)
If your MessageHandlers don’t have dependencies (highly unlikely if you’re already using an IOC container) then you’d still have 3 leaking instances per incoming message. Add the number of transient dependencies of any handler to that and the number of leaking instances can increase dramatically.
First of all, if you do not depend on Windsor’s default ReleasePolicy’s behavior, then the easiest way to avoid this problem is definitely to set the container’s ReleasePolicy to the NoTrackingReleasePolicy like NServiceBus does itself when it’s configuring itself with a new instance of the container.
If you do depend on it, and you don’t want leaking instances that hang around forever, then the approach listed below is one way to solve this problem. There are probably other solutions available, and while the approach listed below can definitely be considered to be a HACK, i do think it’s the best solution to this particular problem.
Because we don’t want to change anything about the way that we’re already using the container, we just need to make sure that NServiceBus’ usage of the container doesn’t conflict with ours. That basically means that we need to make sure that the components that it resolves should not be tracked by the container. There are 2 kinds of components that NServiceBus will resolve during normal operation:
- Its own components that it needs to implement the features it offers you
- Your message handlers that you need to handle incoming messages
The first category is easy to exclude from Windsor’s tracking behavior. We can simply create our own ReleasePolicy which extends the default ReleasePolicy, and make sure that any instances of an NServiceBus-type are no longer tracked by the container:
public class MyReleasePolicy : Castle.MicroKernel.Releasers.LifecycledComponentsReleasePolicy
{
public override void Track(object instance, Castle.MicroKernel.Burden burden)
{
if (!instance.GetType().FullName.StartsWith("NServiceBus"))
{
base.Track(instance, burden);
}
}
}
Then you need to set this release policy somewhere in your application’s startup routine:
IoC.Container.Kernel.ReleasePolicy = new MyReleasePolicy();
(in this case, IoC.Container returns an IWindsorContainer instance)
This will make sure that no instances of NServiceBus-types will ever leak in the container. But now we still have to deal with our message handlers. The solution to making sure they are not disposed is not the cleanest out there but hey, it works. I basically introduced a base class that all my message handlers will need to inherit from, with the following implementation in the Handle method:
public abstract class MessageHandler<TMessage> : IMessageHandler<TMessage> where TMessage : IMessage
{
public void Handle(TMessage message)
{
try
{
Process(message);
}
finally
{
// ugly as hell, but we need this until NSB releases its resolved components
IoC.Container.Release(this);
}
}
protected abstract void Process(TMessage message);
}
Uh oh… i just felt a great disturbance in the Force, as if tens of voices suddenly cried out in terror about my direct usage of the IoC container in a component!
Conceptually, this is wrong on many levels. Then again, this makes sure that the message handlers (and their dependencies) that are resolved by NServiceBus will always be guaranteed to be released by the comtainer. It might not be nice, but it avoids the memory leak and it doesn’t force me to change my other code.
And once NServiceBus is modified to release the components it resolves (if it’s ever modified that is…) i only need to get rid of my custom policy and the try/finally block. Unfortunately, my existing message handlers will then all implement the Process method instead of the Handle method but that is quickly fixed with a simple rename-refactoring.
Even though this is a pretty big hack (IMHO), at least its impact on the code is minimal… it will be easy to get rid of once the real problem is solved in NSB, and there are no real downsides to this that i can think of at the moment.