Wednesday, January 27, 2010

Direct 3D11 Engine

The last few weeks I've been playing with the thought of starting a new rendering engine based on D3D11 and the SlimDX library. I even started porting some parts of my XNA based engine: TXnaGE!

So how does that fit into the context of Borg? Well a game engine or (rendering engine) doesn't quite fit in the Borg philosophy: 1. Borg is completely client agnostic, 2. Borg is all about the server side  framework and simple client side connection oriented helper functions. But ... the main problem is that people stumbling upon Borg want to see something. You might be surprised that even in the field of software engineering "Seeing is believing" still holds true but that's the way it is.

Now, a few "seeing" related problems arise immediately. First you can't exactly show or see a server framework unless you are ready to download it and dig through the source. Next, Borg is inherently tied to game development and games are all about seeing stuff getting rendered, in such a context not having a visual representation is a pretty bad thing. At last but not least, the developers evaluating Borg must have a quick start path where they can relatively easy and fast get something done - that "something" is again a rendered scene representing a part of the virtual Borg World.

While the Borg Server project is still client agnostic we need to provide some sort support (at least through tutorials) to game developers. Therefore my orientation towards the rendering area. Actually on the Borg Development roadmap is even a fully blown MMORPG game, so we need a game engine anyway.

Why not pick up something already implemented, an working open source engine? It might sound strange but there are not so many out there. Most of them are either not really open source and the licensing limitation are unacceptable. A great deal of them are without officially supported .NET language bindings. The rest of them (well actually only three engines that could possibly qualify) are either not actively developed or have IMHO design and implementation issues. I know that the folks participating in those engines wouldn't agree with me , but the reader should note that I am personally active in the field of game engine development for at least a decade and therefore at least to some degree qualified for such conclusions. of course I might be completely wrong but that was just my personal opinion.

Nevertheless I decided to start working on a new D3D11 engine and once it's working do some initial tutorials and examples for Borg. If this is going to be the official Borg client engine will decide the Borg community.

In my next blog I will share a few problems with D3D11 development so stay tuned...

Sunday, January 24, 2010

We are the IEnumerable, resistance is futile

In my last blog: cache-synchronization-problem I talked about the genesis of our cache problems. This type of problems is inherent to using IEnumerable in multithreaded scenarios.

Nevertheless we had the multithreaded constrain and our MPT (MasterPlayerTable) had to expose its internal MPR (MasterPlayerRecord) collection to all consumers in a concurrency friendly and thread safe manner. My first thoughts where heading in a lock-free implementation. While this sounds great (although "lock-free" is becoming more and more a buzzword) there are some undeniable drawbacks:
  1. It is extraordinary hard to implement a really working lock-free algorithm
  2. It is even harder to debug problems coupled with lock-free implementations
  3. I was not aware of any existing lock-free implementation of a data structure with an IEnumerable notion
A bit research on that topic didn't bring any results. The best of lock-free structures I was aware of is a single writer multiple reader stack/queue implementation and that was not nearly good enough. Well even if it was I honestly doubt that I would  use it for something as complex as the Borg Server.


It was time for good old paper & pen problem analysis. The analysis confirmed it was absolutely mandatory to let one thread add and multiple threads remove items from the MPT - most of the others needed  a read only access just iterating the records.
I thought about implementing the synchronization inside Add() and Remove() with a ReaderWriterLockSlim lock which would fit nicely in the single writer/multiple readers scenario. This would be a fairly easy change except that it did nothing to eliminate the biggest problem of most other threads which where iterating the MPT records.
To summarize if you lock the collection during add or remove you have to lock it during enumeration as well. Failing to do so causes exceptions in threads enumerating the collection while it is changed. Locking the collection during enumeration causes to high thread contention. This was a lose lose situation.

A man's got to do, what a man's got to do. The IEnumerable had to go! It had to be replaced with something organized into a collection of records, that could be iterated over without locks and wouldn't throw exception if individual records are removed or added.
And before I forget to mention, a custom IEnumerable implementation which would provide separate copies of the underlying enumerator would work. The threads iterating the collection wouldn't lock on it but producing constant copies of the collection (enumerator) would be an overkill in such a heavy threaded environment.

The solution was a linked list. Organizing all the records in a linked list provided similar capabilities as a collection has. Of course the consumers couldn't use the foreach anymore but using something like:
                LinkedNode node = context.Mpt.First;
                while (node != null)
                {

                    Character c = (Character)node.Value.pCharacter;
                    // do something

                    node = node.Next;
                }

 
So how did that produce thread safety, if a new element was added that would leave any thread iterating through the list in a inconsistent state? Well yes and no. First this solution is not protecting against iterating over an removed node not even against missing a newly added node. But ... it is thread safe: a thread adding a new node couldn't leave the other thread with an inconsistent pointer. That means even if the thread fetching the next node in the list is preempted and another thread changes the next pointer due to adding a new node it can not be preempted back in the middle of a reference assignment leaving the 1st thread with a garbage pointer. Reference assignments are atomic in nature.

