2
\$\begingroup\$

We use a student information system called Colleague that is developed by Ellucian. As far as I can tell, the system does not use any sort of session pool so rapidly opening and closing connections to the database is both expensive/slow and can use up a lot of resources on the server side. In response I developed a session manager to help facilitate parallel and multi-threaded programming.

The code is simple and should be straight forward however seeing how this type of code is hard to test and debug I thought I would get a second opinion. Especially let me know if you find any issues that might lead to race conditions or deadlocks.

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace Person.Colleague
{
    public class SessionManager : IDisposable
    {
        public int NumberOfSessions
        {
            get
            {
                return this.numberOfSessions;
            }

            set
            {
                if (this.Started)
                    return;
                this.numberOfSessions = value;
            }
        }

        public bool Started
        {
            get
            {
                return this.started;
            }

            private set
            {
                this.started = value;
            }
        }

        private int numberOfSessions = 0;
        private int offSet = 0;
        private bool started = false;
        private ConcurrentDictionary<int, ManagedSession> sessionList;
        private object sessionListLock = new object();
        private object instanceLock = new object();
        private string username = String.Empty;
        private string password = String.Empty;

        public SessionManager(int numberOfSessions)
        {
            this.numberOfSessions = numberOfSessions;
            this.sessionList = new ConcurrentDictionary<int, ManagedSession>(5, this.numberOfSessions);
        }

        public void Start(string username = "", string password = "", string colleagueAccountName = "production")
        {
            if (this.Started)
                return;

            this.username = username;
            this.password = password;

            lock (instanceLock)
            {
                lock (sessionListLock)
                {
                    for (int x = 0; x < this.numberOfSessions; x++)
                    {
                        this.sessionList.TryAdd(x, new ManagedSession(x, this.username, this.password, colleagueAccountName));
                        this.sessionList[x].InUse = false;
                    }
                }
            }

            this.Started = true;
        }

        public void Stop()
        {
            this.Started = false;
            // THIS METHOD WILL TRY TO GRACEFULLY CLOSE ALL THE OPEN COLLEAGUE SESSIONS
            // IT WILL LOOP THROUGH ALL THE OPEN CONNECTIONS CLOSING THEM ONE AT A TIME
            // IF THE CONNECTION IS CURRENTLY CHECKED OUT IT WILL SKIP THAT ONE AND TRY
            // AGAIN AFTER WAITING 1 SECOND IF AFTER 30 SEONCDS ALL THE CONNECTIONS HAVE
            // NOT CHECKED IN AND BEEN CLOSED THEY WILL BE FORCED
            lock (instanceLock)
            {
                int closedCount = 0;
                for (int i = 0; i < 30; i++)
                {
                    for (int x = 0; x < this.numberOfSessions; x++)
                    {
                        if (!this.sessionList[x].InUse)
                        {
                            this.sessionList[x].Session.Close();
                            closedCount++;    
                        }
                    }
                    if (closedCount == this.numberOfSessions)
                        break;
                    System.Threading.Thread.Sleep(1000); // wait one second

                    // FORCE THE THE SESSIONS TO CLOSE
                    if (i == 29 && closedCount != this.numberOfSessions)
                    {
                        for (int x = 0; x < this.numberOfSessions; x++)
                        {
                            this.sessionList[x].Session.Close();
                        }
                    }
                }
            }
        }

        public ManagedSession CheckOutSession(int sessionID = -1)
        {
            if (!this.Started)
                throw new SessionManagerNotStartedException("Cannot check out a new session. The SessionManager object is either shutting down or was never started.");

            int attemptCount = 0;
            int offSetAdjusted = 0;

            if (sessionID != -1 && this.sessionList[sessionID].InUse)
                throw new SessionAlreadyCheckedOutException(sessionID);

            while (sessionID == -1)
            {
                lock (sessionListLock)
                {
                    for (int i = 0; i < this.numberOfSessions; i++)
                    {
                        // OFFSET WHERE WE START CHECKING FOR A FREE SESSION SO WE ROTATE
                        // THROUGH THEM INSTEAD OF USING THE FIRST FEW SESSIONS OVER 
                        // AND OVER IN LOW USE SCENARIOS
                        offSetAdjusted = offSet + i;
                        if (offSetAdjusted >= this.numberOfSessions)
                            offSetAdjusted -= this.numberOfSessions;
                        if (!this.sessionList[offSetAdjusted].InUse)
                        {
                            this.sessionList[offSetAdjusted].InUse = true;
                            sessionID = offSetAdjusted;
                            if (this.sessionList[sessionID].Session == null)
                            {
                                this.sessionList[sessionID].Session = new ColleagueSession();
                                this.sessionList[sessionID].Session.Open(this.username, this.password);
                            }
                            if (this.sessionList[sessionID].Session.IsActive == false)
                            {
                                this.sessionList[sessionID].Session.Open(this.username, this.password);
                            }
                            break;
                        }
                    }
                    this.offSet = this.offSet + 1;
                    if (this.offSet >= this.numberOfSessions)
                        this.offSet = 0;
                }
                if (sessionID != -1)
                    break;

                attemptCount = attemptCount + 1;
                if (attemptCount > 500)
                    throw new SessionCheckOutTimedOutException();

                System.Threading.Thread.Sleep(25);
            }

            return this.sessionList[sessionID];
        }

        public void CheckInSession(ManagedSession managedSession)
        {
            this.sessionList[managedSession.SessionID].InUse = false;
        }

        public void Dispose()
        {
            this.Stop();
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            this.Stop();
        }

        public int NumberOfActiveSessions()
        {
            int count = 0;
            foreach (ManagedSession managedSession in this.sessionList.Values)
            {
                if (managedSession.Session.IsActive)
                    count++;
            }
            return count;
        }

        public int NumberOfCheckedOutSessions()
        {
            int count = 0;
            foreach (ManagedSession managedSession in this.sessionList.Values)
            {
                if (managedSession.InUse)
                    count++;
            }
            return count;
        }

        public void RefreshSessions(int sessionID = -1)
        {
            int[] sessionIDs;
            if (sessionID != -1)
                sessionIDs = new int[] { sessionID };
            else
                sessionIDs = this.sessionList.Keys.ToArray();

            int attemptCount = 0;
            foreach (int ID in sessionIDs)
            {
                attemptCount = 0;
                bool success = false;
                if (this.sessionList.Keys.Contains(ID))
                {
                    while (success == false)
                    {
                        try
                        {
                            ManagedSession managedSession = this.CheckOutSession(ID);
                            managedSession.RefreshSession();
                            this.CheckInSession(managedSession);
                            success = true;
                        }
                        catch (Exception ex)
                        {
                            if (ex is Person.Colleague.SessionManager.SessionAlreadyCheckedOutException)
                            {
                                attemptCount = attemptCount + 1;
                                System.Threading.Thread.Sleep(1000);
                            }
                        }
                        if (attemptCount > 5)
                            throw new SessionRefreshTimeOutException(ID);
                    }
                }
                else
                {
                    throw new BadSessionIDException();
                }
            }
        }

        public class ManagedSession
        {
            public Person.Colleague.ColleagueSession Session
            {
                get
                {
                    return this.session;
                }
                internal set
                {
                    this.Session = value;
                }
            }

            public int SessionID
            {
                get
                {
                    return this.sessionID;
                }
            }
            internal bool InUse { get; set; }
            private string Username { get; set; }
            private string Password { get; set; }
            private string ColleagueAccountName { get; set; }

            private ColleagueSession session;
            private int sessionID;

            internal ManagedSession(int sessionID, string username = null, string password = null, string colleagueAccountName = null)
            {
                this.Username = username;
                this.Password = password;
                this.ColleagueAccountName = colleagueAccountName;

                this.session = new Colleague.ColleagueSession();
                this.session.Open(this.Username, this.Password, ColleagueAccountName);
                this.sessionID = sessionID;
            }

            public void RefreshSession()
            {
                if (this.session != null)
                {
                    this.session.Close();
                    this.session.Open(this.Username, this.Password, this.session.ColleagueAccountName);
                    if (this.session.ColleagueInitialized)
                    {
                        this.session.ColleagueInitialized = false;
                        this.session.InitializeColleagueConnection();
                    }
                }
            }
        }

        [Serializable]
        public class SessionRefreshTimeOutException : Exception
        {
            public int SessionID { get; set; }

            public SessionRefreshTimeOutException(int sessionID) { this.SessionID = sessionID; }
            public SessionRefreshTimeOutException(int sessionID, string message) : base(message) { this.SessionID = sessionID; }
            public SessionRefreshTimeOutException(int sessionID, string message, Exception inner) : base(message, inner) { this.SessionID = sessionID; }
            protected SessionRefreshTimeOutException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context)
                : base(info, context) { }
        }

        [Serializable]
        public class SessionManagerNotStartedException : Exception
        {
            public SessionManagerNotStartedException() { }
            public SessionManagerNotStartedException(string message) : base(message) { }
            public SessionManagerNotStartedException(string message, Exception inner) : base(message, inner) { }
            protected SessionManagerNotStartedException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context)
                : base(info, context) { }
        }

        [Serializable]
        public class SessionCheckOutTimedOutException : Exception
        {
            public SessionCheckOutTimedOutException() { }
            public SessionCheckOutTimedOutException(string message) : base(message) { }
            public SessionCheckOutTimedOutException(string message, Exception inner) : base(message, inner) { }
            protected SessionCheckOutTimedOutException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context)
                : base(info, context) { }
        }

        [Serializable]
        public class BadSessionIDException : Exception
        {
            public BadSessionIDException() { }
            public BadSessionIDException(string message) : base(message) { }
            public BadSessionIDException(string message, Exception inner) : base(message, inner) { }
            protected BadSessionIDException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context)
                : base(info, context) { }
        }

        [Serializable]
        public class SessionAlreadyCheckedOutException : Exception
        {
            public int SessionID { get; set; }

            public SessionAlreadyCheckedOutException(int sessionID) { this.SessionID = sessionID; }
            public SessionAlreadyCheckedOutException(int sessionID, string message) : base(message) { this.SessionID = sessionID; }
            public SessionAlreadyCheckedOutException(int sessionID, string message, Exception inner) : base(message, inner) { this.SessionID = sessionID; }
            protected SessionAlreadyCheckedOutException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context)
                : base(info, context) { }
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Try profiling the database/application before taking this route (ex: configure connection pooling) instead. What you trying to do is quite hard and requires some level of expertise that I don't think you have \$\endgroup\$ Commented May 13, 2015 at 21:34

1 Answer 1

3
\$\begingroup\$
public Person.Colleague.ColleagueSession Session
{
    get
    {
        return this.session;
    }
    internal set
    {
        this.Session = value;
    }
}

There's a typo in the setter, it should be this.session = value;. As it stands, calling the setter will cause the program to crash. You can avoid typos like this by using auto-implemented properties, e.g.

public Person.Colleague.ColleagueSession Session { get; internal set; }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.