15

I'm looking for a description of the root of this error: "Transaction context in use by another session".

I get it sometimes in one of my unittests so I can't provider repro code. But I wonder what is "by design" reason for the error.

UPDATE: the error returns as SqlException from SQL Server 2008. A place where I get the error seems to be single-threaded. But probably I have unittests interaction as I get the error where run several tests at once (MSTest in VS2008sp1). But the failing test looks like:

  • create an object and save it inside DB-transaction (commit)
  • create TransactionScope
  • trying to open a connection - here I get SqlException with such stacktrace:

.

System.Data.SqlClient.SqlException: Transaction context in use by another session.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalConnectionTds.PropagateTransactionCookie(Byte[] cookie)
   at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
   at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
   at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
   at System.Data.SqlClient.SqlConnection.Open()

I've found these posts:

But I can't understand what "Multiple threads sharing the same transaction in a transaction scope will cause the following exception: 'Transaction context in use by another session.'" means. All words are understandable but not the point.

I actually can share a system transaction between threads. And there is even special mechanism for this - DependentTransaction class and Transaction.DependentClone method.

I'm trying to reproduce a usecase from the first post:

  1. Main thread creates DTC transaction, receives DependentTransaction (created using Transaction.Current.DependentClone on the main thread
  2. Child thread 1 enlists in this DTC transaction by creating a transaction scope based on the dependent transaction (passed via constructor)
  3. Child thread 1 opens a connection
  4. Child thread 2 enlists in DTC transaction by creating a transaction scope based on the dependent transaction (passed via constructor)
  5. Child thread 2 opens a connection

with such code:

using System;
using System.Threading;
using System.Transactions;
using System.Data;
using System.Data.SqlClient;

public class Program
{
    private static string ConnectionString = "Initial Catalog=DB;Data Source=.;User ID=user;PWD=pwd;";

    public static void Main()
    {
        int MAX = 100;
        for(int i =0; i< MAX;i++)
        {
            using(var ctx = new TransactionScope())
            {
                var tx = Transaction.Current;
                // make the transaction distributed
                using (SqlConnection con1 = new SqlConnection(ConnectionString))
                using (SqlConnection con2 = new SqlConnection(ConnectionString))
                {
                    con1.Open();
                    con2.Open();
                }
                showSysTranStatus();

                DependentTransaction dtx = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
                Thread t1 = new Thread(o => workCallback(dtx));
                Thread t2 = new Thread(o => workCallback(dtx));
                t1.Start();
                t2.Start();
                t1.Join();
                t2.Join();

                ctx.Complete();
            }
            trace("root transaction completes");
        }
    }
    private static void workCallback(DependentTransaction dtx)
    {
        using(var txScope1 = new TransactionScope(dtx))
        {
            using (SqlConnection con2 = new SqlConnection(ConnectionString))
            {
                con2.Open();
                trace("connection opened");
                showDbTranStatus(con2);
            }
            txScope1.Complete();
        }   
        trace("dependant tran completes");
    }
    private static void trace(string msg)
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " : " + msg);
    }
    private static void showSysTranStatus()
    {
        string msg;
        if (Transaction.Current != null)
            msg = Transaction.Current.TransactionInformation.DistributedIdentifier.ToString();
        else
            msg = "no sys tran";
        trace( msg );
    }

    private static void showDbTranStatus(SqlConnection con)
    {
        var cmd = con.CreateCommand();
        cmd.CommandText = "SELECT 1";
        var c = cmd.ExecuteScalar();
        trace("@@TRANCOUNT = " + c);
    }
}

It fails on Complete's call of root TransactionScope. But error is different: Unhandled Exception: System.Transactions.TransactionInDoubtException: The transaction is in doubt. ---> pired. The timeout period elapsed prior to completion of the operation or the server is not responding.

To sum up: I want to understand what "Transaction context in use by another session" means and how to reproduce it.

Esteban Küber
  • 36,388
  • 15
  • 79
  • 97
