-
Notifications
You must be signed in to change notification settings - Fork 351
Description
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 finishExpected 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:
ServerSessioncallsSetFaileddue to the broken TCP connection, setting the state of the owningMySqlConnectiontoClosedwithout returning the failedServerSessionback to theConnectionPool.MySqlConnection.Open()does not check ifm_sessionis already set and simply overwrites it, causing theServerSessionto "leak", permanently blocking a slot in theConnectionPool.