The most dangerous place is the Add() inside the LinkedList implementation. And before you ask, yes it was necessary to implement a custom list since the .NET LinkedNodeList would reset the nodes previous and next pointers after removing the node form the list. So if a node in the middle was removed any threads currently using the very same node would lose the connection to the next node in the list.

The LinkedList.Add() looks like:

        ///
        /// Adds a new node to the list.
        ///
        ///
        public LinkedNode Add(T item)
        {
            LinkedNode previous = null;
            LinkedNode node = new LinkedNode(item, null);
            lock (locker)
            {
                if (nodes.Count > 0)
                {
                    previous = nodes[nodes.Count - 1]; //  this is the last node in the list
                    previous.Next = node;
                }
                node.Previous = previous;
                nodes.Add(node);
            }
            return node;
        }


The hot spot is the line containing previous.Next = node; and nodes.Add(node); where one could argue that the whole solution would fail if thread 1 assigns the new node to previous.next, thread 2  fetches the next node before thread 1 updates the new node previous and next properties.

While the reasoning is 100% correct the solution still works:
1. No one is iterating backwards - this  functionality is not exposed. So obtaining a node with invalid previous pointer during iteration is not hurting anyone.
2. Multiple threads adding new nodes (which is not happening) are protected by the lock, the same is true for simultaneously adding and removing nodes
3. The most dangerous situation is that one thread obtains a new node (the line previous.next = node; is executed) before the node is added to the list. We are on the safe side here as well. Our implementation is only adding new nodes at the end of the list, so all threads obtaining the reference to the new node having null in the Next field work as expected since the Next field simply must be null as it is the last element in the list.

Note that the above solution works only due to insights in other other Borg parts consuming those features. It is no way generic and works only due the fact that the list is a) unsorted, b) iterated forward only and c) new nodes are added to the end.

After the implementation was finished testing showed that all the thread concurrency issues where solved and our MPT was lean and mean as one could only hope for.


Friday, January 22, 2010

Borg Cache Synchronization Problem

The other day just another Borg milestone was about to be reached. It was mostly about refactoring and removing all necessary player, client, character caches stored inside many different Borg components either as dictionaries or lists.

The main problem was data synchronization. If a client disconnects the Network Server can detect that easily and clean up its internal client cache, unfortunately there are many other caches to be cleaned up as well: player cache of the client state manager, playing character cache owned by the world server etc.

After the system has grown and new components where added keeping all that data in sync become a major pain. It was very easy to forget notifying a component about a change and the state got inconsistent which was not quite obvious until an error showed up. Even worse was the fact that adding a cache to a new component would require passing the components reference or interface to all other parts so they can communicate back any changes.

To tackle that problem we decided to do what we should have done at the first place: a central cache accessible from all interested parties. I added a MasterPlayerTable object which was basically a collection of MasterPlayerRecords and a bunch of dictionaries allowing look up per accountId, characterId, IP address etc. Since the Borg host process creates a ServerContext instance passed to all components I just added the MPT (MasterPlayerTable) to the context and everyone was happy.

Well not quite, since now all components where reading/adding/deleting records stored in the MPT it become a thread contention bottleneck. The act of adding and removing records was not a problem - only one single component can actually add new records after a new player connects. Not so many can (or have to) remove records from the MPT. The problem was the nature of cache usage inside Borg. Most parts have to do an enumeration like:
foreach (MPR playerRecord in MPT)
{
       // do something
}

which obviously is not yet a problem. But the parts of Borg which are removing some records (disconnected players, log-outs, bans, kicks, invalid protocol etc) couldn't just remove a record from the MPT since the enumerator would become invalid and all other threads which happen to be inside a foreach would get the "Collection was modified after the enumerator was instantiated" exception.

The classic approach to such problems:
1. store all invalid items in a removal list,
2. decide when to do a cleanup, lock the whole collection and remove all items found in the removal list,
3. clear the removal list and unlock the collection

Well...this was a disaster I had the misfortune to actually implement and test myself. In order for this to work all parts/threads accessing the MPT must acquire exclusive access (lock). There are many situations you need the MPT especially now that the local cache copies where gone. The TurnManager just to mention one is the main consumer executing all the player actions per turn in a foreach manner. It must lock the whole MPT for the most of its execution time. The client state manager must at least once per turn do a cleanup and  acquire the MPT for itself. Note that a server turn executes many times per second. All the other helper threads where starved to death waiting on the lock!

The problem escalated to a magnitude that it was obvious what's happening just from the console output without any need to run the concurrency profiler.

I had to do something and  I had to do it soon...the story will continue in my next post