Shrike
  • 9,218
  • 7
  • 68
  • 105
  • A small point: are you sure you got a distributed transaction? You make 2 open connection: con1.Open(); con2.Open(); But the connestion string is the same and you're using Sql2008. AFAIK if you use Sql2008 AND the same connection string, the transaction does not escalate to distributed. It remains "local". My 2 cents. – Fabrizio Accatino May 21 '10 at 08:00
  • No, opening two simultaneous connections always leads to a distributed transaction. It doesn't matter they have the same connection strings or not – Shrike May 21 '10 at 10:12
  • @Fabrizio, but you're right in case if the same connection instance opens and closes serially then in sql2008 we get local tran but in sql2005 distibuted tran. – Shrike May 21 '10 at 19:11

6 Answers6

7

It's a bit late for answer :) but hope it will be useful for others. Answer contains three parts:

  1. What does it mean "Transaction context in use by another session."
  2. How to reproduce error "Transaction context in use by another session."

1. What does it mean "Transaction context in use by another session."

Important notice: Transaction context lock is acquired just before and released immediately after interaction between SqlConnection and SQL Server.

When you execute some SQL Query, SqlConnection "looks" is there any transaction wrapping it. It may be SqlTransaction ("native" for SqlConnection) or Transaction from System.Transactions assembly.

When transaction found SqlConnection uses it to communicate with SQL Server and at the moment they communicate Transaction context is exclusively locked.

What does TransactionScope? It creates Transaction and provides .NET Framework Components infromation about it, so everyone including SqlConnection can (and by design should) use it.

So declaring TransactionScope we're creating new Transaction which is available to all "transactable" objects instantiated in current Thread.

Described error means the following:

  1. We created several SqlConnections under the same TransactionContext (which means they related to the same transaction)
  2. We asked these SqlConnection to communicate with SQL Server simultaneously
  3. One of them locked current Transaction context and next one throwed error

2. How to reproduce error "Transaction context in use by another session."

First of all, transaction context is used ("locked") right at the time of sql command execution. So it's difficult to reproduce such a behavior for sure.

But we can try to do it by starting multiple threads running relatively long SQL operations under the single transaction. Let's prepare table [dbo].[Persons] in [tests] Database:

