Process, the Concurrency construct, in Erlang

Madanasekaran

Erlang was designed for a telephone system where concurrency was a central concern. Concurrency is the ability for different tasks/functions to execute simultaneously/in parallel. Each concurrent activity in Erlang is called a process. Processes live in the Erlang Virtual machine and provide Concurrency. This is considered to an implementation of actor model but Erlang team and documentation calls this as process. Some of the characteristics of process are:

1) Mostly Short-lived : A process is dynamically created/ spawned from a function and executes the function and killed once the execution of the function is over. As the process has nothing left to do, the process and its context are marked for cleanup and are discarded. But if the function from which a process is spawned contains a “receive” statement and makes a recursive call, the process is waiting for the next message and can live even for years. See the “area_server.erl” in the samples and Figure-6 below.

2) Isolation:  The processes are self-contained. The context of each process is isolated from the contexts of other processes. Messages are required to share information between the processes. The Process isolation and the messages, as the only means of communication between the processes, give a clean separation between tasks;

3) Explicit Linking : The Erlang philosophy for building fault-tolerant software can be summed up in two phrases: “Let it crash.” and  “Let some other process fix the error”. There are functions to explicitly link Processes and this feature is used for effective error handling. We can create processes whose task is to start, monitor, and manage other/ children processes. These supervisor processes can spawn processes and link themselves to these processes. When something unexpected occurs in a process, the process sends an EXIT signal-a dying declaration -  to the supervisor and then only terminates. On receiving the EXIT signals, the supervisors can take an appropriate action like restarting the process. This results in fault tolerant architecture as shown in Figure-1 below

4) Location transparency: Another feature of processes is their transparent distribution. The transparent distribution/location transparency allows Erlang programmers to look at the network as simply a collection of resources—we don’t much care about whether process X is running on a different machine than process Y, because they are going to communicate exactly in the same way, no matter where they are located. Programmer can write his code in the usual Erlang way. The Scheduler in the VM (and OS) will take care of distributing the job on different cores. This enables full utilization the multi-core hardware that is available to us today.

5) Light-weight  Erlang Processes are not only independent but also lightweight. When discussing processes in the context of Erlang, we are referring to Erlang processes and not OS processes. There is a distinction between the two. OS processes are scheduled and controlled by, the operating system -the kernel. Erlang processes, on the other hand, are processes local to the Erlang VM (BEAM). ERTS is the kernel in this regard. It is in charge of the scheduling and management of these processes. An important distinction between these two is a question of weight. An OS process represents a running application. Within a process one or more threads are used to execute tasks. OS will allot Erlang VM a thread per core in the machine. Each of these threads runs a scheduler. As an Erlang Scheduler runs in an OS thread and the scheduler executes a number of Erlang processes, Erlang processes are extremely lightweight. In fact, it is not uncommon for a single Erlang VM to have many thousands to millions of processes running at once. Look at the figure-2 below:

Figure-2

Multi –threaded model of “C” languages uses one thread per connection. So when the number of connections exploded -“C10K” problem- they could not scale well. An executing Erlang application is an OS process. But VM has its own scheduler, which runs on OS thread, but can create and execute millions of light weight Erlang processes which can handle concurrent connections. As VM can have schedulers equal to the cores in the machine it taps the potential of multi-core systems. ``Erlang is the best option for soft Real time systems with low latency.

Erlang Programs:

Please see figure-3 below. An Erlang function should be coded in a module e.g. module1 which is placed in the file “module1.erl. The file has to be compiled. The Built-in-Function(BIF) spawn() is used to “ transform”  a function which is a sequential construct into a concurrency construct -“process”. To invoke spawn(),we can use the “Shell” process. Then a child Process will be spawned/created from the function and the spawned child process will execute the function. A process can send message to and receive message from other processes. A process lives in its separate memory space/heap till the execution of the function is over.

Erlang programs are built from a number of such parallel processes. Shell process is part of Erlang Run-time System (ERTS). ERTS consists of number of such independent processes. In the shell if you execute the function processes(),  you can find output as shown below, displaying the Process IDs of the various running processes. It is reiterated that these processes are part of Erlang Run-time System.

[<0.0.0>,<0.3.0>,<0.6.0>,<0.7.0>,<0.9.0>,<0.10.0>,<0.11.0>,
<0.12.0>,<0.13.0>,<0.14.0>,<0.15.0>,<0.16.0>,<0.17.0>,
<0.18.0>,<0.19.0>,<0.20.0>,<0.21.0>,<0.22.0>,<0.23.0>,
<0.24.0>,<0.25.0>,<0.26.0>,<0.27.0>,<0.28.0>,<0.32.0>]

