Proper Hub dependency lifetime management for SignalR and Castle Windsor
Asked Answered
F

1

37

I have some SignalR hubs which may need to access some transient and singleton dependencies. Hooking the creation of the Hub is easy and works just fine however SignalR does its own Dispose() call on the created Hub rather than notifying the dependency resolver and letting it get involved in the disposal.

This isn't such a big deal if the dependencies are registered singletons, but if they're registered as transients then they'll never get disposed (if that was required) and Windsor will keep them alive until the Windsor container is collected (when the web server is shutting down anyway).

I see several possible ways of handling this...

a) Someone here points out a way to subclass SignalR's HubDispatcher class so that it can do proper disposal. It's not part of SignalR's standard DependencyResolver so this might be difficult / impossible

b) Some other class in SignalR, elsewhere in the pipeline, can be overridden or easily replaced so that we could subclass HubDispatcher and ensure that subclass is used. From what I can tell this would have to be the Owin middleware class HubDispatcherMiddleware. Is there some way to force Owin to not register this class and instead register my own version of this (which in turn uses my own HubDispatcher)?

c) There's some way of intercepting the Dispose() call made by SignalR on my Hub classes so that a call could be made back to Windsor to ensure any dependencies are properly disposed and released from the container

d) Studiously avoid using transient lifestyle dependencies and instead pass in typed factories so that we can resolve and release each dependency via the typed factory within the Hub

At the moment (d) is the only one I know how to do. (a) or (b) would be great. (c) is mostly covered by this post http://kozmic.net/2010/01/27/transparently-releasing-components-in-windsor/, however, the interceptor requires that Dispose() be called via IDisposable. SignalR's HubDispather class' implementation of hub disposal is

private static void DisposeHubs(IEnumerable<IHub> hubs)
{
    foreach (var hub in hubs)
    {
        hub.Dispose();
    }
}

No casting to IDisposable there... Also Dispose() on the Hub class is virtual and that blog post implies that a virtual Dispose() could add some complexity (I'm not quite sure how much and I don't know enough about Castle's interceptors and whether or not that missing cast to IDisposable can be worked around anyway).

I appreciate I've written this question for a fairly narrow audience - those who have used Windsor AND SignalR and care about more than just resolving dependencies. Every example I've found, including those on StackOverflow, seems to just ignore the release of dependencies.

Thanks!

Fortification answered 23/2, 2014 at 11:21 Comment(6)
It is frustrating when libraries purport to be IoC friendly but only support Resolve() and not Release().Wilmot
Absolutely... I'm thinking of compiling my own copy of SignalR and, if I can work out the GitHub stuff, create some sort of pull request with reasoning for the (very slight) change required to the code.Fortification
For others who find this, as of Feb 2014, official advice is at github.com/SignalR/SignalR/issues/2908. A future version ought to improve on this. I'll probably just compile my own slightly tweaked copy in the meantime.Fortification
To be sure I've understood their answer is it "wait until the next release"? If so as a temporary measure is the best route an abstract subclass of Hub: WindsorHub with the disposal logic in there and then just inherit from that?Scrape
I ended up registering my hubs with singleton lifetime in Windsor. What's injected into the hubs are other singleton objects anyway, or they're typed factories, which get called to get the required object, use it and then call the factory's release method.Fortification
Scratch my earlier advice :) I've just gone through and switched my hubs to use transient registration. My Windsor dependency resolver for SignalR then handles things this way: if something implementing IHub is resolved, it's immediately released from the container before being returned. It works for me because hub disposal is harmless and, for me at least, I'm only injecting long-lived singletons into my hubs anyway, so the release of the hub instance doesn't do anything to its dependencies. This way I can avoid the Groups property being intermingled on parallel requests.Fortification
F
2

I've had a bit similar problem but with Unity instead of Castle Windsor.

My requirements:

  • I wanted to avoid singleton registrations on the container.
  • All objects are resolved in Hub and should be disposed on Hub destruction.
  • Registrations reused across Web Api and SignalR.
  • Object lifetime is managed by HierarchicalLifetimeManager - child containers resolve and manage separate object instances. Registered like this:
container.RegisterType<IMessageService, MessageService>(new HierarchicalLifetimeManager());

This is my solution:

[HubName("exampleHub")]
public class ExampleHub : Hub
{
    IUnityContainer _container;

    public CarrierApiHub(IUnityContainer container) // container itself injected in hub
    {
        _container = container.CreateChildContainer(); // child container derived from the main container.
    }

    public async Task<int> UnreadMessagesCount()
    {
        // Here i'm resolving instance of IMessageService which depends on
        // other registrations specified on the container. Full object graph
        // is constructed and destroyed on hub disposal.
        var messageSvc = _container.Resolve<IMessageService>();
        return await messageSvc.CountUnreadOf(UserId);
    }

    protected override void Dispose(bool disposing)
    {
        _container.Dispose(); // child container destroyed. all resolved objects disposed.
        base.Dispose(disposing);
    }

    private int UserId
    {
        get
        {
            // only an example
            var claim = ((ClaimsPrincipal)Context.User).GetClaim("user_id");
            return int.Parse(claim.Value);
        }
    }
}

SignalR and dependency resolver configuration:

public static class ConfigureSignalR
{
    public static void Initialize(UnityContainer unityContainer, IAppBuilder app)
    {
        app.Map("/signalr", map =>
        {
            var resolver = new AppSignalRDependencyResolver(unityContainer);

            map.UseCors(CorsOptions.AllowAll);

            var hubConfiguration = new HubConfiguration
            {
                EnableJavaScriptProxies = false,
                EnableJSONP = true, // Required for IE 9 (supports only polling)
                Resolver = resolver
            };

            map.RunSignalR(hubConfiguration);
        });
    }
}

Dependency resolver implementation:

public class AppSignalRDependencyResolver : DefaultDependencyResolver
{
    protected IUnityContainer _container;

    public AppSignalRDependencyResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        this._container = container.CreateChildContainer();
    }

    public override object GetService(Type serviceType)
    {
        try
        {
            return _container.Resolve(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return base.GetService(serviceType);
        }
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return _container.ResolveAll(serviceType).Concat(base.GetServices(serviceType));
        }
        catch (ResolutionFailedException)
        {
            return base.GetServices(serviceType);
        }
    }

    protected override void Dispose(bool disposing)
    {
        _container.Dispose();
        base.Dispose(disposing);
    }
}
Fitzpatrick answered 27/5, 2015 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.