Skip to content
2 changes: 1 addition & 1 deletion .ci/config/config.compression+ssl.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "RsaEncryption,CachingSha2Password,Tls12,Tls13,UuidToBin",
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,UuidToBin",
"MySqlBulkLoaderLocalCsvFile": "../../../TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../TestData/LoadData_UTF8_BOM_Unix.TSV",
"CertificatesPath": "../../../../.ci/server/certs"
Expand Down
2 changes: 1 addition & 1 deletion .ci/config/config.compression.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket,ZeroDateTime",
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,UnixDomainSocket,ZeroDateTime",
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV"
}
Expand Down
2 changes: 1 addition & 1 deletion .ci/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket,ZeroDateTime",
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,UnixDomainSocket,ZeroDateTime",
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV"
}
Expand Down
2 changes: 1 addition & 1 deletion .ci/config/config.ssl.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
"PasswordlessUser": "no_password",
"SecondaryDatabase": "testdb2",
"UnsupportedFeatures": "RsaEncryption,CachingSha2Password,Tls12,Tls13,UuidToBin",
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,UuidToBin",
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV",
"CertificatesPath": "../../../../.ci/server/certs"
Expand Down
16 changes: 8 additions & 8 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ jobs:
arguments: '-c Release --no-restore'
testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net472/net8.0', 'No SSL') }}
env:
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket'
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,UnixDomainSocket'
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True;UseCompression=True'

- job: windows_integration_tests_2
Expand Down Expand Up @@ -174,7 +174,7 @@ jobs:
arguments: '-c Release --no-restore'
testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net6.0', 'No SSL') }}
env:
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket'
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,UnixDomainSocket'
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True'

- job: linux_integration_tests
Expand All @@ -187,27 +187,27 @@ jobs:
'MySQL 8.0':
image: 'mysql:8.0'
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
unsupportedFeatures: 'Ed25519,StreamingResults,Tls11,ZeroDateTime'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,ZeroDateTime'
'MySQL 8.4':
image: 'mysql:8.4'
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
unsupportedFeatures: 'Ed25519,StreamingResults,Tls11,ZeroDateTime'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,ZeroDateTime'
'MySQL 9.0':
image: 'mysql:9.0'
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
unsupportedFeatures: 'Ed25519,StreamingResults,Tls11,ZeroDateTime'
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,ZeroDateTime'
'MariaDB 10.6':
image: 'mariadb:10.6'
connectionStringExtra: ''
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin'
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin,Redirection'
'MariaDB 10.11':
image: 'mariadb:10.11'
connectionStringExtra: ''
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin'
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin,Redirection'
'MariaDB 11.4':
image: 'mariadb:11.4'
connectionStringExtra: ''
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin'
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin,Redirection'
steps:
- template: '.ci/integration-tests-steps.yml'
parameters:
Expand Down
90 changes: 12 additions & 78 deletions src/MySqlConnector/Core/ConnectionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@

namespace MySqlConnector.Core;

internal sealed class ConnectionPool : IDisposable
internal sealed class ConnectionPool : IConnectionPoolMetadata, IDisposable
{
public int Id { get; }

ConnectionPool? IConnectionPoolMetadata.ConnectionPool => this;

int IConnectionPoolMetadata.Generation => m_generation;

int IConnectionPoolMetadata.GetNewSessionId() => Interlocked.Increment(ref m_lastSessionId);

public string? Name { get; }

public ConnectionSettings ConnectionSettings { get; }
Expand Down Expand Up @@ -95,6 +101,7 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
m_leasedSessions.Add(session.Id, session);
leasedSessionsCountPooled = m_leasedSessions.Count;
}

MetricsReporter.AddUsed(this);
ActivitySourceHelper.CopyTags(session.ActivityTags, activity);
Log.ReturningPooledSession(m_logger, Id, session.Id, leasedSessionsCountPooled);
Expand All @@ -106,7 +113,8 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
}

