Erlang (programming language)/Tutorials/Processes

From Citizendium
Jump to navigation Jump to search

Erlang Processes and Messages

Processes are easy to create and control in erlang. The program chain_hello.erl builds a chain of processes as long as you like. Each process creates one process then sends a message to it. The program creates a chain of N processes which each print out hello world! N. Processes send messages and receive messages from one another. Messages are read with pattern matching. The messages are matched in a fifo(first in, first out) way.

Note 1: The order of the final output depends on process scheduling.

Note 2: Time flows downward(in each vertical line for each process, see note 1).


This is a minimal UML sequence diagram showing the processes and messages for the execution of:

chain_hello:start(1). 
UML sequence notation: Processes start in boxes. Dotted lines are life lines. Processes time lines end in X's.  
Note: Some of the details of have been left out for tutorial purposes. 
      The diagram has English mixed with code.
Local notation: Command line output is in quotes. Messages are in curly braces. 
+——————————+                  
| start(1) | 
+——————————+
   ¦              +———————————+
spawns  ———————>  | listen(1) |
   ¦              +———————————+
   ¦                    ¦                      +———————————+
   ¦                  spawns ————————————————> | listen(0) |
   ¦                    ¦                      +———————————+
   ¦                    ¦                          ¦
sends  —> {speak} —> prints  —> "hello world 1"    ¦
   ¦                    ¦                          ¦
   ¦                  sends ——> {speak} ———————> prints  ———> "hello world 0"
   ¦                    ¦                          ¦
   X                    X                          X

Program listing for: chain_hello.erl

-module(chain_hello). 
-compile(export_all).
                                                            %
start(N)->                                                  % startup
       Pid1 = spawn(chain_hello, listen, [N]),
       Pid1 ! speak,
       io:format("done \n").
                                                            %
listen(0)->                                                 % base case
       receive
                speak ->
                       io:format("hello world!~w\n", [0])
       end;
listen(N)->                                                 % recursive case
       Pid2= spawn(chain_hello, listen, [N-1]),
       Pid2! speak,
       receive
               speak->
                       io:format("hello world!~w\n", [N])
       end.
% ---- sample output for chain_hello:start(1) --- %
%
% 14> chain_hello:start(1).
% done
% okhello world!1
% hello world!0
%
% ---- sample output for chain_hello:start(4) --- %
%
% 14> chain_hello:start(4).
% done
% hello world!4
% hello world!3
% hello world!2
% okhello world!1
% hello world!0

Intermediate message passing

Now, suppose we have some commands in the form of a message. We wish to send this command message to a large set of processes. We might like answers from those processes that are able to respond. If some crash because of the message then they should be restarted.

% ===========================================================================
-module(freeze_clone).
-compile(export_all). 
% The purpose of freeze_clone is to non-destructively test a process with a 
% message. Each process is a state machine. 
% If the process receives any unknown message then it should self destruct,
% (Because we are following the rule: to fail early and often). 
% Each msg_testable process requires the following message handlers: 
% {freeze, clone, get_value, set_value, exit}.
%
%   Steps to test a message on a process:
%     0) setup: spawn a process to listen to messages 
%     1) freeze process in question so it is safe to clone
%     2) we clone the process 
%     3) send the clone a test message 
%     4) unfreeze the clone
%     5) see if clone lived after the message,
%        if the clone is happy/alive then the message should be safe 
%     6) kill clone if clone is alive
%     7) send message to original or 
%        pick another safer message if msg failed on clone
%     8) unfreeze original 
%     9) request resulting value from original
% 
%    test(Pid, msg)      loopy()                                   
%        |                    |                                   
%        |                    |                                   
%        o    - freeze --->   o    
%        |                    |
%        o   - clone ---->    o      == spawn ==>           loopy() clone 
%        |                    |                                 |
%        o    - msg ------------------------------------->      o
%        |                    |                                 |
%        o   - get_value ------------------------------->       o
%        |                    |                                 |
%        o   <--- value ---------------------------------       o
%        |                    |                                 |
%        o   ------------------ [if clone is alive] halt -->    x
%        |                    |
%        o   - unfreeze -->   o
%        |                    |
%       \|/
%     return msg is                
%     safe or unsafe:      
%     {true or false}
% --------------------------------------------------------------------
% Sample output:
% [{msg, keep_on_trucking, is_safe, false},  
%  {msg, bump, is_safe, true}]
% --------------------------------------------------------------------
start() ->
   Pid = spawn(freeze_clone, loopy, [{1, false}] ),   %  process loopy
   Msg_1 = keep_on_trucking,                     % message to test on process loopy
   Safe_1 = test(Pid, Msg_1),
   % ------------------------
   Msg_2 = bump,
   Safe_2 = test(Pid, Msg_2),
   io:format("\n"),
   [
   {msg, Msg_1, is_safe, Safe_1}, 
   {msg, Msg_2, is_safe, Safe_2}
   ].
   
test(Pid, Msg) ->
   Pid ! freeze,
   ClonePid = rpc(Pid, clone),
   ClonePid ! unfreeze,
   Before = rpc(ClonePid, get_value),
   % send test msg to clone
   ClonePid ! Msg,
   Result = rpc(ClonePid, get_value),
   if
       Result == no_one_can_answer -> Safe = false;
       true -> Safe = true
   end,
   if 
       Safe == true ->
           ClonePid ! exit,
           Pid ! unfreeze,
           Pid ! Msg,
           After = rpc(Pid, get_value);
       true ->
           Pid ! unfreeze,
           After = no_value
   end,
   Results = {'message_tried:', Msg, 
   'message_transition_is_safe:', Safe,
   'state_machine_name:', loopy,
   'value_before:', Before, 
   'value_after:', After},
   io:format("\n ~w \n",[Results]),
   Safe.
   
loopy(State) ->
   {Value, Freeze} = State, 
   if 
       Freeze == false ->
           receive
               {From, get_value} ->
                   From ! Value;
               {set_value, NValue} ->
                   loopy({NValue, Freeze});
               bump -> 
                   loopy({Value+1, Freeze}); 
               freeze -> 
                   loopy({Value, true});
               exit ->
                   exit(normal);
               {Error, Error_Msg} ->
                   io:format("error: ~w \n",[{Error, Error_Msg}]);
               _Any -> 
                   exit(normal)
           end;
       Freeze == true ->
           receive              
               unfreeze -> 
                   loopy({Value,false});
               {From, clone} -> 
                   Pid2 = spawn( freeze_clone, 
                               loopy, 
                               [{Value, Freeze}]), 
                   From ! Pid2
           end;
       true -> ok
   end,
   loopy(State).
   
rpc(To, Msg) ->
   To ! {self(), Msg},
   receive
       Answer -> Answer
   after 1000 ->
       Answer = no_one_can_answer
   end,
   Answer.
   
% ===============================================================
%6> c(freeze_clone).
%{ok,freeze_clone}
%7> freeze_clone:start().

%{'message_tried:',keep_on_trucking,'message_transition_is_safe:',
  false,'state_machine_name:',loopy,'value_before:',1,'value_after:',no_value} 
%{'message_tried:',bump,'message_transition_is_safe:',
  true,'state_machine_name:',loopy,'value_before:',1,'value_after:',2} 
%[{msg,keep_on_trucking,is_safe,false},
%{msg,bump,is_safe,true}]