Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle NHibernate Transaction Errors

Our application (which uses NHibernate and ASP.NET MVC), when put under stress tests throws a lot of NHibernate transaction errors. The major types are:

  1. Transaction not connected, or was disconnected
  2. Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
  3. Transaction (Process ID 177) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Can someone help me in identifying the reason for Exception 1? I know I have to handle the other exceptions in my code. Can someone point me to resources which can help me handle these errors in an efficient manner?

Q. How do we manage Sessions and Transactions?

A. We are using Autofac. For every server request, we create a new request container which has the session in the container lifetime scope. On activating the session we begin the transaction. When the request completes, we commit the transaction. In some cases, the transaction can be huge. To simplify, every server request is contained in a transaction.

like image 355
Zuber Avatar asked Aug 03 '10 07:08

Zuber


2 Answers

Have a look at this thread: http://n2cms.codeplex.com/Thread/View.aspx?ThreadId=85016

Basically what it says as a possible cause of this exception:

2010-02-17 21:01:41,204 1 WARN NHibernate.Util.ADOExceptionReporter - System.Data.SqlClient.SqlException: The transaction log for database 'databasename' is full. To find out why space in the log cannot be reused, see the log_reuse_wait_desc column in sys.databases

As the transaction log's size is proportional to the amount of work done during the transaction, perhaps you ought to look into putting your transactional boundaries across command handlers 'handling' of commands on the write-part of transactions. You would then, with a session#X, load the state you wish to mutate, mutate it and commit it, all as one unit of work in #X.

With regards to the read-side of things, you might then have another ISession#Y that reads data; this ISession could be used to batch reads within e.g. RepeatableRead or something similar with the Futures feature and could simply be reading from a cache (albiet it being a crutch indeed). Doing it this way might help you recover from "errors" that aren't; livelocks, deadlocks and victim transactions.

The problem with using a transaction per request is that your ISession acquires a lot of book keeping data while you are working, all of which is part of the transaction. Hence the database marks the datas (rols, cols, tables, etc) as partaking in the transaction, causing the wait-graph to span 'entities' (in the database-sense, not the DDD-sense), which are not actually part of the transactional boundary of the command your application took.

For the record (other people googling this), Fabio had a post dealing with dealing with exceptions from the data layer. Quoting some of his code;

public class MsSqlExceptionConverterExample : ISQLExceptionConverter
{
  public Exception Convert(AdoExceptionContextInfo exInfo)
  {
      var sqle = ADOExceptionHelper.ExtractDbException(exInfo.SqlException) as SqlException;
      if(sqle != null)
      {
          switch (sqle.Number)
          {
              case 547:
                  return new ConstraintViolationException(exInfo.Message,
                      sqle.InnerException, exInfo.Sql, null);
              case 208:
                  return new SQLGrammarException(exInfo.Message,
                      sqle.InnerException, exInfo.Sql);
              case 3960:
                  return new StaleObjectStateException(exInfo.EntityName, exInfo.EntityId);
          }
      }
      return SQLStateConverter.HandledNonSpecificException(exInfo.SqlException,
          exInfo.Message, exInfo.Sql);
  }
}
  • 547 is the exception number for constraint conflict.
  • 208 is the exception number for an invalid object name in the SQL.
  • 3960 is the exception number for Snapshot isolation transaction aborted due to update conflict.

So if you are running into concurrency issues like what you describe; remember that they will invalidate your ISession and that you'd have to handle them like the above.

Part of what you might be looking for is CQRS, where you have separate read and write-sides. This might help: http://abdullin.com/cqrs/, http://cqrsinfo.com.

So to summarize; your problems might be related to the way your handle your transactions. Also, try running select log_wait_reuse_desc from sys.databases where name='MyDBName' and see what it gives you.

like image 113
Henrik Avatar answered Nov 17 '22 13:11

Henrik


This thread has an explanation: http://groups.google.com/group/nhusers/browse_thread/thread/7f5fb68a00829d13

In short, the database probably rolls back the transaction by itself due to some error, so that when you try to rollback the transaction later it is already rolled back and in a zombie state. This tends to hide the actual reason for the rollback since all you see is a TransactionException instead of the exception that actually triggered the rollback in the first place.

I don't think there is much you can do about it beyond logging it and trying to figure out what is causing the underlying error.

like image 22
Samuel Otter Avatar answered Nov 17 '22 11:11

Samuel Otter