Skip to content

Commit ae029d5

Browse files
committed
Set ConnectionState to Broken. Fixes #1599
When a socket/network error occurs, mark the connection as 'Broken', not 'Closed'. The user must explicitly Close the MySqlConnection (which will return it to the pool) before re-Opening it.
1 parent fd5aa32 commit ae029d5

File tree

5 files changed

+91
-9
lines changed

5 files changed

+91
-9
lines changed

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2010,7 +2010,7 @@ internal void SetFailed(Exception exception)
20102010
lock (m_lock)
20112011
m_state = State.Failed;
20122012
if (OwningConnection is not null && OwningConnection.TryGetTarget(out var connection))
2013-
connection.SetState(ConnectionState.Closed);
2013+
connection.SetState(ConnectionState.Broken);
20142014
}
20152015

20162016
private void VerifyState(State state)

src/MySqlConnector/MySqlConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ private async ValueTask<bool> PingAsync(IOBehavior ioBehavior, CancellationToken
510510
{
511511
}
512512

513-
SetState(ConnectionState.Closed);
513+
SetState(ConnectionState.Broken);
514514
return false;
515515
}
516516

tests/MySqlConnector.Tests/CancellationTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ public void Test(int step, int method)
329329
Assert.Null(ex.InnerException);
330330

331331
// connection is unusable
332-
Assert.Equal(ConnectionState.Closed, connection.State);
332+
Assert.Equal(ConnectionState.Broken, connection.State);
333333
}
334334
}
335335

@@ -351,7 +351,7 @@ public async Task Test(int step, int method)
351351
Assert.IsType<SocketException>(ex.InnerException);
352352

353353
// connection is unusable
354-
Assert.Equal(ConnectionState.Closed, connection.State);
354+
Assert.Equal(ConnectionState.Broken, connection.State);
355355
}
356356
}
357357

@@ -374,7 +374,7 @@ public void Execute(int step, int method)
374374
Assert.Null(ex.InnerException);
375375

376376
// connection is unusable
377-
Assert.Equal(ConnectionState.Closed, connection.State);
377+
Assert.Equal(ConnectionState.Broken, connection.State);
378378
}
379379

380380
[SkipCITheory]
@@ -394,7 +394,7 @@ public async Task ExecuteAsync(int step, int method)
394394
Assert.IsType<SocketException>(ex.InnerException);
395395

396396
// connection is unusable
397-
Assert.Equal(ConnectionState.Closed, connection.State);
397+
Assert.Equal(ConnectionState.Broken, connection.State);
398398
}
399399
}
400400

tests/MySqlConnector.Tests/ConnectionTests.cs

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,14 @@ public void Ping()
166166
}
167167

168168
[Fact]
169-
public void PingWhenClosed()
169+
public void PingWhenReset()
170170
{
171171
using var connection = new MySqlConnection(m_csb.ConnectionString);
172172
connection.Open();
173173
Assert.Equal(ConnectionState.Open, connection.State);
174-
m_server.Stop();
174+
m_server.Reset();
175175
Assert.False(connection.Ping());
176-
Assert.Equal(ConnectionState.Closed, connection.State);
176+
Assert.Equal(ConnectionState.Broken, connection.State);
177177
}
178178

179179
[Fact]
@@ -277,6 +277,72 @@ public void ReplaceActiveReader()
277277
connection.Close();
278278
}
279279

280+
[Fact]
281+
public async Task ResetServerConnectionWhileOpen()
282+
{
283+
var csb = new MySqlConnectionStringBuilder(m_csb.ConnectionString)
284+
{
285+
MaximumPoolSize = 5,
286+
ConnectionTimeout = 5,
287+
};
288+
289+
List<Task> tasks = [];
290+
using var barrier = new Barrier((int) csb.MaximumPoolSize);
291+
for (var i = 0; i < csb.MaximumPoolSize - 1; i++)
292+
{
293+
var threadId = i;
294+
tasks.Add(Task.Run(async () =>
295+
{
296+
using var connection = new MySqlConnection(csb.ConnectionString);
297+
await connection.OpenAsync().ConfigureAwait(false);
298+
299+
barrier.SignalAndWait();
300+
//// wait for reset
301+
barrier.SignalAndWait();
302+
303+
switch (threadId % 3)
304+
{
305+
case 0:
306+
{
307+
using (var command = connection.CreateCommand())
308+
{
309+
command.CommandText = "SELECT 1;";
310+
var exception = Assert.Throws<MySqlException>(() => command.ExecuteScalar());
311+
Assert.Equal("Failed to read the result set.", exception.Message);
312+
}
313+
break;
314+
}
315+
case 1:
316+
{
317+
// NOTE: duplicate of PingWhenReset, but included here for completeness
318+
var ping = await connection.PingAsync().ConfigureAwait(false);
319+
Assert.False(ping);
320+
break;
321+
}
322+
case 2:
323+
{
324+
await Assert.ThrowsAnyAsync<Exception>(async () => await connection.ResetConnectionAsync().ConfigureAwait(false));
325+
break;
326+
}
327+
}
328+
329+
Assert.Equal(ConnectionState.Broken, connection.State);
330+
331+
await connection.CloseAsync().ConfigureAwait(false);
332+
Assert.Equal(ConnectionState.Closed, connection.State);
333+
334+
await connection.OpenAsync().ConfigureAwait(false);
335+
Assert.Equal(ConnectionState.Open, connection.State);
336+
}));
337+
}
338+
339+
barrier.SignalAndWait();
340+
m_server.Reset();
341+
barrier.SignalAndWait();
342+
343+
await Task.WhenAll(tasks);
344+
}
345+
280346
private static async Task WaitForConditionAsync<T>(T expected, Func<T> getValue)
281347
{
282348
var sw = Stopwatch.StartNew();

tests/MySqlConnector.Tests/FakeMySqlServer.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ public void Start()
2121
m_tasks.Add(AcceptConnectionsAsync());
2222
}
2323

24+
public void Reset()
25+
{
26+
m_cts.Cancel();
27+
try
28+
{
29+
Task.WaitAll(m_tasks.Skip(1).ToArray());
30+
}
31+
catch (AggregateException)
32+
{
33+
}
34+
m_connections.Clear();
35+
m_tasks.Clear();
36+
m_cts.Dispose();
37+
m_cts = new();
38+
}
39+
2440
public void Stop()
2541
{
2642
if (m_cts is not null)

0 commit comments

Comments
 (0)