Friday, March 20, 2009

PrismServer Update

PrismServer is the TCP/IP server I developed to handle communication between players in my MultiPlayer games. The newest version is written in C# and offered as open-source. It features a set of components that abstract the client/server communication protocol and exposes it through simple properties, events and methods.

When I first switched from the Delphi programming language to C#, PrismServer was the first serious development effort I undertook. I always like to have a concrete project to work through when I learn a new language.

Looking back at my original effort, I discovered some flaws in my work that experience with C# helped me recognize now. These flaws manifested themselves as sporadic crashes in the server piece of the PrismServer package.

I have spent some time over the past few weeks nailing down and correcting these problems. The problems revolved mostly around multi-threading issues. I was not properly locking variables that could be access from different thread. The problems most frequently occurred when using a foreach loop, and then having the List<> that the loop was enumerating change by a call from another thread.

For example, when PrismServer is trying to send information to all of the players in a specific room, it uses a foreach to enumerate the players in the room. If a player entered or exited a room during this loop, PrismServer.exe would crash hard.

After identifying all of the appropriate places to put lock statements, another problem manifested. When a player would drop from the game, there would be noticable lag of about 30 seconds that stalled all traffic in the server. This sounded like a locking issue; i.e. one method waiting for a lock to be released that was set in another method.

Sure enough, I discovered that the method that writes information to the players' sockets was locking the list of connected Guests in that subject. When one of the players dropped, this caused a delay as the socket timed out, and the lock was still set until the socket timeout completed. This caused a bad cascade that eventually brought the whole server to its knees until the socket timeout completed.

I corrected this by creating a new thread to handle outgoing communication. When PrismServer wants to send something out on a socket, it now adds the string to a Queue. The outgoing thread examines this Queue and sends the strings it finds there out the client socket. The Queue is locked only long enough to get the next piece of information to be sent. This design allows the server to run smoothly even when clients are timing out.

After stress testing, I'm happy with the performance and stability of PrismServer, and look forward to seeing how long the new build can run before it needs to be restarted.

Below is the relevant section of code that resulted in the most bang for the buck improvement (from PrismNetworkStream.cs)



//This thread keeps executing and writing information to the client
private void WriteThreadExecute()
{
while (!_closed)
{
bool written = true;
while (written)
{
written = false;
string s = null;
lock (_outgoing)
{
if (_outgoing.Count > 0)
s = _outgoing.Dequeue();
}
if (s != null)
{
written = true;
WriteString(s);
}
}
Thread.Sleep(10);
}
}

No comments:

Post a Comment