Execute

Pid = self(). % self() BIF returns “pid” of current/shell  process, which is assigned variable Pid
<0.32.0> % this is the last item in the above list

We can send a message to shell process, as we send it to any other process.

Pid ! hello.
hello
Pid ! ganesh.
ganesh.
flush(). %  ---1)
Shell got hello.
Shell got ganesh.
  • To retrieve and display all the messages sent to the shell process, and therefore currently held in the process mailbox, you can use the shell command flush/0, which also has the effect of removing (or flushing) those messages from the mailbox:

To the command
<0.32.0> ! fine. % you can not directly use pids.
Shell will display
“ * 1 : syntax error before:  <”.
The Concurrency Primitives:

We will see some full-fledged programs using the essential concurrency primitives   1)  spawn() 2) send 3) receive ... end. We will use Intelli J Idea which has good support for Erlang through a plug-in. Once you choose Create New ProjectErlang,  the IDE will ask for Erlang SDK for the module. You can click “Configure” and Browse for your Erlang Installation folder. Give Project name as “Msg” and click “Finish”. Expand the folders the IDE. Right click on “src” and choose New -> Erlang File. You can give the name “pong” and Kind as “Empty module”. Click Ok. A skeleton pong.erl will be generated. It can be filed up as under:

pong.erl
-module(pong).
-author("admin").

%% API
-export([run/0,ping/0]). % module exports two functions
run() ->
  Pid = spawn(fun ping/0), % ----1)
  Pid ! self(), % ----2)
  receive
   
pong -> ok  %------3)
  end.
ping() ->
  receive
   
From -> From ! pong  %------4)
  end.
  • one variant of spawn() which takes a function as argument is  used to convert ping() function into a child process and its process id is obtained.
  • “!” means send. Self() returns the Pid of current/parent run() process. To the Pid of  Ping, the Pid of run is sent
  • When the message in the mail-box is matched to “pong”, the run process will print “ok”. The pattern to be matched is given in the head before  “->”. Only if the pattern matches, the body, the code after “->”, will execute.
  • In the ping, the message in the mail-box is pattern-matched to “From” - Pid of sender process. If the match succeeds, the ping process just sends “pong” reply message to the Pid of the sender.

 Compile the code and choose Run à Edit Configurations. You will see Figure-1.


Expand “+” and choose Erlang Console. You may see Figure-2.Accept “Unnamed”. Expand “Use code path and SDK of module” and see Msg and click on it.

Figure-5

Now that box will show “Msg” click Ok. Now you can “Run” the project.
The shell will appear and automatically change to the folder where you saved the module. You can execute the command
pong:run(). And see
ok
One more module:
You can create one more “Empty module”by name area_sever. Fill up the generated code as under:

area_server.erl

-module(area_server0).
-author("admin").

%% API
-export([loop/0]).
loop() ->
receive
{rectangle, Width, Ht} ->
io:format("Area of rectangle is ~p~n",[Width * Ht]),
loop(); % ----4)
{square, Side} ->
io:format("Area of square is ~p~n", [Side * Side]),
loop();
{triangle, Width, Ht} ->
io:format("Area of triangle  is ~p~n",[.5 * Width * Ht]),
loop()
end.

Compile the file and Run UnNamed.
Pid = spawn(area_server,loop,[]). % this version of spawn takes three arguments ---1)
<0.33.0> % ----2)
Pid ! {rectangle, 3, 15}.  % -----3)
Area of rectangle is 15 % server returns
{rectangle,3,5}   %--------3)
You can send another message from the shell as under:
Pid ! {square,6}.
Area of square is 36
{square,6}

  1. Three arguments are 1) module name 2) function name 3) array of parameters to the function.-recall that Erlang expects you to code a function only within a module.
  2. spawn(area_server0, loop, []) creates a new parallel process that evaluates the function area_server:loop(); it returns Pid, which is printed as <0.33.0>.In other words the input to BIF spawn() is a function and the output is a process whose id is returned by it.
  3. we send a message to the process id-Pid. This message matches the first pattern in the receive statement in loop/0:
  4. calling loop()(recursive call)  makes the process to wait for next message and prevents it from terminating. You can keep the Erlang process alive using a tail-recursive call to the function that contains the receive statement. We often call this function the receive/evaluate loop of the process. Its task is to receive a message, handle it, and then recursively call itself. It will stop only when it is asked to stop by a link/supervisor. See Figure-6 below:

Figure-6

