Skip to content

Commit 3f75075

Browse files
committed
Fix "Aborted connection" error when connection leaks.
1 parent dd9971e commit 3f75075

File tree

3 files changed

+20
-17
lines changed

3 files changed

+20
-17
lines changed

src/MySqlConnector/MySqlClient/ConnectionPool.cs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace MySql.Data.MySqlClient
1010
{
1111
internal sealed class ConnectionPool
1212
{
13-
public async Task<MySqlSession> GetSessionAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
13+
public async Task<MySqlSession> GetSessionAsync(MySqlConnection connection, IOBehavior ioBehavior, CancellationToken cancellationToken)
1414
{
1515
cancellationToken.ThrowIfCancellationRequested();
1616

@@ -54,17 +54,19 @@ public async Task<MySqlSession> GetSessionAsync(IOBehavior ioBehavior, Cancellat
5454
}
5555

5656
// pooled session is ready to be used; return it
57+
session.OwningConnection = new WeakReference<MySqlConnection>(connection);
5758
lock (m_leasedSessions)
58-
m_leasedSessions.Add(session.Id, new WeakReference<MySqlSession>(session));
59+
m_leasedSessions.Add(session.Id, session);
5960
return session;
6061
}
6162
}
6263

6364
// create a new session
6465
session = new MySqlSession(this, m_generation, Interlocked.Increment(ref m_lastId));
6566
await session.ConnectAsync(m_connectionSettings, ioBehavior, cancellationToken).ConfigureAwait(false);
67+
session.OwningConnection = new WeakReference<MySqlConnection>(connection);
6668
lock (m_leasedSessions)
67-
m_leasedSessions.Add(session.Id, new WeakReference<MySqlSession>(session));
69+
m_leasedSessions.Add(session.Id, session);
6870
return session;
6971
}
7072
catch
@@ -95,6 +97,7 @@ public void Return(MySqlSession session)
9597
{
9698
lock (m_leasedSessions)
9799
m_leasedSessions.Remove(session.Id);
100+
session.OwningConnection = null;
98101
if (SessionIsHealthy(session))
99102
lock (m_sessions)
100103
m_sessions.AddFirst(session);
@@ -111,6 +114,7 @@ public async Task ClearAsync(IOBehavior ioBehavior, CancellationToken cancellati
111114
{
112115
// increment the generation of the connection pool
113116
Interlocked.Increment(ref m_generation);
117+
RecoverLeakedSessions();
114118
await CleanPoolAsync(ioBehavior, session => session.PoolGeneration != m_generation, false, cancellationToken).ConfigureAwait(false);
115119
}
116120

@@ -123,27 +127,25 @@ public async Task ReapAsync(IOBehavior ioBehavior, CancellationToken cancellatio
123127
}
124128

125129
/// <summary>
126-
/// Examines all the <see cref="WeakReference{MySqlSession}"/> in <see cref="m_leasedSessions"/> to determine if any
127-
/// <see cref="MySqlSession"/> objects have been garbage-collected. If so, assumes that the related <see cref="MySqlConnection"/>
128-
/// was not properly disposed but the associated server connection has been closed (by the finalizer). Releases the semaphore
129-
/// once for each leaked session to allow new client connections to be made.
130+
/// Examines all the <see cref="MySqlSession"/> objects in <see cref="m_leasedSessions"/> to determine if any
131+
/// have an owning <see cref="MySqlConnection"/> that has been garbage-collected. If so, assumes that the connection
132+
/// was not properly disposed and returns the session to the pool.
130133
/// </summary>
131134
private void RecoverLeakedSessions()
132135
{
133-
var recoveredIds = new List<int>();
136+
var recoveredSessions = new List<MySqlSession>();
134137
lock (m_leasedSessions)
135138
{
136139
m_lastRecoveryTime = unchecked((uint) Environment.TickCount);
137140
foreach (var pair in m_leasedSessions)
138141
{
139-
if (!pair.Value.TryGetTarget(out var _))
140-
recoveredIds.Add(pair.Key);
142+
var session = pair.Value;
143+
if (!session.OwningConnection.TryGetTarget(out var _))
144+
recoveredSessions.Add(session);
141145
}
142-
foreach (var id in recoveredIds)
143-
m_leasedSessions.Remove(id);
144146
}
145-
if (recoveredIds.Count > 0)
146-
m_sessionSemaphore.Release(recoveredIds.Count);
147+
foreach (var session in recoveredSessions)
148+
session.ReturnToPool();
147149
}
148150

149151
private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<MySqlSession, bool> shouldCleanFn, bool respectMinPoolSize, CancellationToken cancellationToken)
@@ -250,7 +252,7 @@ private ConnectionPool(ConnectionSettings cs)
250252
m_cleanSemaphore = new SemaphoreSlim(1);
251253
m_sessionSemaphore = new SemaphoreSlim(cs.MaximumPoolSize);
252254
m_sessions = new LinkedList<MySqlSession>();
253-
m_leasedSessions = new Dictionary<int, WeakReference<MySqlSession>>();
255+
m_leasedSessions = new Dictionary<int, MySqlSession>();
254256
}
255257

256258
static readonly ConcurrentDictionary<string, ConnectionPool> s_pools = new ConcurrentDictionary<string, ConnectionPool>();
@@ -280,7 +282,7 @@ private ConnectionPool(ConnectionSettings cs)
280282
readonly SemaphoreSlim m_sessionSemaphore;
281283
readonly LinkedList<MySqlSession> m_sessions;
282284
readonly ConnectionSettings m_connectionSettings;
283-
readonly Dictionary<int, WeakReference<MySqlSession>> m_leasedSessions;
285+
readonly Dictionary<int, MySqlSession> m_leasedSessions;
284286
int m_lastId;
285287
uint m_lastRecoveryTime;
286288
}

src/MySqlConnector/MySqlClient/MySqlConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ private async Task<MySqlSession> CreateSessionAsync(IOBehavior ioBehavior, Cance
311311
{
312312
var pool = ConnectionPool.GetPool(m_connectionSettings);
313313
// this returns an open session
314-
return await pool.GetSessionAsync(ioBehavior, linkedSource.Token).ConfigureAwait(false);
314+
return await pool.GetSessionAsync(this, ioBehavior, linkedSource.Token).ConfigureAwait(false);
315315
}
316316
else
317317
{

src/MySqlConnector/Serialization/MySqlSession.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public MySqlSession(ConnectionPool pool, int poolGeneration, int id)
4141
public DateTime LastReturnedUtc { get; private set; }
4242
public string DatabaseOverride { get; set; }
4343
public IPAddress IPAddress => (m_tcpClient?.Client.RemoteEndPoint as IPEndPoint)?.Address;
44+
public WeakReference<MySqlConnection> OwningConnection { get; set; }
4445

4546
public void ReturnToPool()
4647
{

0 commit comments

Comments
 (0)