Threading question -- concurrency issues

  • Thread starter Thread starter Mr. Mountain
  • Start date Start date
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
 
Mr. Mountain said:
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.

Have you confirmed with Microsoft that this is actually happening? Session
ids are large enough that this should really be quite rare, if at all
possible.

John Saunders

P.S. I didn't even read your code, considering the formatting.
 
Hi John,
What should I do when posting code so that the formatting will be
better? How do you post code?

BTW, I usually just copy the code and paste it into my IDE so I get the
syntax highlighting I'm used to. I figured this is what most people do.

I would really appreciate any feedback on the threading issues. If
anyone sees a potential deadlock or other problem in my code, please
point it out to me. Thanks!

Regards,
Mountain
 
John Saunders said:
P.S. I didn't even read your code, considering the formatting.
Here is the code again -- hopefully in a better format. (I apologize if it
is still not readable.) I also changed (hacked up, actually) the test
method. What's bothering me about the code is that on rare occassions, the
max time taken by one of the 3 main session cleanup methods can be as high
as 2300 ms on my computer. Considering that they do nothing but sleep for
about 40 ms, something is clearly not right. I just haven't found the
problem yet.

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 = 4000;//used in TryEnter
private static bool keepTesting = true;
private static TimeSpan maxTime = TimeSpan.MinValue;

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)
{
DateTime start = DateTime.Now;
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
{
keepTesting = false;
Exception e = new Exception(Thread.CurrentThread.Name + ": " +
"Exception: could not get lock on sessionsBeingProcessed for " + sessionID);
//log it
throw e;
}
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; }
Console.WriteLine(Thread.CurrentThread.Name + ":
CheckForConflictingSession time: " + ts.TotalMilliseconds);

}//CheckForConflictingSession

public static void StartSession(string sessionID, int workAmount)
{
DateTime start = DateTime.Now;
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering
StartSession method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpStartSession(sessionID, mySession, workAmount);
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; }
Console.WriteLine(Thread.CurrentThread.Name + ": StartSession time: " +
ts.TotalMilliseconds);
}//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 )
{
keepTesting = false;
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
{
keepTesting = false;
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)
{
DateTime start = DateTime.Now;
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering EndSession
method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpEndSession(sessionID, mySession, workAmount);
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; }
Console.WriteLine(Thread.CurrentThread.Name + ": EndSession time: " +
ts.TotalMilliseconds);
}//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)
{
keepTesting = false;
//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
{
keepTesting = false;
//log and throw exception
Exception ex = new Exception(Thread.CurrentThread.Name + ": " + "Timed
Out. Could not get Monitor to end sessionID " + sessionID);
//log
throw ex;
}
}//HelpEndSession

public static void Main(string[] args)
{
SessionStarter ss = new SessionStarter();

DateTime timeLimit = DateTime.Now.AddMinutes(10.5015);

Thread startSessionThread;
Thread endSessionThread;
Thread startSessionThread2;
Thread endSessionThread2;
Thread startSessionThread3;
Thread endSessionThread3;
Thread startSessionThread4;
Thread endSessionThread4;
Thread startSessionThread5;
Thread endSessionThread5;

Random r = new Random();

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";

startSessionThread4 = new Thread(
new ThreadStart(ss.Start4));
startSessionThread4.Name = "startThread4";

endSessionThread4 = new Thread(
new ThreadStart(ss.End4));
endSessionThread4.Name = "endThread4";


startSessionThread5 = new Thread(
new ThreadStart(ss.Start5));
startSessionThread5.Name = "startThread5";

endSessionThread5 = new Thread(
new ThreadStart(ss.End5));
endSessionThread5.Name = "endThread5";

Thread[] threads = new Thread[10];
int[] ordering = new int[10];
try
{
for (int i = 0; i < 10; ++i)
{
ordering = -1;
}

for (int i = 0; i < 10; ++i)
{
int count = 0;
while (count < 25)
{
int j = r.Next(10);
bool duplicate = false;
for (int k = 0; k < ordering.Length; k++)
{
if (ordering[k] == j)
{
duplicate = true;
break;
}
}
if (!duplicate)
{
ordering = j;
break;
}
}
if (ordering < 0)
{
throw new Exception("error in setting up test");
}
}

threads[ordering[0]] = startSessionThread;
threads[ordering[1]] = endSessionThread;
threads[ordering[2]] = endSessionThread2;
threads[ordering[3]] = startSessionThread2;
threads[ordering[4]] = endSessionThread3;
threads[ordering[5]] = startSessionThread3;
threads[ordering[6]] = endSessionThread4;
threads[ordering[7]] = startSessionThread4;
threads[ordering[8]] = endSessionThread5;
threads[ordering[9]] = startSessionThread5;

for (int i = 0; i < 10; ++i)
{
threads.Start();
}

//
// 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();
startSessionThread4.Join();
endSessionThread4.Join();
startSessionThread5.Join();
endSessionThread5.Join();
Console.WriteLine();
Console.WriteLine(" --------------- repeating test ---------------");
}
}

Console.Write("Main is finished. (Max time = " +
maxTime.TotalMilliseconds + ")");
Console.Read();
}

#region ThreadStart Delegates
public void Start()
{
StartSession("Betty", 41);
}
public void Start2()
{
StartSession("Boop", 41);
}
public void Start3()
{
StartSession("Spunky", 41);
}
public void Start4()
{
StartSession("Grampy", 41);
}
public void Start5()
{
StartSession("Freddy", 41);
}

public void End()
{
EndSession("Betty", 41);
}
public void End2()
{
EndSession("Boop", 41);
}
public void End3()
{
EndSession("Spunky", 41);
}
public void End4()
{
EndSession("Grampy", 41);
}
public void End5()
{
EndSession("Freddy", 41);
}
#endregion

}//class SessionStarter

}//namespace
 
Hi John,
What should I do when posting code so that the formatting will be
better? How do you post code?

I use Outlook Express, so what I have to do is copy the code from VS.NET,
paste it into Notepad, then copy it from Notepad and paste it into Outlook
Express.

Not exactly excessive integration going on here...

John Saunders
 
Back
Top