Search code examples
castle-windsortyped-factory-facility

Castle Windsor 3 Interceptor not releasing components created by a typed factory but 2.5.4 did. Why?


This is a similar pattern to ones stated elsewhere and detailed in this blog post. I have this working using Windsor 2.5.4 pretty much as stated in the blogpost, but decided to switch to using Windsor 3. When I did this I noticed that the memory usage of the application go up over time - I guessed this would be that components were'nt being released.

There were a couple of modifications to the code in the blogpost, which may have caused the behaviour to differ.

Here is my AutoRelease interceptor (straight out of the blogpost, here for convenience and the lazy ;) )

[Transient]
public class AutoReleaseHandlerInterceptor : IInterceptor
{
    private static readonly MethodInfo Execute = typeof(IDocumentHandler).GetMethod("Process");
    private readonly IKernel _kernel;
    public AutoReleaseHandlerInterceptor(IKernel kernel)
    {
        _kernel = kernel;
    }

    public void Intercept(IInvocation invocation)
    {
        if (invocation.Method != Execute)
        {
            invocation.Proceed();
            return;
        }
        try
        {
            invocation.Proceed();
        }
        finally
        {
            _kernel.ReleaseComponent(invocation.Proxy);
        }
    }
}

One of my deviations from the blog post is the selector that the typed factory uses:-

public class ProcessorSelector : DefaultTypedFactoryComponentSelector
{
    protected override Func<IKernelInternal, IReleasePolicy, object> BuildFactoryComponent(MethodInfo method,
                                                                                           string componentName,
                                                                                           Type componentType,
                                                                                           IDictionary additionalArguments)
    {
        return new MyDocumentHandlerResolver(componentName,
            componentType,
            additionalArguments,
            FallbackToResolveByTypeIfNameNotFound,
            GetType()).Resolve;
    }
    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        return null;
    }
    protected override Type GetComponentType(MethodInfo method, object[] arguments)
    {
        var message = arguments[0];
        var handlerType = typeof(IDocumentHandler<>).MakeGenericType(message.GetType());
        return handlerType;
    }
}

What might be noticeable is that I do not use the default resolver. (This is where, perhaps, the problem lies...).

public class MyDocumentHandlerResolver : TypedFactoryComponentResolver
{

    public override object Resolve(IKernelInternal kernel, IReleasePolicy scope)
    {
        return kernel.Resolve(componentType, additionalArguments, scope);
    }
}

(I omitted the ctor for brevity- nothing special happens there, it just calls the base ctor).

The reason I did this was because the default resolver would try to resolve by name and not by Type- and fail. In this case, I know I only ever need to resolve by type, so I just overrode the Resolve method.

The final piece of the puzzle will be the installer.

container.AddFacility<TypedFactoryFacility>()
         .Register(
          Component.For<AutoReleaseHandlerInterceptor>(),
          Component.For<ProcessorSelector>().ImplementedBy<ProcessorSelector>(),
          Classes.FromAssemblyContaining<MessageHandler>()
                 .BasedOn(typeof(IDocumentHandler<>))
                 .WithService.Base()
                 .Configure(c => c.LifeStyle.Is(LifestyleType.Transient)),
          Component.For<IDocumentHandlerFactory>()
                   .AsFactory(c => c.SelectedWith<ProcessorSelector>()));

Stepping through the code, the interceptor is called and the finally clause is executed (e.g. I didn't get the method name wrong). However, the component does not seem to be released (using the performance counter shows this. Each invocation of the factory's create method increases the counter by one).

So far, my workaround has been to add a void Release(IDocumentHandler handler) method to my factory interface, and then after it executes the handler.Process() method, it explicitly releases the handler instance, and this seems to do the job- the performance counter goes up, and as the processing is done, it goes down).

Here is the factory:

public interface IDocumentHandlerFactory
{
    IDocumentHandler GetHandlerForDocument(IDocument document);
    void Release(IDocumentHandler handler);
}

And here is how I use it:

IDocumentHandlerFactory handler = _documentHandlerFactory.GetHandlerForDocument(document);
handler.Process();
_documentHandlerFactory.Release(handler);

Doing the Release explicitly therefore negates the need for the interceptor, but my real question is why this behaviour differs between the releases?


Solution

  • Note to self:- RTFM. Or in fact, read the Breakingchanges.txt file.

    Here's the change that affects this behaviour (emphasis is mine):-

    change - IReleasePolicy interface has a new method: IReleasePolicy CreateSubPolicy(); usage of sub-policies changes how typed factories handle out-of-band-release of components (see description)

    impact - medium fixability - easy

    description - This was added as an attempt to enable more fine grained lifetime scoping (mostly for per-typed-factory right now, but in the future also say - per-window in client app). As a side-effect of that (and change to release policy behavior described above) it is no longer possible to release objects resolved via typed factories, using container.Release. As the objects are now tracked only in the scope of the factory they will be released only if a call to factory releasing method is made, or when the factory itself is released.

    fix - Method should return new object that exposes the same behavior as the 'parent' usually it is just best to return object of the same type (as the built-in release policies do).

    I didn't find the fix suggestion terribly helpful in my instance, however my solution in the question is what you should actually do (release using the factory). I'll leave it up in case anyone else has this (non) issue.