Description:
The test case from bug #107110 does not appear to be completely fixed, but throws a different exception now:
System.InvalidOperationException : Connection must be valid and open to rollback transaction
Stack Trace:
at MySql.Data.MySqlClient.MySqlTransaction.Rollback()
at MySql.Data.MySqlClient.MySqlConnection.CloseFully()
at MySql.Data.MySqlClient.MySqlTransactionScope.Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
at MySql.Data.MySqlClient.MySqlPromotableTransaction.System.Transactions.IPromotableSinglePhaseNotification.Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
at System.Transactions.DurableEnlistmentAborting.EnterState(InternalEnlistment enlistment)
at System.Transactions.DurableEnlistmentActive.InternalAborted(InternalEnlistment enlistment)
at System.Transactions.TransactionStateAborted.EnterState(InternalTransaction tx)
at System.Transactions.TransactionStateActive.Rollback(InternalTransaction tx, Exception e)
at System.Transactions.Transaction.Rollback()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
This exception is new in 8.0.31. In 8.0.30, it threw:
System.NullReferenceException : Object reference not set to an instance of an object.
Stack Trace:
at MySql.Data.MySqlClient.NativeDriver.ExecutePacket(MySqlPacket packetToExecute)
at MySql.Data.MySqlClient.NativeDriver.SendQuery(MySqlPacket queryPacket, Int32 paramsPosition)
at MySql.Data.MySqlClient.Driver.SendQuery(MySqlPacket p, Int32 paramsPosition)
at MySql.Data.MySqlClient.Statement.ExecuteNext()
at MySql.Data.MySqlClient.PreparableStatement.ExecuteNext()
at MySql.Data.MySqlClient.PreparableStatement.Execute()
at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior)
at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader()
at MySql.Data.MySqlClient.MySqlCommand.ExecuteNonQuery()
at MySql.Data.MySqlClient.MySqlTransaction.Rollback()
at MySql.Data.MySqlClient.MySqlTransactionScope.Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
at MySql.Data.MySqlClient.MySqlPromotableTransaction.System.Transactions.IPromotableSinglePhaseNotification.Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
at System.Transactions.DurableEnlistmentAborting.EnterState(InternalEnlistment enlistment)
at System.Transactions.DurableEnlistmentActive.InternalAborted(InternalEnlistment enlistment)
at System.Transactions.TransactionStateAborted.EnterState(InternalTransaction tx)
at System.Transactions.TransactionStateActive.Rollback(InternalTransaction tx, Exception e)
at System.Transactions.Transaction.Rollback()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
There appears to be a test case that verifies that this method doesn't throw:
https://github.com/mysql/mysql-connector-net/blob/8.0.31/MySQL.Data/tests/MySql.Data.Tests....
I'm not sure how that test is passing, because this sample code is failing consistently for me (with mysql:8.0.31 running in a Docker container).
How to repeat:
Run the following C# code:
public void Test()
{
using var scope = new TransactionScope();
using var connection = new MySqlConnection("...");
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = "SELECT * from INFORMATION_SCHEMA.TABLES LIMIT 1; SELECT SLEEP(5);";
command.CommandTimeout = 1;
command.ExecuteNonQuery();
}
Suggested fix:
It should be possible to roll back a TransactionScope that outlives a MySqlConnection. Disposing the MySqlConnection wouldn't actually close the connection, but store it with the active Transaction to be committed or rolled back later.