Skip to content

Failed ServerSession is not returned to ConnectionPool, eventually causing ConnectTimeout #1599

@geieredgar

Description

@geieredgar

Software versions
MySqlConnector version: 2.4.0
Server type (MySQL, MariaDB, Aurora, etc.) and version: MariaDB 10.4
.NET version: net8.0
(Optional) ORM NuGet packages and versions:

Describe the bug
When the tcp connection is broken and MySqlConnection.Open() is called to reconnect to the database, the failed ServerSession is not returned to the ConnectionPool, which eventually leads to a ConnectTimeout.

Exception

MySqlConnector.MySqlException (0x80004005): Connect Timeout expired. All pooled connections are in use.
   at MySqlConnector.MySqlConnection.CreateSessionAsync(ConnectionPool pool, Int64 startingTimestamp, Activity activity, Nullable`1 ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlConnection.cs:line 1109
   at MySqlConnector.MySqlConnection.OpenAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlConnection.cs:line 567
   at MySqlConnector.MySqlConnection.Open() in /_/src/MySqlConnector/MySqlConnection.cs:line 522
   at Program.<>c__DisplayClass0_1.<<Main>$>b__1() in Program.cs:line 34

Code sample

using MySqlConnector;

const string connString = "server=localhost;port=3306;user=root;password=pass;Character Set=utf8mb4;";
const string sql = @"SELECT 1;";

using var barrier = new Barrier(52);

for (var i = 0; i < 51; i++)
{
    new Thread(() =>
    {
        using (var conn = new MySqlConnection(connString))
        {
            conn.Open();
            barrier.SignalAndWait(); //Signal main thread that connection is open
            barrier.SignalAndWait(); //Wait for main thread
            try
            {
                using var cmd = new MySqlCommand(sql, conn);
                cmd.ExecuteScalar();
            }
            catch (Exception)
            {
                //Reconnect
                if (conn.State == System.Data.ConnectionState.Closed)
                {
                    try
                    {
                        conn.Open();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex);
                    }
                }
                else
                {
                    //The bug is only reproducible if Open() is called, which requires the Closed state.
                    Console.WriteLine($"Connection is in state {conn.State}, expected Closed");
                }

            }
            barrier.SignalAndWait(); //Signal main thread and wait for other threads
        }
    }).Start();
}

barrier.SignalAndWait(); //Wait for other threads to open connections
Console.Write("Reconnect database, then press Enter to continue...");
Console.ReadLine();
barrier.SignalAndWait(); //Signal other threads
barrier.SignalAndWait(); //Wait for other threads to finish

Expected behavior
The sample program exits without an exception.

Additional context
Restarting the database via docker restart is not enough, because that leaves the MySqlConnection in the Open state. To reproduce the bug, I had to use docker network disconnect followed by docker network connect.

My assumption of what happens here:

  • ServerSession calls SetFailed due to the broken TCP connection, setting the state of the owning MySqlConnection to Closed without returning the failed ServerSession back to the ConnectionPool.
  • MySqlConnection.Open() does not check if m_session is already set and simply overwrites it, causing the ServerSession to "leak", permanently blocking a slot in the ConnectionPool.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions