Skip to content

Commit 48c6754

Browse files
committed
Rewrite server redirection loop.
This fixes a resource leak from the original session, at the cost of some potentially-unnecessary server initialisation happening on the first session. The retry logic is only implemented for pooled connections.
1 parent 1a70a1e commit 48c6754

File tree

3 files changed

+46
-42
lines changed

3 files changed

+46
-42
lines changed

src/MySqlConnector/Core/ConnectionPool.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,7 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
104104
}
105105

106106
// create a new session
107-
session = new(this, m_generation, Interlocked.Increment(ref m_lastSessionId));
108-
if (Log.IsInfoEnabled())
109-
Log.Info("Pool{0} no pooled session available; created new Session{1}", m_logArguments[0], session.Id);
110-
await session.ConnectAsync(ConnectionSettings, startTickCount, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false);
107+
session = await ConnectSessionAsync("Pool{0} no pooled session available; created new Session{1}", startTickCount, ioBehavior, cancellationToken).ConfigureAwait(false);
111108
AdjustHostConnectionCount(session, 1);
112109
session.OwningConnection = new(connection);
113110
int leasedSessionsCountNew;
@@ -350,9 +347,7 @@ private async Task CreateMinimumPooledSessions(IOBehavior ioBehavior, Cancellati
350347

351348
try
352349
{
353-
var session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId));
354-
Log.Info("Pool{0} created Session{1} to reach minimum pool size", m_logArguments[0], session.Id);
355-
await session.ConnectAsync(ConnectionSettings, Environment.TickCount, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false);
350+
var session = await ConnectSessionAsync("Pool{0} created Session{1} to reach minimum pool size", Environment.TickCount, ioBehavior, cancellationToken).ConfigureAwait(false);
356351
AdjustHostConnectionCount(session, 1);
357352
lock (m_sessions)
358353
m_sessions.AddFirst(session);
@@ -365,6 +360,42 @@ private async Task CreateMinimumPooledSessions(IOBehavior ioBehavior, Cancellati
365360
}
366361
}
367362

363+
private async ValueTask<ServerSession> ConnectSessionAsync(string logMessage, int startTickCount, IOBehavior ioBehavior, CancellationToken cancellationToken)
364+
{
365+
var session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId));
366+
if (Log.IsInfoEnabled())
367+
Log.Info(logMessage, m_logArguments[0], session.Id);
368+
var statusInfo = await session.ConnectAsync(ConnectionSettings, startTickCount, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false);
369+
370+
if (statusInfo is not null && statusInfo.StartsWith("Location: mysql://", StringComparison.Ordinal))
371+
{
372+
// server redirection string has the format "Location: mysql://{host}:{port}/user={userId}[&ttl={ttl}]"
373+
Log.Info("Session{0} has server redirection header {1}", session.Id, statusInfo);
374+
375+
if (Utility.TryParseRedirectionHeader(statusInfo, out var host, out var port, out var user))
376+
{
377+
Log.Info("Session{0} found server redirection Host={1}; Port={2}; User={3}", session.Id, host, port, user);
378+
379+
if (host != ConnectionSettings.HostNames![0] || port != ConnectionSettings.Port || user != ConnectionSettings.UserID)
380+
{
381+
var redirectedSettings = ConnectionSettings.CloneWith(host, port, user);
382+
Log.Info("Pool{0} opening new connection to Host={1}; Port={2}; User={3}", m_logArguments[0], host, port, user);
383+
var redirectedSession = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId));
384+
await redirectedSession.ConnectAsync(redirectedSettings, startTickCount, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false);
385+
Log.Info("Pool{0} closing Session{1} to use redirected Session{2} instead", m_logArguments[0], session.Id, redirectedSession.Id);
386+
await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
387+
return redirectedSession;
388+
}
389+
else
390+
{
391+
Log.Info("Session{0} is already connected to this server; ignoring redirection", session.Id);
392+
}
393+
}
394+
}
395+
396+
return session;
397+
}
398+
368399
public static ConnectionPool? GetPool(string connectionString)
369400
{
370401
// check single-entry MRU cache for this exact connection string; most applications have just one

src/MySqlConnector/Core/ConnectionSettings.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
150150
static int ToSigned(uint value) => value >= int.MaxValue ? int.MaxValue : (int) value;
151151
}
152152

153-
public ConnectionSettings CloneWith(string host, int port, string userId, bool isRedirected) => new ConnectionSettings(this, host, port, userId, isRedirected);
153+
public ConnectionSettings CloneWith(string host, int port, string userId) => new ConnectionSettings(this, host, port, userId);
154154

