Posts Tagged ‘System.Transaction’

Recently I was working on a strange problem faced by a development team.They had used .NET 2.0 Lightweight Transaction Manager provided by the classes under System.Transaction namespace.Within the TransactionScope a long running database job was executing.They faced an intermittent TransactionAbortedException saying “Transaction has been aborted”.

After taking a look into the code it was found that they are using a ADO.NET Command Timeout of about 900s.But there is no command timeout set for the Transaction.The default timeout period of Transaction is 60s.So there can be a situation where the database job is taking more than a minute to complete and Transaction Manager is aborting the transaction as transaction timeout has expired even though database process has not timed out.

We can set the Transaction timeout in two ways using the constructor of the TransactionScope class or config.

  • The following two constructors of TransactionScope class accepts a TimeSpan parameter which defines the timeout period for the code executing within the context of that TransactionScope.
    • public TransactionScope(Transaction transactionToUse,TimeSpan scopeTimeout)
    • public TransactionScope(TransactionScopeOption scopeOption,TimeSpan scopeTimeout)
  • We can also define the timeout in the application configuration (web.config or app.config) as shown

                 <system.transactions>

                       <defaultSettings timeout=”02:00:00″/>

                </system.transactions>

            We need to define the max timeout setting at machine level in machine.config

            <system.transactions>
                    <machineSettings maxTimeout=”02:00:00″/>
            </system.transactions>

But it was very surprising that there was no timeout exception instead a transaction aborted message which was very confusing.Then I came to know from the following MSDN Forum post that timeout is actually wrapped as an inner exception:

http://social.msdn.microsoft.com/forums/en-US/adodotnetdataproviders/thread/9f9c1d11-6c0d-423e-bffc-4d1957e9bdd7

Then I wrote the following snippet to simulate the situation:

using(TransactionScope scope = new TransactionScope(
                                TransactionScopeOption.RequiresNew,
                                new TimeSpan(0,0,30)))
{
     Console.WriteLine(“Entered Transaction Scope”);
     System.Threading.Thread.Sleep(60000);
     Console.WriteLine(“Start Commit”);
     scope.Complete();
     Console.WriteLine(“Transaction Complete.”);
}

Here I have set transaction timeout 30 s and the code block will take 60 s to execute.

The program threw an exception as shown below:

timeout

But surprisingly the exception was not thrown just after 30 s elapsed.The exception was thrown after the entire code block completed it’s execution.The exception was thrown while exiting from the using {} block.So I changed the code as follows:

try
          {
              Console.WriteLine(“Entered Transaction Scope”);
              System.Threading.Thread.Sleep(60000);
              Console.WriteLine(“Start Commit”);
              scope.Complete();
              Console.WriteLine(“Transaction Complete.”);
          }
          catch
          {
              throw;
          }
          finally
          {
              scope.Dispose();
          }

Then I found out that this exception is thrown from scope.Dispose();

Advertisements

In my last post I had discussed about the basics of transactions,transaction manager & resource manager.In this post we will discuss about the Lightweight Transaction Manager(LTM) a transaction manager introduced with .NET 2.0.
Prior to LTM the only transaction manager available in the Microsoft world was the Micrsoft Distributed Transaction Coordinator(MSDTC).This transaction manager is able to handle transaction flowing across process/machine/network boundaries.But MSDTC has it’s own overheads and which is not required for simple transactional scenarios.That’s why the LTM was introduced.
LTM can manage transactions with
a) Any number of volatile resource managers
b) Single durable resource manager
c) No flows across process/machine
When anyone of the above mentioned conditions is violated the transaction gets promoted/escalated to the MSDTC or Kernel Transaction Manager(introduced with Windows Vista)
One of the great features of LTM is management of volatile or in-memory resource managers.Using the classes/interfaces present in System.Transaction namespace we can make Plain old .NET Objects transaction aware.

The first important class to take note of is the Transaction.It contains methods used for implementing resource managers for enlistment. It also provides functionalities for cloning a transaction and controlling the current transaction context. We can get current transaction, if one is set, using the static Current property.
      tx = Transaction.Current;
            if (tx != null)
            {
               ….
            }
As we have seen in the earlier post transaction manager communicates with the resource managers.Each resource manager needs to enlist with the transaction manager for a particular transaction.For this the Transaction class provides two methods EnlistVolatile-for volatile resource managers and EnlistDurable for durable resource managers.
For custom resource managers to enlist in the transaction they have to implement the IEnlistmentNotification interface.This interface defines the contract that a resource manager should implement to provide two phase commit notification callbacks for the transaction manager upon enlisting for participation.
This interface specifies the following methods:
      
 This callback is invoked during the second phase of a transaction if the transaction is commited.
        public void Commit(Enlistment enlistment)
        {
            ….
        }
 This callback is invoked during the second phase of a transaction if the transaction is in doubt.
        public void InDoubt(Enlistment enlistment)
        {
            …
.
        }
 This notification method is invoked in the first phase when the transaction manager asks participants whether they can commit the transaction.

        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            …
        }
 This callback is invoked during the second phase of a transaction if the transaction is aborted.
        public void Rollback(Enlistment enlistment)
        {
           …
        }

    }
Another important class in TransactionScope.This class is used for marking a code block transactional.On instanting the TransactionScope the transaction manager which transaction to participate.This is guided by the TransactionScopeOption parameter.The ambient transaction is the transaction in whichcode executes.A reference to the ambient transaction can be obtained by Current property of the Transaction class.
If no exception occurs within the transaction scope (that is, between the initialization of the TransactionScope object and the calling of its Dispose method), then the transaction in which the scope participates is allowed to proceed. If an exception does occur within the transaction scope, the transaction in which it participates will be rolled back.When code completes all tasks the Complete method has to be called to inform that transaction manager that it is acceptable to commit the transaction. If this method is not called then transaction is aborted.

The following classes shows the sample and simple implementation of a transaction aware Hashtable:

   class TransactionHashtable
    {
        private Hashtable ht = null;
        private Transaction tx;
        private bool enListed = false;
        public TransactionHashtable()
        {
            ht = new Hashtable();
        }
        public void Add(object key, object value)
        {
            tx = Transaction.Current;
            if (tx != null)
            {
                if (!enListed)
                {
                    tx.EnlistVolatile(new HashtableEnlistor(ref ht), EnlistmentOptions.None);
                    enListed = true;
                }
            }
          
            ht.Add(key, value);
        }
        public IDictionaryEnumerator GetEnumerator()
        {
            return ht.GetEnumerator();
        }
    }

    class HashtableEnlistor : IEnlistmentNotification
    {
        Hashtable ht = null;
        Hashtable htOld = null;
        public HashtableEnlistor(ref Hashtable ht)
        {
            this.ht = ht;
            MemoryStream ms = new MemoryStream();
           
            IFormatter f = new BinaryFormatter();
            f.Serialize(ms, ht);
            ms.Position = 0;
            htOld = (Hashtable)f.Deserialize(ms);
        }
        #region IEnlistmentNotification Members

        public void Commit(Enlistment enlistment)
        {
            enlistment.Done();
        }

        public void InDoubt(Enlistment enlistment)
        {
            throw new NotImplementedException();
        }

        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            preparingEnlistment.Prepared();
        }

        public void Rollback(Enlistment enlistment)
        {
            ht.Clear();
            IDictionaryEnumerator en = htOld.GetEnumerator();
            while (en.MoveNext())
            {
                ht.Add(en.Key,en.Value);
            }
            enlistment.Done();
        }

        #endregion
    }