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) { }
}
}
}