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);
        }
Example #4
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();
            }
        }
Example #8
0
        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);
Example #12
0
 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;
            }
        }