Concurrency issue found in 3.1

Topics: Data Access Application Block
Oct 21, 2008 at 4:18 PM

We are experience an issue with the 3.1 EnterpriseLibrary when it is used under a heavy load, 30 thread process.  The error is;

 

System.ArgumentException: An item with the same key has already been added.

   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)

   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)

   at Microsoft.Practices.EnterpriseLibrary.Data.TransactionScopeConnections.GetConnection(Database db)

   at Microsoft.Practices.EnterpriseLibrary.Data.Database.LoadDataSet(DbCommand command, DataSet dataSet, String[] tableNames)

   at Microsoft.Practices.EnterpriseLibrary.Data.Database.LoadDataSet(DbCommand command, DataSet dataSet, String tableName)

   at Microsoft.Practices.EnterpriseLibrary.Data.Database.ExecuteDataSet(DbCommand command)

Here's a code snip of one of the calls producing the error;

 

      // create the database connection

     SqlDatabase db = new SqlDatabase(ConnectString);

     // create sql command object

     using (SqlCommand cmd = new SqlCommand("dbo.Insert..."))

     {

        cmd.CommandType = CommandType.StoredProcedure;

        // discover the parameters

        //db.DiscoverParameters(cmd);

        // set command parameters

       ...

        // execute

        db.ExecuteNonQuery(cmd);

     }

 

The error is being thrown from ' db.ExecuteNonQuery(cmd);'

 

I believe the source of the issue is within; Microsoft.Practices.EnterpriseLibrary.Data.TransactionScopeConnections,

GetConnection method;

 

     public static DbConnection GetConnection(Database db)

    {

       Transaction currentTransaction = Transaction.Current;

       if (currentTransaction == null)

              return null;

       Dictionary<string, DbConnection> connectionList;

       transactionConnections.TryGetValue(currentTransaction, out connectionList);

       DbConnection connection;

       if (connectionList != null)

       {

              connectionList.TryGetValue(db.ConnectionString.ToString(), out connection);

              if (connection != null)

                     return connection;

       }

       else

       {       // We don't have a list for this transaction, so create a new one

              connectionList = new Dictionary<string, DbConnection>();

              lock (transactionConnections)

                     transactionConnections.Add(currentTransaction, connectionList);

       } 

 

The lock statement 'lock (transactionConnections)' is more than likely the issue in a multithreaded call. 

 

Oct 22, 2008 at 8:17 PM

Hi,

I've been looking at this and while there are concurrency issues with this code I can't see how you'd get the specific error you're getting. How are you setting up the TransactionScopes? Are you somehow sharing transaction across threads?

Regards,

Fernando

Oct 23, 2008 at 2:18 AM


The transaction scope option is RequiresNew.  The transaction is created right after the thread is started, so I don't believe it is being shared across threads.
Oct 23, 2008 at 12:45 PM
Thanks.

It looks like a misleading error message triggered by leaving the dictionary in an inconsistent state due to a race condition.

I'm logging an issue for this.

Fernando
Oct 29, 2008 at 2:24 PM
Hi,

This is fixed in v4.1.

Regards,
Fernando
Jan 11, 2011 at 12:18 PM

Hi,

I have a simple question. How is it possible that the Enterprise Library / Data Block is thread-safe? Inside the GetConnection method, it's using a STATIC field (Transaction.Current) to track if a connection is part of a transaction. The moment transaction "A" starts a transaction, and tries to get a connection, transaction "B" could have ended and cleaned out the value of Transaction.Current "A" thus leaving transaction "A" thinking there is no current transaction in its scope?

public static DbConnection GetConnection(Database db)

{

 Transaction currentTransaction = Transaction.Current; <------ STATIC FIELD???

...

}

Jan 11, 2011 at 2:02 PM

Jsfunfun,


Transaction.Current is not a static field, it is a static property. The getter method of this property contains a lot of logic to allow fetching a transaction based on the current context. Below the covers the transaction is stored in the thread local storage (TLS) of a specific thread. You can see for yourself using .NET Reflector.

Cheers