[Functional Assets Required to Make Highly Available Nontrivial Distributed Systems] 


Methodology, components & tools > Design patterns > Basic server
 

A server is an abstraction found in the vast majority of concurrent and distributed applications. A server performs iteratively as follows:

  1. Receives a request from a client.
  2. It computes a response based on information from the request and the server's internal state.
  3. The response is sent to the client.
  4. The server state is updated for subsequent iteration.

A simple representation of this model is a definition of tail recursion which explicitly provides the state of the server as a parameter. For instance, the following code defines the behavior of a memory allocator server.

-module(allocator). -export([loop/1]). loop([loop/1]) -> receive {request, From, {alloc, N}} -> From ! {reply, HeapPointer}, loop(HeapPointer) end.

Here, a request is represented as a tuple of 3 elements {request, From, {alloc, N}}, where request is an atom that identifies the client's request, From is the identity of the client process to receive the response, and {alloc, N} is the current request. The expression spawn(allocator, loop, [16]) creates a server with the initial state {HeapPointer = 16}. The client API is defined as:

call(Server, Request) -> Server ! {request, self(), Request} receive {reply, Reply} -> Reply end.

where Server is the server process identity and self/0 is a built-in primitive that returns the current process Pid. If PidS is linked to the server PID and its state is 100, then call(PidS, {alloc, 10}} returns 100 changing the state of the server to 110.


Fig. 1. Server Behaviour

High-order functions can be defined to generalize this basic server pattern, to avoid repetition of the same structure in different places. As shown in Figure 1, the function Behavior: Request X State -> State X Response is used to model the computation of the response, and the state for the next iteration from the request and the current state. While the Behavior is a function without side effects, the high-order recursive definition loop deals with client-server interactions. As shown, the resolution of requests and state changes on the server are serialized.

-module(server). -export([start/2, loop/2]). start(Behaviour, State) -> spawn(server, loop, [Behaviour, State]). loop(Behaviour, State) -> receive {request, From, Request} -> {Response, NewState} = Behaviour(Request, State), From ! {reply, Response}, loop(Behaviour, NewState) end.

This abstraction can be used to build the original memory allocator server. In this case, an anonymous function is used to model the server behavior:

Allocator = server:start(fun ({alloc, N}, HeapPointer) -> {HeapPointer, HeapPointer+N} end, 16)