USE [tests]
GO
DROP TABLE [dbo].[Persons]
GO
CREATE TABLE [dbo].[Persons](
    [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY,
    [Name] [nvarchar](1024) NOT NULL,
    [Nick] [nvarchar](1024) NOT NULL,
    [Email] [nvarchar](1024) NOT NULL)
GO
DECLARE @Counter INT
SET @Counter = 500

WHILE (@Counter > 0) BEGIN
    INSERT [dbo].[Persons] ([Name], [Nick], [Email])
    VALUES ('Sheev Palpatine', 'DarthSidious', 'spalpatine@galaxyempire.gov')
    SET @Counter = @Counter - 1
END
GO

And reproduce "Transaction context in use by another session." error with C# code based on Shrike code example

using System;
using System.Collections.Generic;
using System.Threading;
using System.Transactions;
using System.Data.SqlClient;

namespace SO.SQL.Transactions
{
    public static class TxContextInUseRepro
    {
        const int Iterations = 100;
        const int ThreadCount = 10;
        const int MaxThreadSleep = 50;
        const string ConnectionString = "Initial Catalog=tests;Data Source=.;" +
                                        "User ID=testUser;PWD=Qwerty12;";
        static readonly Random Rnd = new Random();
        public static void Main()
        {
            var txOptions = new TransactionOptions();
            txOptions.IsolationLevel = IsolationLevel.ReadCommitted;
            using (var ctx = new TransactionScope(
                TransactionScopeOption.Required, txOptions))
            {
                var current = Transaction.Current;
                DependentTransaction dtx = current.DependentClone(
                    DependentCloneOption.BlockCommitUntilComplete);               
                for (int i = 0; i < Iterations; i++)
                {
                    // make the transaction distributed
                    using (SqlConnection con1 = new SqlConnection(ConnectionString))
                    using (SqlConnection con2 = new SqlConnection(ConnectionString))
                    {
                        con1.Open();
                        con2.Open();
                    }

                    var threads = new List<Thread>();
                    for (int j = 0; j < ThreadCount; j++)
                    {
                        Thread t1 = new Thread(o => WorkCallback(dtx));
                        threads.Add(t1);
                        t1.Start();
                    }

                    for (int j = 0; j < ThreadCount; j++)
                        threads[j].Join();
                }
                dtx.Complete();
                ctx.Complete();
            }
        }

        private static void WorkCallback(DependentTransaction dtx)
        {
            using (var txScope1 = new TransactionScope(dtx))
            {
                using (SqlConnection con2 = new SqlConnection(ConnectionString))
                {
                    Thread.Sleep(Rnd.Next(MaxThreadSleep));
                    con2.Open();
                    using (var cmd = new SqlCommand("SELECT * FROM [dbo].[Persons]", con2))
                    using (cmd.ExecuteReader()) { } // simply recieve data
                }
                txScope1.Complete();
            }
        }
    }
}

And in conclusion a few words about implementing transaction support in your application:

  • Avoid multi-threaded data operations if it's possible (no matter loading or saving). E.g. save SELECT/UPDATE/ etc... requests in a single queue and serve them with a single-thread worker;
  • In multi-threaded applications use transactions. Always. Everywhere. Even for reading;
  • Don't share single transaction between multiple threads. It causes strange, unobvious, transcendental and not reproducible error messages:
    • "Transaction context in use by another session.": multiple simultaneous interactions with server under one transaction;
    • "Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.": not dependent transactions were completed;
    • "The transaction is in doubt.";
    • ... and I assume a lot of other ...
  • Don't forget to set Isolation Level for TransactionScope. Default is Serializable but in most cases ReadCommitted is enough;
  • Don't forget to Complete() TransactionScope and DependentTransaction
2

"Multiple threads sharing the same transaction in a transaction scope will cause the following exception: 'Transaction context in use by another session.'"

Sounds pretty straightforward. If you enlist two different connections in the same transaction, then try to issue commands on each of the two connections, simultaneously, from different threads, a conflict could occur.

In other words, one thread is issuing a command on one connection and holds some kind of lock on the transaction context. The other thread, using the other connection, tries to execute commands at the same time, and cannot lock the same transaction context, which is being used by the other thread.

Triynko
  • 18,766
  • 21
  • 107
  • 173
  • The phase is straightforward, but not the point. I showed an example with multithreaded using of System.Transactions. And there wasn't such error (but there's another one). Moreover take a look at this post of a guy from SysTrans team (I believe): http://www.pluralsight-training.net/community/blogs/jimjohn/archive/2005/05/01/7923.aspx – Shrike May 22 '10 at 19:15
  • And if SysTrans doesn't support multithreaded environment why do we need DependantTransaction type. – Shrike May 22 '10 at 19:29
1

Take a step back and focus more on your code and less in the multiple threads info floating around.

If your scenario doesn't involve threading, it might relate to pieces that aren't closed as you expect it to.

Maybe the sql code you are calling doesn't reach that commit transaction instruction. Or there is something else involved at that level. Maybe you used a SqlConnection instance setting the transaction in the .net code, and are reusing that same instance on the other code that uses the TransactionScope. Try adding using() instructions where appropriate, to make sure everything is closed as you expect it.

eglasius
  • 35,831
  • 5
  • 65
  • 110
1