155155
private static MySqlGuidFormat GetEffectiveGuidFormat(MySqlGuidFormat guidFormat, bool oldGuids)
156156
{
@@ -242,7 +242,6 @@ private static MySqlGuidFormat GetEffectiveGuidFormat(MySqlGuidFormat guidFormat
242242
public bool UseXaTransactions { get; }
243243

244244
public byte[]? ConnectionAttributes { get; set; }
245-
public bool IsRedirected { get; }
246245

247246
// Helper Functions
248247
int? m_connectionTimeoutMilliseconds;
@@ -268,7 +267,7 @@ public int ConnectionTimeoutMilliseconds
268267
}
269268
}
270269

271-
private ConnectionSettings(ConnectionSettings other, string host, int port, string userId, bool isRedirected)
270+
private ConnectionSettings(ConnectionSettings other, string host, int port, string userId)
272271
{
273272
ConnectionStringBuilder = other.ConnectionStringBuilder;
274273
ConnectionString = other.ConnectionString;
@@ -324,8 +323,6 @@ private ConnectionSettings(ConnectionSettings other, string host, int port, stri
324323
UseAffectedRows = other.UseAffectedRows;
325324
UseCompression = other.UseCompression;
326325
UseXaTransactions = other.UseXaTransactions;
327-
328-
IsRedirected = isRedirected;
329326
}
330327

331328
static readonly string[] s_localhostPipeServer = { "." };

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,10 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
340340
m_state = State.Closed;
341341
}
342342

343-
public async Task ConnectAsync(ConnectionSettings cs, int startTickCount, ILoadBalancer? loadBalancer, IOBehavior ioBehavior, CancellationToken cancellationToken)
343+
public async Task<string?> ConnectAsync(ConnectionSettings cs, int startTickCount, ILoadBalancer? loadBalancer, IOBehavior ioBehavior, CancellationToken cancellationToken)
344344
{
345+
string? statusInfo = null;
346+
345347
try
346348
{
347349
lock (m_lock)
@@ -350,7 +352,6 @@ public async Task ConnectAsync(ConnectionSettings cs, int startTickCount, ILoadB
350352
m_state = State.Connecting;
351353
}
352354

353-
serverRedirection:
354355
// TLS negotiation should automatically fall back to the best version supported by client and server. However,
355356
// Windows Schannel clients will fail to connect to a yaSSL-based MySQL Server if TLS 1.2 is requested and
356357
// have to use only TLS 1.1: https://github.com/mysql-net/MySqlConnector/pull/101
@@ -467,34 +468,7 @@ public async Task ConnectAsync(ConnectionSettings cs, int startTickCount, ILoadB
467468
}
468469

469470
var ok = OkPayload.Create(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
470-
if (ok.StatusInfo is not null && ok.StatusInfo.StartsWith("Location: mysql://", StringComparison.Ordinal))
471-
{
472-
// server redirection string has the format "Location: mysql://{host}:{port}/user={userId}[&ttl={ttl}]"
473-
m_logArguments[1] = ok.StatusInfo;
474-
Log.Info("Session{0} has server redirection header {1}", m_logArguments);
475-
476-
if (cs.IsRedirected)
477-
{
478-
Log.Info("Session{0} is already redirected; ignoring it.", m_logArguments);
479-
}
480-
else if (Utility.TryParseRedirectionHeader(ok.StatusInfo, out var host, out var port, out var user))
481-
{
482-
Log.Info("Session{0} found server redirection Host={1}; Port={2}; User={3}", m_logArguments[0], host, port, user);
483-
484-
if (host != cs.HostNames![0] || port != cs.Port || user != cs.UserID)
485-
{
486-
Log.Info("Session{0} closing existing connection", m_logArguments);
487-
await SendAsync(QuitPayload.Instance, ioBehavior, cancellationToken).ConfigureAwait(false);
488-
Log.Info("Session{0} opening new connection to Host={1}; Port={2}; User={3}", m_logArguments[0], host, port, user);
489-
cs = cs.CloneWith(host, port, user, isRedirected: true);
490-
goto serverRedirection;
491-
}
492-
else
493-
{
494-
Log.Info("Session{0} is already connected to this server; ignoring redirection", m_logArguments);
495-
}
496-
}
497-
}
471+
statusInfo = ok.StatusInfo;
498472

499473
if (m_useCompression)
500474
m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler);
@@ -519,6 +493,8 @@ public async Task ConnectAsync(ConnectionSettings cs, int startTickCount, ILoadB
519493
Log.Error(ex, "Session{0} couldn't connect to server", m_logArguments);
520494
throw new MySqlException(MySqlErrorCode.UnableToConnectToHost, "Couldn't connect to server", ex);
521495
}
496+
497+
return statusInfo;
522498
}
523499

524500
public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)