// create a new session
session = await ConnectSessionAsync(connection, s_createdNewSession, startingTimestamp, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
session = await ServerSession.ConnectAndRedirectAsync(m_connectionLogger, m_logger, this, ConnectionSettings, m_loadBalancer,
connection, s_createdNewSession, startingTimestamp, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
AdjustHostConnectionCount(session, 1);
session.OwningConnection = new(connection);
int leasedSessionsCountNew;
Expand Down Expand Up @@ -402,7 +410,8 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh

try
{
var session = await ConnectSessionAsync(connection, s_createdToReachMinimumPoolSize, Stopwatch.GetTimestamp(), null, ioBehavior, cancellationToken).ConfigureAwait(false);
var session = await ServerSession.ConnectAndRedirectAsync(m_connectionLogger, m_logger, this, ConnectionSettings, m_loadBalancer,
connection, s_createdToReachMinimumPoolSize, Stopwatch.GetTimestamp(), null, ioBehavior, cancellationToken).ConfigureAwait(false);
AdjustHostConnectionCount(session, 1);
lock (m_sessions)
_ = m_sessions.AddFirst(session);
Expand All @@ -416,81 +425,6 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh
}
}

private async ValueTask<ServerSession> ConnectSessionAsync(MySqlConnection connection, Action<ILogger, int, string, Exception?> logMessage, long startingTimestamp, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
var session = new ServerSession(m_connectionLogger, this, m_generation, Interlocked.Increment(ref m_lastSessionId));
if (m_logger.IsEnabled(LogLevel.Debug))
logMessage(m_logger, Id, session.Id, null);
string? statusInfo;
try
{
statusInfo = await session.ConnectAsync(ConnectionSettings, connection, startingTimestamp, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
}
catch (Exception)
{
await session.DisposeAsync(ioBehavior, default).ConfigureAwait(false);
throw;
}

Exception? redirectionException = null;
if (statusInfo is not null && statusInfo.StartsWith("Location: mysql://", StringComparison.Ordinal))
{
// server redirection string has the format "Location: mysql://{host}:{port}/user={userId}[&ttl={ttl}]"
Log.HasServerRedirectionHeader(m_logger, session.Id, statusInfo);

if (ConnectionSettings.ServerRedirectionMode == MySqlServerRedirectionMode.Disabled)
{
Log.ServerRedirectionIsDisabled(m_logger, Id);
}
else if (Utility.TryParseRedirectionHeader(statusInfo, out var host, out var port, out var user))
{
if (host != ConnectionSettings.HostNames![0] || port != ConnectionSettings.Port || user != ConnectionSettings.UserID)
{
var redirectedSettings = ConnectionSettings.CloneWith(host, port, user);
Log.OpeningNewConnection(m_logger, Id, host, port, user);
var redirectedSession = new ServerSession(m_connectionLogger, this, m_generation, Interlocked.Increment(ref m_lastSessionId));
try
{
_ = await redirectedSession.ConnectAsync(redirectedSettings, connection, startingTimestamp, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.FailedToConnectRedirectedSession(m_logger, ex, Id, redirectedSession.Id);
redirectionException = ex;
}

if (redirectionException is null)
{
Log.ClosingSessionToUseRedirectedSession(m_logger, Id, session.Id, redirectedSession.Id);
await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
return redirectedSession;
}
else
{
try
{
await redirectedSession.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
}
catch (Exception)
{
}
}
}
else
{
Log.SessionAlreadyConnectedToServer(m_logger, session.Id);
}
}
}

if (ConnectionSettings.ServerRedirectionMode == MySqlServerRedirectionMode.Required)
{
Log.RequiresServerRedirection(m_logger, Id);
throw new MySqlException(MySqlErrorCode.UnableToConnectToHost, "Server does not support redirection", redirectionException);
}
return session;
}

public static ConnectionPool? CreatePool(string connectionString, MySqlConnectorLoggingConfiguration loggingConfiguration, string? name)
{
// parse connection string and check for 'Pooling' setting; return 'null' if pooling is disabled
Expand Down
8 changes: 6 additions & 2 deletions src/MySqlConnector/Core/ConnectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,12 @@ public int ConnectionTimeoutMilliseconds

private ConnectionSettings(ConnectionSettings other, string host, int port, string userId)
{
ConnectionStringBuilder = other.ConnectionStringBuilder;
ConnectionString = other.ConnectionString;
ConnectionStringBuilder = new MySqlConnectionStringBuilder(other.ConnectionString);
ConnectionStringBuilder.Port = (uint)port;
ConnectionStringBuilder.Server = host;
ConnectionStringBuilder.UserID = userId;

ConnectionString = ConnectionStringBuilder.ConnectionString;

ConnectionProtocol = MySqlConnectionProtocol.Sockets;
HostNames = [host];
Expand Down
26 changes: 26 additions & 0 deletions src/MySqlConnector/Core/IConnectionPoolMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace MySqlConnector.Core;

internal interface IConnectionPoolMetadata
{
/// <summary>
/// Returns the <see cref="ConnectionPool"/> this <see cref="IConnectionPoolMetadata"/> is associated with,
/// or <c>null</c> if it represents a non-pooled connection.
/// </summary>
ConnectionPool? ConnectionPool { get; }

/// <summary>
/// Returns the ID of the connection pool, or 0 if this is a non-pooled connection.
/// </summary>
int Id { get; }

/// <summary>
/// Returns the generation of the connection pool, or 0 if this is a non-pooled connection.
/// </summary>
int Generation { get; }

/// <summary>
/// Returns a new session ID.
/// </summary>
/// <returns>A new session ID.</returns>
int GetNewSessionId();
}
13 changes: 13 additions & 0 deletions src/MySqlConnector/Core/NonPooledConnectionPoolMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace MySqlConnector.Core;

internal sealed class NonPooledConnectionPoolMetadata : IConnectionPoolMetadata
{
public static IConnectionPoolMetadata Instance { get; } = new NonPooledConnectionPoolMetadata();

public ConnectionPool? ConnectionPool => null;
public int Id => 0;
public int Generation => 0;
public int GetNewSessionId() => Interlocked.Increment(ref m_lastId);

private int m_lastId;
}
Loading