heavy use of behaviours—direct use of
the raw message-sending or receiving
expressions is uncommon. In the
Ericsson AXD301 telecom switch—
the largest known Erlang project,
with more than a million lines of
code—nearly all the application
code uses standard behaviours, a
majority of which are the server behaviour. 1
Erlang’s OTP standard library provides three main behaviours:
Generic server (gen_server). The
generic server is the most common
behaviour. It abstracts the standard
request-response message pattern
used in client-server or remote procedure call protocols in distributed
computing. It provides sophisticated
functionality beyond our simple server
module:
˲ Responses can be delayed by the
server or delegated to another process.
˲ Calls have optional timeouts.
˲ The client monitors the server so
that it receives immediate notification
of a server failure instead of waiting for
a timeout.
Generic finite state machine (gen_
fsm). Many concurrent algorithms are
specified in terms of a finite state machine model. The OTP library provides
a convenient behaviour for this pattern.
The message protocol that it obeys
provides for clients to signal events to
the state machine, possibly waiting for
a synchronous reply. The application-specific callbacks handle these events,
receiving the current state and passing
a new state as a return value.
Generic event handler (gen_event).
An event manager is a process that receives events as incoming messages,
then dispatches those events to an
arbitrary number of event handlers,
each of which has its own module of
callback functions and its own private
state. Handlers can be dynamically
added, changed, and deleted. Event
handlers run application code for
events, frequently selecting a subset
to take action upon and ignoring the
rest. This behaviour naturally models
logging, monitoring, and “pubsub”
systems. The OTP library provides off-the-shelf event handlers for spooling
events to files or to a remote process
or host.
The behaviour libraries provide
functionality for dynamic debugging
Large erlang
applications make
heavy use of
behaviours—direct
use of the raw
message-sending
or receiving
expressions is
uncommon.
of a running program. They can be
requested to display the current behaviour state, produce traces of messages received and sent, and provide
statistics. Having this functionality automatically available to all applications
gives Erlang programmers a profound
advantage in delivering production-quality systems.
Worker Processes
Erlang applications can implement
most of their functionality using
long-lived processes that naturally fit
a standard behaviour. Many applications, however, also need to create
concurrent activities on the fly, often
following a more ad-hoc protocol too
unusual or trivial to be captured in the
standard libraries.
Suppose we have a client that wants
to make multiple server calls in parallel. One approach is to send the server
protocol messages directly, shown
in Figure 4A. The client sends well-formed server call messages to all servers, then collects their replies. The
replies may arrive in the inbox in any
order, but collect_replies/1 will
gather them in the order of the original list. The client may block waiting
for the next reply even though other replies may be waiting. This doesn’t slow
things down, however, since the speed
of the overall operation is determined
by the slowest call.
To reimplement the protocol, we
had to break the abstraction that the
server behaviour offered. While this
was simple for our toy example, the
production-quality generic server in
the Erlang standard library is far more
involved. The setup for monitoring the
server processes and the calculations
for timeout management would make
this code run on for several pages, and
it would need to be rewritten if new
features were added to the standard
library.
Instead, we can reuse the existing behaviour code entirely by using
worker processes—short-lived, special-purpose processes that don’t execute a
standard behaviour. Using worker processes, this code becomes that shown
in Figure 4B.
We spawn a new worker process for
each call. Each makes the requested
call and then replies to the parent,
using its own pid as a tag. The parent