I'm hunting down a bug in a large business application, where business processes are failing but partially persisted to the database. To make things harder to figure out, the process fails only once every few weeks, with hundreds of thousands successfully processed between every failure. The error frequency seems to go up when concurrency/number of worker processes goes up.
So far, we've been able to recreate what we're seeing with this program. (.NET 4.7.1 framework)
using (var transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (var sqlConnection = new SqlConnection("Server=localhost;Database=Database1;Trusted_Connection=True"))
{
sqlConnection.Open();
// Doing some unwanted nested manual transaction and rolling it back
var transaction = sqlConnection.BeginTransaction();
new SqlCommand($"INSERT INTO TestTable VALUES('This will be rolled back}')", sqlConnection).ExecuteNonQuery();
transaction.Rollback();
// Now doing some work with the DB.
// You might expect it to become a part of transactionScope, but that is NOT the case!
// So this command will actually succeed, with data written to the DB.
new SqlCommand($"INSERT INTO TestTable VALUES('This will be inserted')", sqlConnection).ExecuteNonQuery();
// Doing something which promotes the local transaction to a distributed transaction.
using (var sqlConnection2 = new SqlConnection("Server=localhost;Database=Database2;Trusted_Connection=True"))
{
// The program will fail here, with message "The transaction has aborted, Failure while attempting to promote transaction."
sqlConnection2.Open();
new SqlCommand($"INSERT INTO TestTable2 VALUES('We will never come this far')", sqlConnection2).ExecuteNonQuery();
}
}
transactionScope.Complete();
}
Our production code does not explicitly make calls to transaction.Rollback()
, it is simply in my example as the means to reproduce the error message and behavior. But if any of our third party libraries makes this call, I would like to throw exception and exit as soon as possible. Preferably in the application layer.
How can I detect that the call to Rollback()
has been made? I really do not want to make crud operations without being sure that the transactionScope
is able to do it's job.
My unwanted "rollback" was caused by a bug somewhere in the connection sharing mechanism of .Net. The bug is reproduced on all .Net Framework version between 4.5.1 and 4.8, and also on the new System.Data.SqlClient package.
An issue has been added to the System.Data.SqlClient repository.
Annotation Type Rollback@Rollback is a test annotation that is used to indicate whether a test-managed transaction should be rolled back after the test method has completed. Consult the class-level Javadoc for TransactionalTestExecutionListener for an explanation of test-managed transactions.
In SQL server, you can klii an active process using the command KILL <SPID> causing it to roll back any in flight transactions. And if you have killed a process, you can check the progress of the rollback by running KILL <SPID> WITH STATUSONLY.
You can use ROLLBACK TRANSACTION to erase all data modifications made from the start of the transaction or to a savepoint. It also frees resources held by the transaction. This does not include changes made to local variables or table variables. These are not erased by this statement.
Transactions in the SQL server are rollbacked automatically. However, with the rollback SQL statement, you can manually rollback a transaction based on certain conditions. In this article, you will see what a transaction is and how it can be rollbacked both manually and automatically.
The different transaction APIs don't all work together. So you're in dangerous territory here.
How can I detect that the call to Rollback() has been made?
select @@trancount
should always tell you. The rollback will revert @@trancount to 0.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With