Why Consider this Abstraction?

The OTP/Erlang concurrent programming model provides a more principled approach to structuring concurrent applications while still providing a high degree of flexibility. It details a low level, flexible definition of concurrent programming through "processes", and provides the tools to build a tree-structured, concurrent, fault-tolerant application from this primitive. By creating a semantic separation between the generic and specific definitions of a what an individual process is, it provides a framework for building reusable logic. This helps to solve one of the main pain points with our use of the builtin Async concurrent programming primitives; that is, we can easily express tightly defined, simple, high level abstractions that we decorate with specifications in order to build our application. The tree-structuring approach to modeling an application helps greatly with designing fault tolerance systems as it puts fault recovery at the forefront of a developers mind while building subsystems with this abstraction. The tree-structuring approach also greatly increases the debuggability and analyzability of the application, giving us unified layers where we can stick our debugging and visualization logic. The OTP/Erlang model has proven to be a very effective and useful concurrent programming abstraction.

Description

Much of the information here is distilled from the erlang documentation (some of it nearly word for word). If you would rather take a look at this information directly, take a look at these links:

Copy of OTP/Erlang Documentation

Processes

A "process" is a thread of execution which follows a certain set of properties. Firstly, a process shares no data (state) with other processes. Every process has an input mailbox, which is a essentially a queue of messages. A process can request to receive messages, which will cause the process to block and read messages from the queue until a message it is interested in is found. A process terminates when it's main function exits. Processes can be spawned, starting the execution of the process' main function and returning a PID to the process. Processes can be registered to a name for debugging purposes. Processes can exit execution early with an error. Processes can also be linked/unlinked from one another, which determines notification of errors, processes to handle errors from each other. Processes can also be monitored, which provides a stream of events regarding changes in the processes state (this is a more heavyweight alternative to process links).

Mailbox Semantics

At a basic level, mailboxes are queues of unbound length. However, unlike a traditional queue model (like how Async.Pipe is intended to be used), items can be taken from the middle of the queue. In erlang, receiving a message in a process has the following behavior: if there are no messages in the mailbox, block and wait for messages, but while there are messages, read messages from the front of the queue until you find a message- that matches one of the receive patterns requested, removing an item from the queue and continuing execution of the current process if a match is found. Next time the process blocks, if there are new patterns present in the receive statement, then the older messages in the mailbox that were skipped will be checked against those cases again. Otherwise, erlang knows how to skip checking messages that have already been checked for all cases you are matching against. The last bit of behavior is implementable in OCaml, but it would require creating a type level separation between the pattern matching function checking items in the queue and where the receive is actually called from.

At first glance, mailboxes can seem like they are just pipes all over again, but there is a key difference here: if you want queue semantics, then you just write a receive pattern that accepts all messages. The use case for looking further into a mailbox is for other messages is very rare, and is opt in instead of opt-out. In fact, in our implementation, I would argue the default receive should just always read a message, and another function receive_selected could be called if you need those semantics. In general, logic for patterns around mailbox processing will be handled by behaviors and not specific process definitions (see section below).

Process Behaviors

Processes are a generalized concurrent thread model. We categorize various processes based on their behaviors. In Erlang, behaviors are actually built into the language, and the way they work is fairly akin to the concept of functors. Therefore, behaviors in our code would just be a functor that returns a process.

More specifically, a behavior describes some generalized logic for a category of process, and a "callback handler" is implemented for specific instantiations of a behavior in order to provide the specific logic for that category of process.

Erlang/OTP comes with a few builtin behaviors for describing processes: gen_server (client-server based process), gen_statem (state machine processes), gen_event (event handling process), and supervisor (see supervision trees section).

Supervision Trees

A supervison tree is a structuring model for "worker" and "supervisor" processes. A worker process is a process that performs some computation, and a supervisor process monitors workers as they run. Supervisors are nested in a tree structure, and each supervisor has some specified behaviors around how it should handle various states of its children. This design pattern is very useful for building fault-tolerant concurrent programs. Each supervisor can have its own rules for how it will handle various errors, allowing it to make decisions like whether to respawn a worker, to ignore the error, or perhaps to exit and notify the parent supervisor of an error.

gIn the diagram below (pulled from -the Erlang docs) shows an example supervisor tree setup. The squares are supervisors, circles are workers, and edges are monitoring links.

http://erlang.org/doc/design_principles/sup6.gif

Monitors

Monitors are the primary tool for getting rich events for state changes in processes. There are a lot of things that monitors can notify supervising processes of, so rather than lay them all out here, I will just link to the documentation so that you can get an idea of the scope of what it means to monitor a process. http://erlang.org/doc/man/erlang.html#monitor-2