We are having an issue when using NHibernate with distributed transactions.
Consider the following snippet:
//
// There is already an ambient distributed
We've finally narrowed this down to a cause.
When opening a session, if there is an ambient distributed transaction, NHibernate attaches an event handler to the Transaction.TransactionCompleted, which closes the session when the distributed transaction is completed. This appears to be subject to a race condition wherein the connection may be closed and returned to the pool before the deadlock error propagates across, leaving the connection in an unusable state.
The following code will reproduce the error for us occasionally, even without any load on the server. If there is extreme load on the server, it becomes more consistent.
using(var scope = new TransactionScope()) {
//
// Force promotion to distributed transaction
//
TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
var connection = new SqlConnection(_connectionString);
connection.Open();
//
// Close the connection once the distributed transaction is
// completed.
//
Transaction.Current.TransactionCompleted +=
(sender, e) => connection.Close();
using(connection.BeginTransaction())
//
// Deadlocks but sometimes does not raise exception
//
ForceDeadlockOnConnection(connection);
scope.Complete();
}
//
// Subsequent attempts to open a connection with the same
// connection string will fail
//
We have not settled on a solution, but the following things will eliminate the problem (while possibly having other consequences):
According to Microsoft (https://connect.microsoft.com/VisualStudio/feedback/details/722659/), the SqlConnection class is not thread-safe, and that includes closing the connection on a separate thread. Based on this response we have filed a bug report for NHibernate (http://nhibernate.jira.com/browse/NH-3023).
It is an NHibernate issue. NHibernate is not opening and closing the connection on the same thread, which is not supported by ADO.NET. You can work around it by opening and closing the connection yourself. NHibernate will not close the connection unless it has also opened it.
Workaround
var connection = ((SessionFactoryImpl)_sessionFactory).ConnectionProvider.GetConnection();
using(var session = _sessionFactory.OpenSession(connection))
{
//do database stuff
}
connection.Close();
not a definitive answer, but i suspect you have some problems with session management and that you are using the same session across multiple calls to handlers. i don't think it's actually the connection that is in a bad state, but rather the nhibernate session. this doesn't seem to jive with you not seeing the problem with connection pooling turned off, so i may be off base, but i still suspect it has to do with reusing sessions.
the first thing i would suggest is that you try to confirm this by logging the hashcode of the session and the hashcode of session.GetSessionImplementation() (my understanding of using the castle nhibernate facility is that you will see the same instance of session, even though it is actually a different session and the session implementation will actually show a difference). see if you are seeing the same hashcodes being used in handling different messages.
if it is a question of session management, try using a nservicebus module to manage your sessions for your handlers. here is a post from andreas about doing that. i don't think his edit about having a way to do this built in on the trunk was in the 2.5 release, so you probably want to go ahead with this. (i could be wrong about that.)
http://andreasohlund.net/2010/02/03/nhibernate-session-management-in-nservicebus/
This doesn't exactly solve your problem, but you could make your IPreInsertEventListener just send a NSB message, and then have the receiver of the message invoke the stored procedure. I've done that with problematic pre-and post event listeners while using NHibernate and NSB in the past.
Another thought is have your pre-event listener create its own connection object wrapped in a nice using statement, then it won't touch NHibernate's connection. If it deadlocks, then just do a throw an make sure you've disposed of any object's in scope.