private async Task CreateMinimumPooledSessions(IOBehavior ioBehavior, CancellationToken cancellationToken) { while (true) { lock (m_sessions) { // check if the desired minimum number of sessions have been created if (ConnectionSettings.MaximumPoolSize - m_sessionSemaphore.CurrentCount + m_sessions.Count >= ConnectionSettings.MinimumPoolSize) { return; } } // acquire the semaphore, to ensure that the maximum number of sessions isn't exceeded; if it can't be acquired, // we have reached the maximum number of sessions and no more need to be created if (ioBehavior == IOBehavior.Asynchronous) { if (!await m_sessionSemaphore.WaitAsync(0, cancellationToken).ConfigureAwait(false)) { return; } } else { if (!m_sessionSemaphore.Wait(0, cancellationToken)) { return; } } try { var session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId)); Log.Info("{0} created Session{1} to reach minimum pool size", m_logArguments[0], session.Id); await session.ConnectAsync(ConnectionSettings, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false); AdjustHostConnectionCount(session, 1); lock (m_sessions) m_sessions.AddFirst(session); } finally { // connection is in pool; semaphore shouldn't be held any more m_sessionSemaphore.Release(); } } }
private bool SessionIsHealthy(ServerSession session) { if (!session.IsConnected) { return(false); } if (session.PoolGeneration != m_generation) { return(false); } if (ConnectionSettings.ConnectionLifeTime > 0 && unchecked ((uint)Environment.TickCount) - session.CreatedTicks >= ConnectionSettings.ConnectionLifeTime) { return(false); } return(true); }
// Returns zero for healthy, non-zero otherwise. private int GetSessionHealth(ServerSession session) { if (!session.IsConnected) { return(1); } if (session.PoolGeneration != m_generation) { return(2); } if (ConnectionSettings.ConnectionLifeTime > 0 && unchecked ((uint)Environment.TickCount) - session.CreatedTicks >= ConnectionSettings.ConnectionLifeTime) { return(3); } return(0); }
public async ValueTask ReturnAsync(IOBehavior ioBehavior, ServerSession session) #endif { if (Log.IsDebugEnabled()) { Log.Debug("Pool{0} receiving Session{1} back", m_logArguments[0], session.Id); } try { lock (m_leasedSessions) m_leasedSessions.Remove(session.Id); session.OwningConnection = null; var sessionHealth = GetSessionHealth(session); if (sessionHealth == 0) { lock (m_sessions) m_sessions.AddFirst(session); } else { if (sessionHealth == 1) { Log.Warn("Pool{0} received invalid Session{1}; destroying it", m_logArguments[0], session.Id); } else { Log.Info("Pool{0} received expired Session{1}; destroying it", m_logArguments[0], session.Id); } AdjustHostConnectionCount(session, -1); await session.DisposeAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); } } finally { m_sessionSemaphore.Release(); } #if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0 return(default);
private bool SessionIsHealthy(ServerSession session) { if (!session.IsConnected) { return(false); } if (session.PoolGeneration != m_generation) { return(false); } if (session.DatabaseOverride != null) { return(false); } if (ConnectionSettings.ConnectionLifeTime > 0 && (DateTime.UtcNow - session.CreatedUtc).TotalSeconds >= ConnectionSettings.ConnectionLifeTime) { return(false); } return(true); }
public void Return(ServerSession session) { try { lock (m_leasedSessions) m_leasedSessions.Remove(session.Id); session.OwningConnection = null; if (SessionIsHealthy(session)) { lock (m_sessions) m_sessions.AddFirst(session); } else { AdjustHostConnectionCount(session, -1); session.DisposeAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult(); } } finally { m_sessionSemaphore.Release(); } }
public void Return(ServerSession session) { if (Log.IsDebugEnabled()) { Log.Debug("Pool{0} receiving Session{1} back", m_logArguments[0], session.Id); } try { lock (m_leasedSessions) m_leasedSessions.Remove(session.Id); session.OwningConnection = null; var sessionHealth = GetSessionHealth(session); if (sessionHealth == 0) { lock (m_sessions) m_sessions.AddFirst(session); } else { if (sessionHealth == 1) { Log.Warn("Pool{0} received invalid Session{1}; destroying it", m_logArguments[0], session.Id); } else { Log.Info("Pool{0} received expired Session{1}; destroying it", m_logArguments[0], session.Id); } AdjustHostConnectionCount(session, -1); session.DisposeAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult(); } } finally { m_sessionSemaphore.Release(); } }
private async ValueTask <ServerSession> ConnectSessionAsync(string logMessage, int startTickCount, IOBehavior ioBehavior, CancellationToken cancellationToken) { var session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId)); if (Log.IsInfoEnabled()) { Log.Info(logMessage, m_logArguments[0], session.Id); } var statusInfo = await session.ConnectAsync(ConnectionSettings, startTickCount, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false); 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.Info("Session{0} has server redirection header {1}", session.Id, statusInfo); if (ConnectionSettings.ServerRedirectionMode == MySqlServerRedirectionMode.Disabled) { Log.Info("Pool{0} server redirection is disabled; ignoring redirection", m_logArguments); } 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)
private async Task CleanPoolAsync(IOBehavior ioBehavior, Func <ServerSession, bool> shouldCleanFn, bool respectMinPoolSize, CancellationToken cancellationToken) { // synchronize access to this method as only one clean routine should be run at a time if (ioBehavior == IOBehavior.Asynchronous) { await m_cleanSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); } else { m_cleanSemaphore.Wait(cancellationToken); } try { var waitTimeout = TimeSpan.FromMilliseconds(10); while (true) { // if respectMinPoolSize is true, return if (leased sessions + waiting sessions <= minPoolSize) if (respectMinPoolSize) { lock (m_sessions) if (ConnectionSettings.MaximumPoolSize - m_sessionSemaphore.CurrentCount + m_sessions.Count <= ConnectionSettings.MinimumPoolSize) { return; } } // try to get an open slot; if this fails, connection pool is full and sessions will be disposed when returned to pool if (ioBehavior == IOBehavior.Asynchronous) { if (!await m_sessionSemaphore.WaitAsync(waitTimeout, cancellationToken).ConfigureAwait(false)) { return; } } else { if (!m_sessionSemaphore.Wait(waitTimeout, cancellationToken)) { return; } } try { // check for a waiting session ServerSession session = null; lock (m_sessions) { if (m_sessions.Count > 0) { session = m_sessions.Last.Value; m_sessions.RemoveLast(); } } if (session == null) { return; } if (shouldCleanFn(session)) { // session should be cleaned; dispose it and keep iterating Log.Info("{0} found Session{1} to clean up", m_logArguments[0], session.Id); await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } else { // session should not be cleaned; put it back in the queue and stop iterating lock (m_sessions) m_sessions.AddLast(session); return; } } finally { m_sessionSemaphore.Release(); } } } finally { m_cleanSemaphore.Release(); } }
public async ValueTask <ServerSession> GetSessionAsync(MySqlConnection connection, IOBehavior ioBehavior, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // if all sessions are used, see if any have been leaked and can be recovered // check at most once per second (although this isn't enforced via a mutex so multiple threads might block // on the lock in RecoverLeakedSessions in high-concurrency situations if (m_sessionSemaphore.CurrentCount == 0 && unchecked (((uint)Environment.TickCount) - m_lastRecoveryTime) >= 1000u) { Log.Warn("{0} is empty; recovering leaked sessions", m_logArguments); RecoverLeakedSessions(); } if (ConnectionSettings.MinimumPoolSize > 0) { await CreateMinimumPooledSessions(ioBehavior, cancellationToken).ConfigureAwait(false); } // wait for an open slot (until the cancellationToken is cancelled, which is typically due to timeout) Log.Debug("{0} waiting for an available session", m_logArguments); if (ioBehavior == IOBehavior.Asynchronous) { await m_sessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); } else { m_sessionSemaphore.Wait(cancellationToken); } try { // check for a waiting session ServerSession session = null; lock (m_sessions) { if (m_sessions.Count > 0) { session = m_sessions.First.Value; m_sessions.RemoveFirst(); } } if (session != null) { Log.Debug("{0} found an existing session; checking it for validity", m_logArguments); bool reuseSession; if (session.PoolGeneration != m_generation) { Log.Debug("{0} discarding session due to wrong generation", m_logArguments); reuseSession = false; } else { if (ConnectionSettings.ConnectionReset) { reuseSession = await session.TryResetConnectionAsync(ConnectionSettings, ioBehavior, cancellationToken).ConfigureAwait(false); } else { reuseSession = await session.TryPingAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } } if (!reuseSession) { // session is either old or cannot communicate with the server Log.Warn("{0} Session{1} is unusable; destroying it", m_logArguments[0], session.Id); AdjustHostConnectionCount(session, -1); await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } else { // pooled session is ready to be used; return it session.OwningConnection = new WeakReference <MySqlConnection>(connection); int leasedSessionsCountPooled; lock (m_leasedSessions) { m_leasedSessions.Add(session.Id, session); leasedSessionsCountPooled = m_leasedSessions.Count; } if (Log.IsDebugEnabled()) { Log.Debug("{0} returning pooled Session{1} to caller; m_leasedSessions.Count={2}", m_logArguments[0], session.Id, leasedSessionsCountPooled); } return(session); } } // create a new session session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId)); if (Log.IsInfoEnabled()) { Log.Info("{0} no pooled session available; created new Session{1}", m_logArguments[0], session.Id); } await session.ConnectAsync(ConnectionSettings, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false); AdjustHostConnectionCount(session, 1); session.OwningConnection = new WeakReference <MySqlConnection>(connection); int leasedSessionsCountNew; lock (m_leasedSessions) { m_leasedSessions.Add(session.Id, session); leasedSessionsCountNew = m_leasedSessions.Count; } if (Log.IsDebugEnabled()) { Log.Debug("{0} returning new Session{1} to caller; m_leasedSessions.Count={2}", m_logArguments[0], session.Id, leasedSessionsCountNew); } return(session); } catch { m_sessionSemaphore.Release(); throw; } }
public static void AddSession(ServerSession session, MySqlConnection?owningConnection) { var resetTask = session.TryResetConnectionAsync(session.Pool !.ConnectionSettings, IOBehavior.Asynchronous, default);
public async ValueTask <int> ReturnAsync(IOBehavior ioBehavior, ServerSession session)
public async Task <ServerSession> GetSessionAsync(MySqlConnection connection, IOBehavior ioBehavior, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // if all sessions are used, see if any have been leaked and can be recovered // check at most once per second (although this isn't enforced via a mutex so multiple threads might block // on the lock in RecoverLeakedSessions in high-concurrency situations if (m_sessionSemaphore.CurrentCount == 0 && unchecked (((uint)Environment.TickCount) - m_lastRecoveryTime) >= 1000u) { RecoverLeakedSessions(); } if (m_connectionSettings.MinimumPoolSize > 0) { await CreateMinimumPooledSessions(ioBehavior, cancellationToken).ConfigureAwait(false); } // wait for an open slot (until the cancellationToken is cancelled, which is typically due to timeout) if (ioBehavior == IOBehavior.Asynchronous) { await m_sessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); } else { m_sessionSemaphore.Wait(cancellationToken); } try { // check for a waiting session ServerSession session = null; lock (m_sessions) { if (m_sessions.Count > 0) { session = m_sessions.First.Value; m_sessions.RemoveFirst(); } } if (session != null) { bool reuseSession; if (session.PoolGeneration != m_generation) { reuseSession = false; } else { if (m_connectionSettings.ConnectionReset) { reuseSession = await session.TryResetConnectionAsync(m_connectionSettings, ioBehavior, cancellationToken).ConfigureAwait(false); } else { reuseSession = await session.TryPingAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } } if (!reuseSession) { // session is either old or cannot communicate with the server AdjustHostConnectionCount(session, -1); await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } else { // pooled session is ready to be used; return it session.OwningConnection = new WeakReference <MySqlConnection>(connection); lock (m_leasedSessions) m_leasedSessions.Add(session.Id, session); return(session); } } // create a new session session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastId)); await session.ConnectAsync(m_connectionSettings, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false); AdjustHostConnectionCount(session, 1); session.OwningConnection = new WeakReference <MySqlConnection>(connection); lock (m_leasedSessions) m_leasedSessions.Add(session.Id, session); return(session); } catch { m_sessionSemaphore.Release(); throw; } }