IDbConnection lifecycle management with persistent HTTP connections
Asked Answered
M

2

8

I have a problem managing the lifetime of open database connections with StructureMap scoped to HttpContext when there are persistent HTTP connections in my ASP.NET MVC application, like SignalR hubs.

My DI container, StructureMap, injects an open IDbConnection into several services. To ensure that these database connections are closed and properly disposed of, I call ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects() on the EndRequest event.

This works great for MVC controllers until a service requiring a database connection is injected into a SignalR hub, which keeps a persistent HTTP connection open for each client and eventually saturates the connection pool.

If I scope IDbConnection to a singleton, only one connection is ever opened per-application and the pool doesn't saturate, but this is a bad idea in case the connection is ever locked or times out.

So maybe there is a way to customise the scope of database connections for my SignalR hubs? I tried resolving a service instance in each Hub method, but this still instantiates a database connection at the HttpContext scope and keeps it open for the duration of the calling client's hub connection.

How should I manage the lifetime of database connections with StructureMap in an HTTP-scoped context when there are persistent HTTP connections around?

Example Code

Typical Service

public class MyService
{
    private IDbConnection _con;
    public MyService(IDbConnection con)
    {
        _con = con;
    }

    public IEnumerable<string> GetStuff()
    {
        return _con.Select<string>("SELECT someString FROM SomeTable").ToList();
    }
}

Typical SignalR Hub

public class MyHub : Hub
{
    private MyService _service;
    public MyHub(MyService service)
    {
        _service = service; // Oh Noes! This will open a database connection
                            // for each Client because of HttpContext scope
    }

    public Task AddMessage()
    {
        var result = _service.GetStuff();
        // ...
    }
}

StructureMap Configuration

For<IDbConnection>()
    .HybridHttpOrThreadLocalScoped()
    .Use(() => BaseController.GetOpenConnection(MyConnectionString));

Global.asax.cs

public class GlobalApplication : System.Web.HttpApplication
{
    public GlobalApplication()
    {
        EndRequest += delegate
        {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
        };
    }
    // ...
 }
Maddening answered 8/11, 2012 at 12:10 Comment(5)
Would creating your service on every hub method be a solution here? There isn't really enough information for an answer. How many clients would SignalR manage? How often do they communicate/call server methods?Victualer
Hey @VladCiobanu, I updated my question with notes on resolving a service instance in each hub method. For this specific application, SignalR manages ~10-40 persistent clients (for now), but this would easily max out the connection pool if the load doubles during peak-time. I'm hoping for a canonical answer for my problem.Maddening
I think I have solved the problem by resolving service instances inside each SignalR hub method by using GetNestedContainer() to explicitly dispose of services and open connections it uses.Maddening
Nope, looks like nested containers only explicitly dispose of transient instances, and IDbConnection cannot be transient because of LINQ query materialization in the service layer.Maddening
Solved using a named instance of a transient IDbConnection! Will post answer soon.Maddening
M
1

Solution using transient database connection and nested StructureMap container

First, configure a named, transient database connection instance in StructureMap:

For<IDbConnection>()
    .Transient() // scope
    .Add(x => BaseController.GetOpenConnection(connectionString, IsDebugging()))
    .Named("Transient");

Make sure you configure this before your default instance, or it will override the default instance.

Secondly, inject an IContainer into your SignalR hub so you can build a nested StructureMap container:

public class JobHub : Hub
{
    private readonly IContainer _container;

    public JobHub(IContainer container)
    {
        _container = container;
    }

    public Task DoStuff(string input)
    {
        // ...

Instantiate a nested container in your SignalR method and resolve your named transient database connection:

        using (var httpRequestScope = _container.GetNestedContainer())
        {
            var transientConnection =
                    httpRequestScope.GetInstance<IDbConnection>("Transient");

Use .With<IDbConnection>(transientConnection) to ensure services and repositories instantiated by your nested container use this connection:

            var myService = httpRequestScope
                .With<IDbConnection>(transientConnection)
                .GetInstance<MyService>();

            var result = myService.DoStuff(input);

            return Clients.addResult(result);
        }
    }
}

Finally, the scoped using (...) statement will ensure that your nested container cleans up after itself, including the database connection.

The downside here is that you are opening and closing a database connection for every SignalR method call, but since connections are pooled, releasing early may not be so bad. Your mileage should depend on your SignalR request volume.

You may be able to ditch the nested container and just ask DependencyResolver.Current for the named connection instance, but then you may have to remember to explicitly close each connection to prevent a leak.

Maddening answered 27/11, 2012 at 13:46 Comment(0)
I
1

In SignalR 1.0.0 Alpha, Hub's implement IDisposable. SignalR Hub instances are ephemeral unlike the HttpContext, so if you close your your IDbConnection in the Hub's Dispose method, you shouldn't unnecessarily saturate your connection pool.

Illlooking answered 21/11, 2012 at 3:18 Comment(3)
Thanks @halter73. Default injected IDbConnection instances are scoped to HttpContext, which would affect other services if the hub closed it. My solution was to use a transient named instance of IDbConnection that is resolved and disposed of in a nested StructureMap container for each SignalR method. I'm not sure if this is the best way, but it ensures that my connection pool never saturates.Maddening
It's definitely not the best way. When you use websockets, you'll get a hub instance per call but one long running http request. So you'll leak then.Uzial
See new answer with my solution, @dfowler.Maddening
M
1

Solution using transient database connection and nested StructureMap container

First, configure a named, transient database connection instance in StructureMap:

For<IDbConnection>()
    .Transient() // scope
    .Add(x => BaseController.GetOpenConnection(connectionString, IsDebugging()))
    .Named("Transient");

Make sure you configure this before your default instance, or it will override the default instance.

Secondly, inject an IContainer into your SignalR hub so you can build a nested StructureMap container:

public class JobHub : Hub
{
    private readonly IContainer _container;

    public JobHub(IContainer container)
    {
        _container = container;
    }

    public Task DoStuff(string input)
    {
        // ...

Instantiate a nested container in your SignalR method and resolve your named transient database connection:

        using (var httpRequestScope = _container.GetNestedContainer())
        {
            var transientConnection =
                    httpRequestScope.GetInstance<IDbConnection>("Transient");

Use .With<IDbConnection>(transientConnection) to ensure services and repositories instantiated by your nested container use this connection:

            var myService = httpRequestScope
                .With<IDbConnection>(transientConnection)
                .GetInstance<MyService>();

            var result = myService.DoStuff(input);

            return Clients.addResult(result);
        }
    }
}

Finally, the scoped using (...) statement will ensure that your nested container cleans up after itself, including the database connection.

The downside here is that you are opening and closing a database connection for every SignalR method call, but since connections are pooled, releasing early may not be so bad. Your mileage should depend on your SignalR request volume.

You may be able to ditch the nested container and just ask DependencyResolver.Current for the named connection instance, but then you may have to remember to explicitly close each connection to prevent a leak.

Maddening answered 27/11, 2012 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.