Rex Morgan
Rex Morgan

Reputation: 3029

NHibernate Leaking Connections/Transactions

We have NHibernate 3.1 setup using Fluent NHibernate and our session's life is managed using StructureMap 2.6.1. This is within a Web Application using VB.NET (some projects are in C#).

We're getting exceptions from production that makes it sound like multiple threads are attempting to use the same connection/transaction. These only happen when connection pooling is turned on. Turning connection pooling off clears these exceptions up, but we're seeing significant performance issues, so it's a temporary fix.

When calling session.BeginTransaction()

The server failed to resume the transaction. Desc:970000004d. The transaction active in this session has been committed or aborted by another session.

When calling transaction.Rollback()

Transaction not connected, or was disconnected

When attempting to inject ISession through StructureMap. (This only seems to happen occasionally when connection pooling is turned off.)

Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.

Our NHibernateRegistry for SturctureMap looks like this:

var dbConfiguration = MsSqlConfiguration.MsSql2008.ConnectionString(ModuleConfig.GetSettings().NHibernateConnectionString)
     .AdoNetBatchSize(100).IsolationLevel(IsolationLevel.ReadCommitted);

var cfg = Fluently.Configure()
    .Database(dbConfiguration)
    .Mappings(m =>
    {
        m.FluentMappings.AddFromAssemblyOf<MapMarker>();
        m.AutoMappings.Add(AutoMap.AssemblyOf<EntityMarker>()
            .Where(x => 
                x.GetInterface(typeof(ISubClassEntity).Name) == null && 
                x.GetInterface(typeof(IFakeEntity).Name) == null && 
                typeof(BaseEntity).IsAssignableFrom(x))
            .Conventions.AddFromAssemblyOf<ConventionsMarker>()
            .UseOverridesFromAssemblyOf<OverridesMarker>()
            .OverrideAll(map => map.IgnoreProperties(x => !x.CanWrite && !x.Name.EndsWith("Id") && !x.PropertyType.IsEnumerable())));
    })
    .Cache(c => c.UseQueryCache().ProviderClass(typeof(DotNetCacheProvider).AssemblyQualifiedName));

cfg.ExposeConfiguration(x =>
{
    // custom tuplizers here, removed from snippet.
    x.SetProperty("adonet.batch_size", "50");
});

var sessionFactory = cfg.BuildSessionFactory();
For<ISessionFactory>().Singleton().Use(sessionFactory);
For<ISession>().HybridHttpOrThreadLocalScoped().Use(cx =>
{
    var session = cx.GetInstance<ISessionFactory>().OpenSession();
    session.FlushMode = FlushMode.Commit;
    session.SetBatchSize(50);
    return session;
});

At the end of each request, we clean up StructureMap with the following call in the Global.asax:

Sub Application_EndRequest(ByVal sender As Object, ByVal e As EventArgs)
    ' Make sure we dipose of all http scoped objects
    ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects()
End Sub

We have a method that we pass a Func to in order to handle our transactions. This is what that code looks like:

protected virtual TResult Transact<TResult>(Func<TResult> func)
{
    if (!session.Transaction.IsActive)
    {
        TResult result;
        using (var transaction = session.BeginTransaction())
        {
            try
            {
                result = func.Invoke();
                transaction.Commit();
            }
            catch(Exception ex)
            {
                // Make sure the transaction is still active...
                if(session.Transaction.IsActive)
                {
                    transaction.Rollback();
                }
                throw new InvalidOperationException("There was an error while executing within an NHibernate Transaction.", ex);
            }
        }
        return result;
    }
    return func.Invoke();
}

In order to prevent using implicit transactions, we use this Transact method for SELECT statements. Calls to this method look like this (the session is injected via StructureMap using constructor injection):

public T Get(TId id)
{
    return Transact(() => session.Get<T>(id));
}

My question is, how do we stop connections from being shared between multiple threads, causing the exceptions above? If you need more information, please let me know.

Upvotes: 0

Views: 2353

Answers (2)

Peter
Peter

Reputation: 27944

Your problem is in your session management. Each thread should have its own session. The session object is not thread save.

Upvotes: 1

cremor
cremor

Reputation: 6876

I don't know if that's your problem, but your Transact() method seems weird. session.Transaction returns a new transaction if there isn't one currently. So your session.BeginTransaction() does only start the transaction, but doesn't create it. Objects used in a using should also be instantiated there and not before.

Upvotes: 0

Related Questions