1
votes

I'm testing a SignalR server (self-hosted in a console application) that will eventually form the basis of a data logging system. A test client is making approx 180 calls/sec to the hub, while the messages themselves are very small (just a name/value pair).

I'm using Castle Windsor for DI, with a custom IHubActivator to resolve the Hub instance:

internal class WindsorHubActivator : IHubActivator
{
    private readonly IWindsorContainer _container;

    public WindsorHubActivator(IWindsorContainer container)
    {
        _container = container;
    }

    public IHub Create(HubDescriptor descriptor)
    {
        var hubType = descriptor.HubType;
        return _container.Resolve(hubType) as IHub;
    }
}

Here is the hub class:

public class TelemetryHub : Hub
{
    private readonly TelemetryDataService _telemetryDataService;

    public TelemetryHub(TelemetryDataService telemetryDataService)
    {
        _telemetryDataService = telemetryDataService;
    }

    public void LogTelemetryData(string name, double value)
    {
        _telemetryDataService.LogTelemetryData(name, value);
    }
}

When the hub class is registered with Windsor as "Transient", the memory consumption climbs steadily until it hits 2Gb then falls over with an OOM exception. If I instead register the Hub as "Singleton" then the application memory consumption remains very low and consistent.

The TelemetryDataService class is not the issue. I've commented out the hub's constructor and method code and the problem still occurs.

Out of curiosity I then took things a stage further and changed the WindsorHubActivator class to take Windsor out of the equation:

internal class WindsorHubActivator : IHubActivator
{
    ...

    public IHub Create(HubDescriptor descriptor)
    {
        return new TelemetryHub(new TelemetryDataService());
    }
}

This time the memory problem went away, so I'm assuming Windsor is holding onto the created hub instances and preventing them from being garbage-collected. What's the solution? I understand it's not recommended to use a singleton Hub, and I don't want to leave the IHubActivator in the above "hardcoded" state.

2
It seems this is a known issue that should be resolved in SignalR v3.Andrew Stephens

2 Answers

5
votes

For transient components that are explicitly Resolved from the container, you need to explicitly Release them to let Windsor know it can release its reference to them:

Tran­sient com­po­nents are sim­i­lar to pooled, because there’s no well known end to tran­sient component’s life­time, and Wind­sor will not know if you still want to use a com­po­nent or not, unless you explic­itly tell it (by call­ing Release). Since tran­sient com­po­nents are by def­i­n­i­tion non-shared Wind­sor will imme­di­ately destroy the com­po­nent when you Release it.

So, I believe you have two options:

  1. If you can know where IHub.Dispose() will be called, you can just put in a call to container.Release there. This is probably not the best way, since you'd be coupling your usage of IHub to how it is created, which is one of the big problems (if not the problem) that using an IOC container solves in the first place.

  2. If you don't know where IHub.Dispose() will be called, you can wrap the hub and container in an object to manage this for you. Something like this:

    public class HubContainerWrapper : IHub, IDisposable
    {
        IWindsorContainer container;
        IHub hub;
    
        public HubContainerWrapper (IWindsorContainer container, IHub hub)
        {
            this.container = container;
            this.hub = hub;
        }
    
        ~HubContainerWrapper()
        {
            Dispose(false);
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (hub != null)
                {
                    try
                    {
                        hub.Dispose();
                    }
                    finally
                    {
                        container.Release(hub);
                        container = null;
                        hub = null;
                    }
                }                   
            }
        }
    
        // forward all IHub calls to hub member
    }
    

    Then in your IHubActivator:

    public IHub Create(HubDescriptor descriptor)
    {
        var hubType = descriptor.HubType;
        var hub = _container.Resolve(hubType) as IHub;
        return new HubContainerWrapper(_container, hub);
    }
    

    This way, when SignalR disposes your hub wrapper, it will release the actual hub that you resolved from the container.

1
votes
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        //this value default is 1000
        GlobalHost.Configuration.DefaultMessageBufferSize = 32;
        app.MapSignalR();
    }
}