M
Mr. Mountain
This question isn't about ASP.NET or sessions. However, to illustrate my
question about threading, I'll explain the situation I'm working on in an
ASP.NET app. We need to do some work on a session start or session end
event. Because session IDs get reused (unless the user closes the browser),
there have been times when a session start event was received while that
same sessionID was still ending. There have also been times when a session
end event was received while a session was still starting. These are
obviously rare issues, and are mostly related to other exceptions that might
have been throw.
Anyway, this question isn't about sessions, it's about threading. I have
written the following code to ensure that sessions with the same ID are not
processed by the start_session code while the end_session code is still
working on that same session ID (and vice versa).
I would appreciate any feedback on the code. Does anyone see any obvious
synchronization issues?
The code contains a console app. The test code can be run in a loop for any
length of time.
using System;
using System.Threading;
using System.Collections;
namespace testing
{
/// <summary>
/// solution for cleaning up sessions where sessionID can be reused.
/// </summary>
public class SessionStarter
{
private const int waitTime = 400;//used in TryEnter
public static void Main(string[] args)
{
SessionStarter ss = new SessionStarter();
bool keepTesting = true;
DateTime timeLimit = DateTime.Now.AddMinutes(5.015);
Thread startSessionThread;
Thread endSessionThread;
Thread startSessionThread2;
Thread endSessionThread2;
Thread startSessionThread3;
Thread endSessionThread3;
while (keepTesting && DateTime.Now < timeLimit)
{
startSessionThread = new Thread(
new ThreadStart(ss.Start));
startSessionThread.Name = "startThread1";
endSessionThread = new Thread(
new ThreadStart(ss.End));
endSessionThread.Name = "endThread1";
startSessionThread2 = new Thread(
new ThreadStart(ss.Start2));
startSessionThread2.Name = "startThread2";
endSessionThread2 = new Thread(
new ThreadStart(ss.End2));
endSessionThread2.Name = "endThread2";
startSessionThread3 = new Thread(
new ThreadStart(ss.Start3));
startSessionThread3.Name = "startThread3";
endSessionThread3 = new Thread(
new ThreadStart(ss.End3));
endSessionThread3.Name = "endThread3";
try
{
startSessionThread.Start();
endSessionThread.Start();
endSessionThread2.Start();
startSessionThread2.Start();
endSessionThread3.Start();
startSessionThread3.Start();
}
catch (Exception e)
{
keepTesting = false;
Console.WriteLine(e.Message);
}
finally
{
startSessionThread.Join();
endSessionThread.Join();
startSessionThread2.Join();
endSessionThread2.Join();
startSessionThread3.Join();
endSessionThread3.Join();
Console.WriteLine();
Console.WriteLine(" --------------- repeating test ---------------");
}
}
Console.Write("Main is finished: ");
Console.Read();
}
#region ThreadStart Delegates
public void Start()
{
StartSession("Betty", 100);
}
public void Start2()
{
StartSession("Boop", 395);
}
public void Start3()
{
StartSession("Spunky", 30);
}
public void End()
{
EndSession("Betty", 50);
}
public void End2()
{
EndSession("Boop", 390);
}
public void End3()
{
EndSession("Spunky", 100);
}
#endregion
private class ActiveSession
{
public string SessionID;
public ActiveSession(string sessionID)
{
this.SessionID = sessionID;
}//ctor
}//struct ActiveSession
private static Hashtable sessionsBeingProcessed = new Hashtable();
private static void CheckForConflictingSession(string sessionID, out
ActiveSession mySession)
{
if (Monitor.TryEnter(sessionsBeingProcessed, waitTime))
{
try
{
if (sessionsBeingProcessed.Contains(sessionID))
{
mySession = sessionsBeingProcessed[sessionID] as ActiveSession;
Console.WriteLine(">>>>>>>>> " + Thread.CurrentThread.Name + ": " +
sessionID + " is already being processed! <<<<<<<<<");
}
else
{
mySession = new ActiveSession(sessionID);
sessionsBeingProcessed.Add(sessionID, mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " added to
sessionsBeingProcessed");
}
}
finally
{
Monitor.Exit(sessionsBeingProcessed);
}
}
else
{
Exception e = new Exception(Thread.CurrentThread.Name + ": " + "Exception:
could not get lock on sessionsBeingProcessed for " + sessionID);
//log it
throw e;
}
}//CheckForConflictingSession
public static void StartSession(string sessionID, int workAmount)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering StartSession
method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpStartSession(sessionID, mySession, workAmount);
}//StartSession
private static void HelpStartSession(string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnter(mySession, waitTime);
if (entered2)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is OK to
be initialized.");
try
{
//do all work
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Starting session " +
sessionID + " initialization...");
Thread.Sleep(workAmount);
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Finished session " +
sessionID + " initialization...");
}
catch (Exception )
{
Exception me = new Exception("Exception: Unable to complete session " +
sessionID + " initialization.");
//log it
throw me;
}
finally
{
//Monitor.Pulse(mySession);
sessionsBeingProcessed.Remove(sessionID);
Monitor.Exit(mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is
unlocked by StartSession");
}
}
else
{
Exception me2 = new Exception("Timed out: could not get Monitor to start
sessionID " + sessionID);
//log it
throw me2;
}
}//HelpStartSession
public static void EndSession(string sessionID, int workAmount)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering EndSession
method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpEndSession(sessionID, mySession, workAmount);
}//EndSession
private static void HelpEndSession(string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnter(mySession, waitTime);
if (entered2)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is OK to
be terminated.");
try
{
//do all work
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Beginning session " +
sessionID + " termination...");
Thread.Sleep(workAmount);
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Completed session " +
sessionID + " termination.");
}
catch (Exception e)
{
//log it, etc.
throw e;
}
finally
{
//Monitor.Pulse(mySession);
sessionsBeingProcessed.Remove(sessionID);
Monitor.Exit(mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is
unlocked by EndSession");
}
}
else
{
//log and throw exception
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Exception: could not
get Monitor to end sessionID " + sessionID);
throw new Exception();
}
}
}//class SessionStarter
}//namespace
question about threading, I'll explain the situation I'm working on in an
ASP.NET app. We need to do some work on a session start or session end
event. Because session IDs get reused (unless the user closes the browser),
there have been times when a session start event was received while that
same sessionID was still ending. There have also been times when a session
end event was received while a session was still starting. These are
obviously rare issues, and are mostly related to other exceptions that might
have been throw.
Anyway, this question isn't about sessions, it's about threading. I have
written the following code to ensure that sessions with the same ID are not
processed by the start_session code while the end_session code is still
working on that same session ID (and vice versa).
I would appreciate any feedback on the code. Does anyone see any obvious
synchronization issues?
The code contains a console app. The test code can be run in a loop for any
length of time.
using System;
using System.Threading;
using System.Collections;
namespace testing
{
/// <summary>
/// solution for cleaning up sessions where sessionID can be reused.
/// </summary>
public class SessionStarter
{
private const int waitTime = 400;//used in TryEnter
public static void Main(string[] args)
{
SessionStarter ss = new SessionStarter();
bool keepTesting = true;
DateTime timeLimit = DateTime.Now.AddMinutes(5.015);
Thread startSessionThread;
Thread endSessionThread;
Thread startSessionThread2;
Thread endSessionThread2;
Thread startSessionThread3;
Thread endSessionThread3;
while (keepTesting && DateTime.Now < timeLimit)
{
startSessionThread = new Thread(
new ThreadStart(ss.Start));
startSessionThread.Name = "startThread1";
endSessionThread = new Thread(
new ThreadStart(ss.End));
endSessionThread.Name = "endThread1";
startSessionThread2 = new Thread(
new ThreadStart(ss.Start2));
startSessionThread2.Name = "startThread2";
endSessionThread2 = new Thread(
new ThreadStart(ss.End2));
endSessionThread2.Name = "endThread2";
startSessionThread3 = new Thread(
new ThreadStart(ss.Start3));
startSessionThread3.Name = "startThread3";
endSessionThread3 = new Thread(
new ThreadStart(ss.End3));
endSessionThread3.Name = "endThread3";
try
{
startSessionThread.Start();
endSessionThread.Start();
endSessionThread2.Start();
startSessionThread2.Start();
endSessionThread3.Start();
startSessionThread3.Start();
}
catch (Exception e)
{
keepTesting = false;
Console.WriteLine(e.Message);
}
finally
{
startSessionThread.Join();
endSessionThread.Join();
startSessionThread2.Join();
endSessionThread2.Join();
startSessionThread3.Join();
endSessionThread3.Join();
Console.WriteLine();
Console.WriteLine(" --------------- repeating test ---------------");
}
}
Console.Write("Main is finished: ");
Console.Read();
}
#region ThreadStart Delegates
public void Start()
{
StartSession("Betty", 100);
}
public void Start2()
{
StartSession("Boop", 395);
}
public void Start3()
{
StartSession("Spunky", 30);
}
public void End()
{
EndSession("Betty", 50);
}
public void End2()
{
EndSession("Boop", 390);
}
public void End3()
{
EndSession("Spunky", 100);
}
#endregion
private class ActiveSession
{
public string SessionID;
public ActiveSession(string sessionID)
{
this.SessionID = sessionID;
}//ctor
}//struct ActiveSession
private static Hashtable sessionsBeingProcessed = new Hashtable();
private static void CheckForConflictingSession(string sessionID, out
ActiveSession mySession)
{
if (Monitor.TryEnter(sessionsBeingProcessed, waitTime))
{
try
{
if (sessionsBeingProcessed.Contains(sessionID))
{
mySession = sessionsBeingProcessed[sessionID] as ActiveSession;
Console.WriteLine(">>>>>>>>> " + Thread.CurrentThread.Name + ": " +
sessionID + " is already being processed! <<<<<<<<<");
}
else
{
mySession = new ActiveSession(sessionID);
sessionsBeingProcessed.Add(sessionID, mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " added to
sessionsBeingProcessed");
}
}
finally
{
Monitor.Exit(sessionsBeingProcessed);
}
}
else
{
Exception e = new Exception(Thread.CurrentThread.Name + ": " + "Exception:
could not get lock on sessionsBeingProcessed for " + sessionID);
//log it
throw e;
}
}//CheckForConflictingSession
public static void StartSession(string sessionID, int workAmount)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering StartSession
method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpStartSession(sessionID, mySession, workAmount);
}//StartSession
private static void HelpStartSession(string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnter(mySession, waitTime);
if (entered2)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is OK to
be initialized.");
try
{
//do all work
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Starting session " +
sessionID + " initialization...");
Thread.Sleep(workAmount);
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Finished session " +
sessionID + " initialization...");
}
catch (Exception )
{
Exception me = new Exception("Exception: Unable to complete session " +
sessionID + " initialization.");
//log it
throw me;
}
finally
{
//Monitor.Pulse(mySession);
sessionsBeingProcessed.Remove(sessionID);
Monitor.Exit(mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is
unlocked by StartSession");
}
}
else
{
Exception me2 = new Exception("Timed out: could not get Monitor to start
sessionID " + sessionID);
//log it
throw me2;
}
}//HelpStartSession
public static void EndSession(string sessionID, int workAmount)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering EndSession
method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpEndSession(sessionID, mySession, workAmount);
}//EndSession
private static void HelpEndSession(string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnter(mySession, waitTime);
if (entered2)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is OK to
be terminated.");
try
{
//do all work
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Beginning session " +
sessionID + " termination...");
Thread.Sleep(workAmount);
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Completed session " +
sessionID + " termination.");
}
catch (Exception e)
{
//log it, etc.
throw e;
}
finally
{
//Monitor.Pulse(mySession);
sessionsBeingProcessed.Remove(sessionID);
Monitor.Exit(mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is
unlocked by EndSession");
}
}
else
{
//log and throw exception
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Exception: could not
get Monitor to end sessionID " + sessionID);
throw new Exception();
}
}
}//class SessionStarter
}//namespace