Before
we proceed to the detailed design of the service, I must confess I screwed up
in the initial versions. I underestimate how difficult it is to code this up. On
one hand, there isn’t much logic at all, this is not graph matching algorithm,
or assembly hacking techniques. On the other hand, there are many threads, and
at any time there are incomplete messages, and error conditions. Complexity
grows exponentially when all these get coupled together. We must decouple them.
My
first revision involves re-thinking about concurrency. At its core our concurrency
problem is about shared mutable states, and if we can be sure no such thing
existed, we can freely code without concerning about concurrent edits.
Think
about the human world, everyone thinks at the same time, that’s okay because
brains are not shared, they don’t interfere. When a group of people work
together, they talks and listen, and that message is shared. However, it is
immutable and is therefore also safe. We can model our computation as such. Actors
are objects that receives message, access it only by reading it, and can freely
modify its own state. They can also send messages, but they never reach each
other directly without going through messages. That way we can guarantee the no
shared mutable state guarantee.
In
typical actor system implementation – such as Akka, these constraints (e.g.
always send message, don’t call methods, messages are immutable) are enforced
by the framework though various means (e.g. encapsulating the actor object
instances, staying immutable in a functional language, serializing the
message), but in mine, it is only the spirit that is important. I enforced
these constraint simply by following them myself, this is done so because they
is no need to invest in a framework, and in general that is more performant.
Each
actor is simply modeled as a concurrent safe queue of messages called a
mailbox, each actor override the OnReceiveMessage() method to respond to the
message object, make sure it don’t modify the message object, and that’s it.
When a message is sent to an actor, if the actor is not already processing a
message, a thread is requested from the ThreadPool and will call the
OnReceiveMessage() on it.
Last
but not least, an actor can voluntarily terminate itself, which is done by
returning ActorContinuation.Done in the OnReceiveMessage call, otherwise the
actor should return ActorContinuation.BlockOnReceive to get itself ready to
process another message.
With
this programming model, I can focus on the next level without worrying about
concurrency now.