@@ -246,22 +246,35 @@ public void Dispose()
246
246
/// </summary>
247
247
private async Task RecoverLeakedSessionsAsync ( IOBehavior ioBehavior )
248
248
{
249
- var recoveredSessions = new List < ServerSession > ( ) ;
249
+ var recoveredSessions = new List < ( ServerSession Session , MySqlConnection Connection ) > ( ) ;
250
250
lock ( m_leasedSessions )
251
251
{
252
252
m_lastRecoveryTime = unchecked ( ( uint ) Environment . TickCount ) ;
253
253
foreach ( var session in m_leasedSessions . Values )
254
254
{
255
255
if ( ! session . OwningConnection ! . TryGetTarget ( out var _ ) )
256
- recoveredSessions . Add ( session ) ;
256
+ {
257
+ // create a dummy MySqlConnection so that any thread running RecoverLeakedSessionsAsync doesn't process this one
258
+ var connection = new MySqlConnection ( ) ;
259
+ session . OwningConnection = new ( connection ) ;
260
+ recoveredSessions . Add ( ( session , connection ) ) ;
261
+ }
257
262
}
258
263
}
259
264
if ( recoveredSessions . Count == 0 )
260
265
Log . Trace ( "Pool{0} recovered no sessions" , m_logArguments ) ;
261
266
else
262
267
Log . Warn ( "Pool{0}: RecoveredSessionCount={1}" , m_logArguments [ 0 ] , recoveredSessions . Count ) ;
263
- foreach ( var session in recoveredSessions )
268
+
269
+ foreach ( var ( session , connection ) in recoveredSessions )
270
+ {
271
+ // bypass MySqlConnection.Dispose(Async), because it's a dummy MySqlConnection that's not set up
272
+ // properly, and simply return the session to the pool directly
264
273
await session . ReturnToPoolAsync ( ioBehavior , null ) . ConfigureAwait ( false ) ;
274
+
275
+ // be explicit about keeping the associated MySqlConnection alive until the session has been returned
276
+ GC . KeepAlive ( connection ) ;
277
+ }
265
278
}
266
279
267
280
private async Task CleanPoolAsync ( IOBehavior ioBehavior , Func < ServerSession , bool > shouldCleanFn , bool respectMinPoolSize , CancellationToken cancellationToken )
0 commit comments