To sum up when a spawn command is executed, the system creates a new/child process. Each process has an associated mailbox that is also created when the process is created. When you send a message to a process, the message is put into the mailbox of the process. The only time the mailbox is examined is when your program evaluates a receive statement.

Client-Server design pattern : Please see the figures below: 

In the earlier program sketch –I above, the shell process sends a request to area_server process which just calculates and prints the result. If the area_server process sends a reply to the process making the request it becomes a Client-Server model. The earlier Pong follows a Client-server model. In Erlang, server is just another light-weight process and not a heavy-weight software running on a specialized machine, as in other environments. The Client and server are just two processes that communicate through messaging and they can run on the same or separate machines. The process that makes a request is a Client and the process that sends a reply to the request is the Server

Error handling:

If you pass a wrong type of argument while invoking a function, the shell process will raise “exception error” and then exits. The ERTS will start another shell process. If the error occurs in a ‘spawned’ process, it is detected by another part of the Erlang runtime system called the error logger, which by default prints an ‘error in process report’ in the shell. The shell also prints its “exception exit”. Apart from these built-in error reporting facilities, the language also provides some primitives to enable the programmer to handle errors according to his needs, but within the over-all scheme of “Let it crash” and “Let some other process handle it”. To see them in action we will see some code.  Create one more Erlang Module “linkmon”.

-module(linkmon).
-author("admin").

%% API
-export([start/0, request/1, loop/0]).

start() ->
  register(linkmon, spawn_link(linkmon, loop, [])). %-----1)

request(Int) ->
  linkmon ! {request, self(), Int},
  receive
   
{result, Result} ->  Result
  after 1000       ->  timeout
  end.

loop() ->
  receive
   
{request, Pid, Msg} ->
      Pid ! {result, Msg * 2}
  end,
  loop().
  1. The process loop registered in the name “linkmon”(module’s name) was spawned and linked to start process. You can compile the file and Run “Unnamed”.
1> self().% ascertains the Pid of shell 
<0.49.0>
2> linkmon:start().
true
3> linkmon:request(4).
8
4> linkmon:request(four).
** exception exit: badarith
     in function  linkmon:loop/0 (e:/lunaSpace/ProcessLink/src/linkmon.erl, line 28)
=ERROR REPORT==== 28-Mar-2016::11:35:54 ===
Error in process <0.52.0> with exit value:
{badarith,[{linkmon,loop,0,[{file,"e:/lunaSpace/ProcessLink/src/linkmon.erl"},{line,28}]}]}
5> self().  % ----1)
<0.55.0>
6>  
  1. On receiving the wrong type of parameter the process ‘loop’ registered in the name “linkmon” dies after sending an error-signal. The start linked to it

also dies and also the shell process. You can see self() now returns a new process id

How abnormal termination can be handled?.

If a linked process terminates, it sends an exit message to another linked process before exiting. The linked process which received the EXIT message sends the Exit message with the reason received with its Pid to another linked process and then exits. The propagation goes on till all the processes in the link set are terminated.This behavior is useful when a group of processes carries out a single “transaction”. For other cases the behavior can be changed and “termination” can be restricted. One way of doing this is to make process in the link set, a system/supervisor process. System/supervisor processes are basically normal processes which can trap exit signals by setting the process flag trap_exit. When the function process_flag(trap_exit, true)  is called, exit signals are converted into to regular messages of the format {'EXIT', Pid, Reason} and queued in the mail-box. Once an EXIT signal is trapped in a system/supervisor process, there is no further propagation and only some process in the set that received the EXIT signal before it was trapped by system/supervisor process will die. The system/supervisor process can take an appropriate action like restarting the processes that got terminated abnormally.  Another way of stopping the propagation is by using monitor in place of link. Link is bi-directional, that is, both the linked process can observe one another. Monitor is unidirectional. If A monitors B and B dies, then A will be sent a DOWN message but not the other way around. In links the death of either process would result in the other process being informed.

Look at the following program:

module(link_pro).
-author("admin").

%% API
-export([start/0, request/1, loop/0]).

start() ->
  process_flag(trap_exit, true), % will trap exit signal ---1)
  Pid = spawn_link(link_pro, loop, []),
  register(link_pro, Pid),
  {ok, Pid}.

request(Int) ->
  link_pro ! {request, self(), Int},
  receive
   
{result, Result}       -> Result;
    {'EXIT', _Pid, Reason} -> {error, Reason}
  after 1000             -> timeout
  end.


loop() ->
  receive
   
