Bug #102129 Hang in MySqlConnection.Close after TransactionScope times out
Submitted: 1 Jan 2021 23:00 Modified: 17 Jan 2022 19:21
Reporter: Bradley Grainger (OCA) Email Updates:
Status: Can't repeat Impact on me:
None 
Category:Connector / NET Severity:S2 (Serious)
Version:8.0.22 OS:Windows (10)
Assigned to: CPU Architecture:Other (x64)

[1 Jan 2021 23:00] Bradley Grainger
Description:
After a System.Transactions.Transaction (that a connection is enlisted in) times out, calling MySqlConnection.Close just hangs for DefaultCommandTimeout seconds. After the hang, the call to Close sometimes succeeds, but sometimes throws a NullReferenceException:

NullReferenceException: Object reference not set to an instance of an object. 
   at MySql.Data.MySqlClient.MySqlDataReader.Close()
   at MySql.Data.MySqlClient.MySqlConnection.Close()
   at UserQuery.Main(), line 24 

Meanwhile, the transaction throws an exception on a background thread:

MySqlException: Fatal error encountered during command execution.
   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.EnlistableStates.Timeout(InternalTransaction tx)
   at System.Transactions.Bucket.TimeoutTransactions()
   at System.Transactions.BucketSet.TimeoutTransactions()
   at System.Transactions.TransactionTable.ThreadTimer(Object state)
   at System.Threading.TimerQueueTimer.<>c.<.cctor>b__23_0(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.TimerQueueTimer.CallCallback(Boolean isThreadPool)
   at System.Threading.TimerQueueTimer.Fire(Boolean isThreadPool)
   at System.Threading.TimerQueue.FireNextTimers()
   at System.Threading.TimerQueue.AppDomainTimerCallback(Int32 id)

How to repeat:
Run the following C# code, which causes a System.Transactions transaction to time out before the SQL statement has finished executing:

var transactionScope = new TransactionScope(TransactionScopeOption.Required, TimeSpan.FromSeconds(1));
var connection = new MySqlConnection("...;AutoEnlist=true");
connection.Open();
Console.WriteLine("Executing command");
using (var command = new MySqlCommand("DO SLEEP(2);", connection))
	command.ExecuteNonQuery();
Console.WriteLine("Executed command");
connection.Close(); // delays for 30 seconds; then sometimes succeeds, sometimes throws NullReferenceException
connection.Dispose();

Console.WriteLine("Closed connection");
transactionScope.Complete();
[4 Jan 2021 6:30] MySQL Verification Team
Hello Bradley,

Thank you for the report and test case.
With the provided test case I'm getting below Unhandled Exception:

- C/NET 8.0.22, VS 2019, .NET framework 4.8

using MySql.Data.MySqlClient;
using System;
using System.Transactions;

namespace Bug102129
{
    class Program
    {
        static void Main(string[] args)
        {
            var transactionScope = new TransactionScope(TransactionScopeOption.Required, TimeSpan.FromSeconds(1));
            var connection = new MySqlConnection("server=xxx;port=3333;userid = ushastry; password = mysql123;AutoEnlist=true");

            connection.Open();

            Console.WriteLine("Executing command");
            using (var command = new MySqlCommand("DO SLEEP(2);", connection))
                command.ExecuteNonQuery();
            Console.WriteLine("Executed command");
            connection.Close(); // delays for 30 seconds; then sometimes succeeds, sometimes throws NullReferenceException
            connection.Dispose();

            Console.WriteLine("Closed connection");
            transactionScope.Complete();
        }
    }
}

Unhandled Exception: System.Transactions.TransactionAbortedException: The transaction has aborted.
   at MySql.Data.MySqlClient.Interceptors.ExceptionInterceptor.Throw(Exception exception)
   at MySql.Data.MySqlClient.MySqlConnection.Throw(Exception ex)
   at MySql.Data.MySqlClient.MySqlCommand.Throw(Exception ex)
   at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior)
   at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader()
   at MySql.Data.MySqlClient.Driver.LoadServerProperties(MySqlConnection connection)
   at MySql.Data.MySqlClient.Driver.Configure(MySqlConnection connection)
   at MySql.Data.MySqlClient.MySqlConnection.Open()
   at Bug102129.Program.Main(String[] args) in C:\Work\MySQLNet\Bug102129\Bug102129\Program.cs:line 14
Press any key to continue . . .

Am I missing anything here? Please let me know. Thank you!

regards,
Umesh
[13 Jan 2021 14:07] Bradley Grainger
I tested this again. I don't get the exception I reported when targeting "net48" (.NET Framework 4.8), but I do get it when targeting "net5.0" (.NET 5.0). So it appears this bug may be framework-specific.
[18 Jan 2021 8:24] MySQL Verification Team
Thank you for the feedback.
[17 Jan 2022 19:21] Daniel Valdez
Posted by developer:
 
Could not be reproduced using the latest release of Connector/NET, v8.0.27.