You must create a DependentTransaction for each thread an then inside the thread create & open the db connection inside a TransacctionScope using the dependentTransaction in the ctor.

            //client code / main thread
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, timeout))
            {
                Transaction currentTransaction = Transaction.Current;
                currentTransaction.TransactionCompleted += OnCompleted;
                DependentTransaction dependentTransaction;
                int nWorkers = Config.Instance.NumDBComponentWorkers;
                for (int i = 0; i < nWorkers; i++)
                {
                    dependentTransaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
                    this.startWorker(dependentTransaction);
                }
                do
                {
                    //loop + wait
                    Thread.Sleep(150);
                } while (this.status == DBComponentStatus.Running);
                //No errors-commit transaction
                if (this.status == DBComponentStatus.Finished && this.onCanCommit())
                {
                    scope.Complete();
                }
            }

    //workers
    protected override void startWorker(DependentTransaction dependentTransaction)
    {
        Thread thread = new Thread(workerMethod);
        thread.Start(dependentTransaction);
    }

    protected override void workerMethod(object transaction)
    {
        int executedStatements = 0;
        DependentTransaction dependentTransaction;
        dependentTransaction = transaction as DependentTransaction;
        System.Diagnostics.Debug.Assert(dependentTransaction != null); //testing
        try
        {
            //Transaction.Current = dependentTransaction;
            using (TransactionScope scope = new TransactionScope(dependentTransaction))
            {
                using (SqlConnection conn = new SqlConnection(this.GetConnectionString(this.parameters)))
                {
                    /* Perform transactional work here */
                    conn.Open();
                    string statement = string.Empty;
                    using (SqlCommand cmd = conn.CreateCommand())
                    {

                    }
                }
                //No errors-commit transaction
                if (this.status == DBComponentStatus.Finished)
                {
                    scope.Complete();
                }
            }
        }
        catch (Exception e)
        {
            this.status = DBComponentStatus.Aborted;
        }
        finally
        {
            dependentTransaction.Complete();
            dependentTransaction.Dispose();
        }
    }
Himanshu
  • 31,810
  • 31
  • 111
  • 133
Tochas
  • 11
  • 1
0

How I'd deal with that issue when building Linq statements with mutlipe objects is to have a constructor for each class that takes in a data context and a coresponding GetDataContext() method in each class. when combining classes, I'd new up the class instances passing in the first class's GetContext()

  public class CriterionRepository : ICriterionRepository
    {

        private Survey.Core.Repository.SqlDataContext _context = new Survey.Core.Repository.SqlDataContext();

        public CriterionRepository() { }

        public CriterionRepository(Survey.Core.Repository.SqlDataContext context)
        {            
            _context = context;
        }

...


        public Survey.Core.Repository.SqlDataContext GetDataContext()
        {
            return _context;
        }

}
James Fleming
  • 2,589
  • 2
  • 25
  • 41
0

I have a multi-threaded application that does some data manipulation and stores the results in the database. Because different threads are working on different types of data, writing code to collect the results and flush it out to the database in one thread is more cumbersome than just having each thread write the results out itself when it is done.

I wanted to run this in a transaction, so that I have the option to revert all of the work in the event that an error happens in any one of the child threads. Adding transactions started causing problems, which led me to this posting, but I was able to work through them. Multi-threaded database access in a single transaction is possible. I'm even using both LINQ-to-SQL and SqlBulkCopy together in the same transaction.

I found Ilya Chidyakin's answer to be very helpful. You need to pass a DependentTransaction to each thread, and use that to create a new TransactionScope. And, you need to remember to commit both the TransactionScope and the DependentTransaction in each thread. Finally, you must wait to commit your "original" transaction until all of the child work is done. (DependentTransaction should take care of this, actually, but I was already using Thread.Join to wait for all of the work to be done, before I added transactions to this project.)

The key thing is, only one thread can be accessing the database at any given time. I just used a semaphore to block access to the database to one thread at a time. Since my threads spend most of the time computing and only a little bit of time writing to the database, I didn't really incur a performance penalty because of this... However, if your threads are using the database frequently, this requirement may essentially remove the performance benefit of multi-threading, if you want everything contained in one transaction.

If you have multiple threads accessing the database at once, you will get an Exception with the message "Transaction context in use by another session." If you forget to commit all of the transactions in each thread, you will get an Exception with the message "The transaction is in doubt" when you try to commit the outer-most transaction.

Truisms Hounds
  • 422
  • 4
  • 9