{request, Pid, Msg} ->
      Pid ! {result, Msg * 2}
  end,
  loop().
  1. Except this function the code is the same as in the earlier sample. You can compile this file and Run “UnNamed”

1> self().
<0.31.0>
2> link_pro:start().
{ok,<0.34.0>}
3> link_pro:request(5).
10
4> link_pro:request(five).
{error,{badarith,[{link_pro,loop,0,
[{file,"e:/lunaSpace/ProcessLink/src/link_pro.erl"},
{line,33}]}]}}
5>
=ERROR REPORT==== 28-Mar-2016::11:28:04 ===
Error in process <0.34.0> with exit value: {badarith,[{link_pro,loop,0,[{file,"e:/lunaSpace/ProcessLink/src/link_pro.erl"},{line,33}]}]}

5> self(). 
<0.31.0>

6>
  1. Error message stops with “loop”. Here further propagation of exit messages is stopped; we can program in the system process about the further action to be taken. As mentioned earlier, The philosophy of Erlang is “Let it crash” .That is, let the problematic process die at the earliest and leave the job of handling the error to another process. Concurrency and Fault-tolerance and high availability are among the reasons for the popularity of Erlang.

We saw only some aspects of fault-tolerance in Erlang. One has to carefully plan to avoid race conditions and deadlock when using this feature. A better alternative is to use OTP behaviors which provide fault-tolerance out of box without the above pitfalls.

Garbage collection

As a Process is created /spawned when a function is evaluated, it is terminated when the execution of the function is over and the memory heap of the process also dies. Only loop() process in area_server module is not terminated, as it waits for the next message and the execution of the function is not over. As Erlang is a functional language, the functions are mostly compact and small and the processes are transient and short-lived. No GC occurs in a short-lived process which doesn’t use heap more that min_heap_size and then terminates. This way the whole memory used by process is collected. .In other words, heaps of the processes that were created and died between two runs of the garbage collector need not be garbage collected at all, as those heaps lived and died with the processes themselves. Process do not share memory, and communicate with each other only through message passing. Messages are copied from the stack of the sending process to the heap of the receiving one. This results in efficiency in reclaiming memory. As processes execute concurrently in separate memory heaps, these memory- heaps die or can be garbage collected separately. This helps in implementing Soft Real-time systems with a high level of responsiveness, even under sustained heavy loadsErlang uses a “generational garbage collector” which helps the GC to reduce its unnecessary cycles over the data which have not become garbage yet. All of this together means that the Erlang GC is doing much, much less work relative to GCs in other languages/platforms and minimizes latency.

Even when exceptions occur and a process fails, because there is no shared memory, failure can often be isolated as the process was working on a stand-alone task. This allows other processes working on unrelated or unaffected tasks to continue executing and the program as a whole to recover on its own. Thus independent light weight process helps in an efficient garbage collection scheme and isolation of errors and easy error-recovery.

Conclusion:

Now we’ve built a simple client-server module. All we needed were the three primitives, spawn, send, and receive. That’s enough for a process communication. Every call to spawn yields a fresh process identifier that uniquely identifies the new child process. This process identifier can then be used to send messages to the child. Each process has a “process mailbox” where incoming messages are stored as they arrive, regardless of what the process is currently busy doing, and are kept there until it decides to look for messages. The process may then search and retrieve messages from this mailbox at its convenience using a receive expression, as shown in the example. Normally processes are independent of one another and cannot corrupt one another because they do not share anything. This is concurrency. There is another feature fault tolerance, which enables you to explicitly link a process to another process or use a monitor process to supervise a few processes for error-recovery. But using this feature properly requires a lot of careful design from the programmer. Though Erlang, by doing away with shared data, reduces scope for race conditions and deadlock, it is for the programmer to design for completely avoiding them .But if he chooses OTP behaviors instead of raw Erlang Processes his botheration is over. OTP supervisor behavior and application behavior can provide the services provided by supervisor process, links and monitors but without the extra care required from the programmer while designing. OTP abstracts common use-patterns and puts them in carefully engineered and well-tested components. In the words of Joe Anderson the chief creator of Erlang “The power of OTP comes from the fact that properties such as fault tolerance, scalability, dynamic-code upgrade, and so on, can be provided by the behavior itself”.

Behaviors are ready-to use framework-supplied components that have implemented some known design patterns. The  gen-server behavior implements client –server design pattern; for supervision there is supervisor behavior; for Finite state machine(FSM) gen-fsm ,for event-handling gen-event and for packaging the related modules Application -all  are available as behaviors. We will start using the OTP behaviors from the next article.

 








}