Timeout expired. - Using Db in ServiceStack Service
Asked Answered
I

1

6

I'm using the Db property in a ServiceStack service to access my database but every now and then I get the following error from IIS:

Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.

Stack Trace:

[InvalidOperationException: Timeout expired.  The timeout period elapsed prior to obtaining a connection from the pool.  This may have occurred because all pooled connections were in use and max pool size was reached.]
   System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) +6371713
   System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) +6372046
   System.Data.SqlClient.SqlConnection.Open() +300
   ServiceStack.OrmLite.OrmLiteConnection.Open() +44
   ServiceStack.OrmLite.OrmLiteConnectionFactory.OpenDbConnection() +132
   ServiceStack.ServiceInterface.Service.get_Db() +68

I have set the ReuseScope in the Configure method to ReuseScope.None which should close the connection on a per request basis I believe? What am I doing wrong here?

public override void Configure(Container container)
{
    JsConfig.EmitCamelCaseNames = true;

    //Register all your dependencies
    ConfigureDb(container);

    //Set MVC to use the same Funq IOC as ServiceStack
    ControllerBuilder.Current.SetControllerFactory(new FunqControllerFactory(container));
}

ConfigureDb:

private static void ConfigureDb(Container container)
{
    var connectionString = ConfigurationManager.ConnectionStrings["AppDb"].ConnectionString;
    container.Register<IDbConnectionFactory>(c =>
        new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider))
        .ReusedWithin(ReuseScope.None);

    using (var db = container.Resolve<IDbConnectionFactory>().Open())
    {
      // Do database seed/initialisation
    }
}

Edit

After more diagnosis, it seems to happen when I refresh the page several times when I call this service method:

public Warranty Get(Warranty request)
    {
        var warranty = new Warranty();
        if (request.Id != default(int))
        {
            warranty = Db.Id<Warranty>(request.Id);
            warranty.WarrantyOrder = ResolveService<WarrantyOrderService>().Get(new WarrantyOrder { WarrantyId = warranty.Id });
            warranty.WarrantyStatus = ResolveService<WarrantyStatusService>().Get(new WarrantyStatus { Id = warranty.StatusId });
            warranty.WarrantyNotes = ResolveService<WarrantyNoteService>().Get(new WarrantyNotes { WarrantyId = warranty.Id });
            warranty.WarrantyDialogues = ResolveService<WarrantyDialogueService>().Get(new WarrantyDialogues { WarrantyId = warranty.Id });
            warranty.WarrantyCredit = ResolveService<WarrantyCreditService>().Get(new WarrantyCredit { WarrantyId = warranty.Id });
            warranty.WarrantyPhotos = ResolveService<WarrantyPhotoService>().Get(new WarrantyPhotos { WarrantyReference = warranty.WarrantyReference });
            warranty.WarrantyReport = ResolveService<WarrantyReportService>().Get(new WarrantyReport { WarrantyId = warranty.Id });
        }

        return warranty;
    }

I have changed ConfigureDb as per @mythz answer below:

    private static void ConfigureDb(Container container)
    {
        var connectionString = ConfigurationManager.ConnectionStrings["AppDb"].ConnectionString;
        container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider));
    }

The service needs to call out other services to populate the other objects on my Warranty object, I'm not sure on how to improve this?

Iverson answered 12/11, 2013 at 11:20 Comment(0)
T
7

The IDbConnectionFactory like all connection managers is a thread-safe factory to create DB Connections, i.e. it's not a DB connection itself. It is supposed to be registered as a singleton.

By default ServiceStack's IOC (Funq) registers as a Singleton by default, so the correct registration is just:

container.Register<IDbConnectionFactory>(c =>
    new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider));

Resolving Services

As ServiceStack Services have the potential to utilize managed resources, they should be used within a using statement whenever they're resolved from another service, so that they're appropriately disposed of, e.g:

using var orders = ResolveService<WarrantyOrderService>();
using var status = ResolveService<WarrantyStatusService>();

var warranty = new Warranty { 
    WarrantyOrder = orders.Get(new WarrantyOrder { WarrantyId = warranty.Id }),
    WarrantyStatus = status.Get(new WarrantyStatus { 
        WarrantyId = warranty.StatusId }),
   //etc
}       

return warranty;
Tropical answered 12/11, 2013 at 11:26 Comment(6)
So, am I using Db incorrectly then or not setting it's ReuseScope correctly?Iverson
Yes remove the ReuseScope as Funq registers as a Singleton by default.Tropical
I'm finding the issue is still occurring, see the edit @TropicalIverson
@BiffBaffBoff that's because they need to be used in a using statement if the services makes use of disposable resources, see updated answer.Tropical
Does this mean that property-injected services are not disposed correctly or does the container take care of it? For example where WarrantyOrderService (from above) is an injected property of the outer service.Virgenvirgie
@Virgenvirgie ServiceStack only disposes the Services, Filters, Dependencies, etc that it resolves itself. So if you manually resolve an autowired dependency from the IOC yourself (i.e. what ResolveService does under-the-hood) than you're responsible for disposing it.Tropical

© 2022 - 2024 — McMap. All rights reserved.