Gaffer Docs

Message Passing

As an alternative to using OS threads, a runtime can create lightweight processes which communicate with each other through messages.

Note: This pattern is sometimes called the Actor Model, but I won't use that name here, since Gaffer uses the term "Actor" for an entirely different construct.

Erlang is a functional language developed as a platform for highly concurrent telephone switching systems. It employs a runtime that implements lightweight processes and message passing. It serves as an excellent case study for both functional programming and the message passing approach to concurrency. Gaffer's concurrency model borrows several of its ideas, so I will describe some of its details here to serve as background.

I will only scratch the surface of Erlang, but it is a fascinating language that is very applicable to certain types of problems (namely highly concurrent, robust, IO bound server solutions). To learn more, I recommend Erlang and OTP in Action by Logan et al.

Erlang Runtime

Concurrent Erlang programs are composed of "Processes", which are lightweight units of execution that run within the Erlang runtime. Each Process has its own heap, and there is no sharing of data between Processes.

The language provides a very concise syntax for passing messages between Processes. Each message is stored in a queue. A Process function typically processes a message with the "receive" statement, and then makes a tail-recursive call to itself to wait for the next message. This is effictively an infinite loop until some condition is met.

Here is a very simple Erlang process function to serve as an example.

procfunc() ->           %% declare a function called procfunc
  receive               %% waits for a message, then matches with patterns below  
    {From, Msg} ->      %% matches a tuple message with two members
      ...               %% perform some processing of the message
      procfunc();       %% tail-recurse to process next message
    stop ->             %% matches the stop message
      true              %% if we receive stop message, return thus ending our process
  end.

Every process has a unique pid others can use to send messages to it. Since there is no shared data, there is no locking required. The state data of a Process is sent as arguments to the Process function when it is started, or as message data once the Process is running. The data is effectively copied. In the cases of lists, a built in type of Erlang, the data isn't copied, but since lists are singly linked and can only be modified by adding to the head, no process can trample on list data that may be being used by other processes.

It turns out that this model works extremely well for server applications that have a high number of concurrent clients that aren't interacting with each other. For example, if you were to implement a chat server in C++, you might create a few thread pools to handle different parts of message parsing, sending, etc. You might partition work into user connections and have class instances for each connection.

In Erlang, the correct design for a chat server is completely different. You'd create a separate Process for every message that arrives. Creation and teardown of Processes is very cheap. You might have hundreds of thousands of simultaneous messages, but the Erlang runtime is horizontally scalable, and can handle this very efficiently.

Erlang and Games

Although there are some great strengths of Erlang, it is not a good language for game development. Games have too much shared state data, and moving it around through processes becomes inefficient.

In addition, computationally intensive activities are considerably slower in Erlang than in C++.

Some online game architectures can benefit from it in the server space, but in terms of game clients and servers that are managing game state, user inputs, AI, graphics, etc., it doesn't fit.

However, certain aspects of the Erlang runtime design are borrowed for Gaffer and used to great effect.