TransactionScope TransactionAborted Exception - transaction not rolled back. Should it be?

MT. picture MT. · Aug 10, 2011 · Viewed 33.1k times · Source

(SQL SERVER 2008) If a Transaction Timeout error occurs within a TransactionScope (.Complete()) would you expect the transaction to be rolled back?

Update:
The error is actually being thrown in the closing curly brace (i.e. .Dispose()), not .Complete(). Full error is:

The transaction has aborted. System.Transactions.TransactionAbortedException TransactionAbortedException System.Transactions.TransactionAbortedException: The transaction has aborted. ---> System.TimeoutException: Transaction Timeout
   --- End of inner exception stack trace ---
   at System.Transactions.TransactionStateAborted.BeginCommit(InternalTransaction tx, Boolean asyncCommit, AsyncCallback asyncCallback, Object asyncState)
   at System.Transactions.CommittableTransaction.Commit()
   at System.Transactions.TransactionScope.InternalDispose()
   at System.Transactions.TransactionScope.Dispose()

As far as I can tell the transaction is not rolled back and the tables remained locked until I issued a KILL against the SPID/session_id.

I used DBCC OPENTRAN to get the oldest transaction and then KILL it. I have tried KILL WITH STATUS but get a message that no status is available as nothing is being rolled back. Status of the SPID/session_id in sys.dm_exec_sessions is 'sleeping'. Code snippet:

try
{            
    using (var transaction = new TransactionScope())
    {
        LOTS OF WORK CARRIED OUT WITH LINQ ENTITIES/SubmitChanges() etc.
        transaction.Complete();  //Transaction timeout
    }
    return result;
}
catch (Exception ex)
{
    logger.ErrorException(ex.Message, ex);
    result.Fail(ex.Message);
    return result;
}

UPDATE:
Problem is not entirely solved, but further information should anyone else have this problem.

  1. I am using LINQ to SQL and within the transaction scope I call context.SubmitChanges(). I am carrying out a lot of inserts. SQL Server profiler indicates that a separate INSERT statement is issued for each insert.
  2. In development, if I sleep the thread for 60 seconds (default TransactionScope timeout is 60 seconds) BEFORE calling SubmitChanges() then I get a different error when calling TransactionScope.Complete() (The operation is not valid for the state of the transaction.).
  3. If I sleep for 60 seconds AFTER .SubmitChages() and just before .Complete() then I get 'The transaction has aborted - System.TimeoutException: Transaction Timeout'
  4. NOTE however that on my dev machine no open transactions are found when using DBCC opentran - which is what you would expect as you would expect the transaction to rollback.
  5. If I then add the code at the bottom of this question (sorry couldn't get the website to insert it here) to my config file which increases the TransactionScope timeout to 2 minutes, things start working again (research indicates that if this doesn't work there could be a setting in machine.config that is lower than this that is taking precedence).
  6. Whilst this will stop the transaction aborting, due to the nature of the updates, it does mean that locks on a core business table could be up to 2 minutes so other select commands using the default SqlCommand timeout of 30 seconds will timeout. Not ideal, but better than an open transaction sitting there and totally holding up the application.
  7. A few days ago we had a disastrous release that meant we ran out of diskspace mid upgrade (!) so we did end up using the shrink database functionality which apparently can cause performance problems after you have used it.
  8. I feel a rebuild of the database and a rethink of some business functionality coming on...

Answer

alun picture alun · Aug 11, 2011

I'm thinking that the TransactionAbortedException is actually a timeout. If so you should find that the InnerException of the TransactionAbortedException is a timeout.

You should be able to get rid of it by making sure that the timeout of the transactionscope is longer than the command timeout.

Try changing the transaction scope to something like this:

new TransactionScope(TransactionScopeOption.Required, TimeSpan.FromSeconds(60))

And also set an explicit timeout on your context. Should be something like:

myContext.CommandTimeout = 30; //This is seconds