Я ищу описание корня этой ошибки: "Контекст транзакции используется другим сеансом".
Я иногда получаю это в одном из моих unittests, поэтому я не могу воспроизвести код. Но мне интересно, что такое "по дизайну" причина ошибки.
UPDATE: ошибка возвращается как SqlException из SQL Server 2008. Место, где я получаю ошибку, кажется однопоточным. Но, вероятно, у меня есть взаимодействие unittests, поскольку я получаю ошибку, когда запускают сразу несколько тестов (MSTest в VS2008sp1). Но неудачный тест выглядит следующим образом:
- создать объект и сохранить его внутри транзакции DB (commit)
- создать TransactionScope
- пытается открыть соединение - здесь я получаю исключение SqlException с такой 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()
Я нашел эти сообщения:
- http://blogs.msdn.com/asiatech/archive/2009/08/10/system-transaction-may-fail-in-multiple-thread-environment.aspx
- http://msdn.microsoft.com/en-us/library/ff649002.aspx
Но я не могу понять, что "Несколько потоков, разделяющих одну и ту же транзакцию в области транзакций, вызовут следующее исключение:" контекст транзакции, используемый другим сеансом ".). Все слова понятны, но не суть.
Фактически я могу совместно использовать системную транзакцию между потоками. И для этого существует даже специальный механизм - DependentTransaction class и метод Transaction.DependentClone.
Я пытаюсь воспроизвести usecase с первого поста:
- Основной поток создает транзакцию DTC, получает DependentTransaction (созданный с помощью Transaction.Current.DependentClone в основном потоке
- Детский поток 1 завершает транзакцию DTC путем создания области транзакции на основе зависимой транзакции (переданной через конструктор)
- Детский поток 1 открывает соединение
- Детский поток 2 завершает транзакцию DTC, создавая область транзакции на основе зависимой транзакции (переданной через конструктор)
- Детский поток 2 открывает соединение
с таким кодом:
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);
}
}
Не удается выполнить полный вызов root TransactionScope. Но ошибка отличается: Необработанное исключение: System.Transactions.TransactionInDoubtException: транзакция вызывает сомнения. --- > pired. Период ожидания истекает до завершения операции или сервер не отвечает.
Подводя итог: хочу понять, что означает "контекст транзакции, используемый другим сеансом", и как его воспроизводить.