duncanatt
4/24/2017 - 3:30 PM

Concurrent Programming in Erlang - Week 1 Assignment

Concurrent Programming in Erlang - Week 1 Assignment

%% Based on code from
%%   Erlang Programming
%%   Francecso Cesarini and Simon Thompson
%%   O'Reilly, 2008
%%   http://oreilly.com/catalog/9780596518189/
%%   http://www.erlangprogramming.org/
%%   (c) Francesco Cesarini and Simon Thompson

-module(frequency_sup).
-export([start/0,allocate/0,deallocate/1,stop/0,client/3,start_client/1]).
-export([init/2,super/2]).

% Launches the supervisor process with the specified MFA and registered name
% to which {M, F, A} is to be associated to.
super({M, F, A}, Name) ->

  % Add a reference to the supervisor's PID so that the child has a reference
  % to its parent, in order to be able to communicate with it to send its
  % state, as well as to be able to determine whether the supervisor was
  % terminated or one of the clients was terminated.
  State = [self() | A],

  % Trap exits.
  process_flag(trap_exit, true),
  io:format("Starting system with {M, F, A} ~p.~n", [{M, F, State}]),

  % Spawn and register the MFA used to manage the main process. Again, note that
  % we have injected the supervisor PID into the arguments.
  register(Name, ChildPid = spawn_link(M, F, State)),
  super_loop({M, F, State}, Name, ChildPid).

% The loop which manages the lifetime of the process that is spawned using the
% {M, F, A} specified above. We also maintain the child PID, so that messages
% tagged with {state, ...} coming from the child PID are identified.
super_loop({M, F, State}, Name, ChildPid) ->
  receive
    {'EXIT', _Pid, _Reason} ->
      io:format("Restarting system.~n"),
      register(Name, NewChildPid = spawn_link(M, F, State)),
      super_loop({M, F, State}, Name, NewChildPid);
    {state, ChildPid, NewFrequencies} ->
      io:format("Updating frequencies in supervisor ~p.~n", [NewFrequencies]),
      super_loop({M, F, [self(), NewFrequencies]}, Name, ChildPid)
  end.



%% These are the start functions used to create and
%% initialize the server.

start() ->

    % Get the list of hard-coded frequencies to pass to the init function, which
    % is presented as a {M, F, A} below. Note that the process is will also be
    % registered under the name 'frequency'.
    Frequencies = {get_frequencies(), []},
    register(super, spawn(frequency_sup, super,
                            [{frequency_sup, init, [Frequencies]}, frequency])).

% The main function includes also the supervisor PID, in addition to the
% frequencies. This serves two functions:
% 1. The main process will be able to send its state also to the supervisor,
%    so that if it crashes, the supervisor knows to start it from the last
%    known state.
% 2. This process is set to trap EXITs to deal with client deaths. However,
%    since the supervisor spawns_links this process, a death of the supervisor
%    would also send a message to this process in the form of KILLED. We need
%    to identify between process death messages coming from the supervisor and
%    the connected clients, and we do this by pattern-matching the EXIT
%    messages using the SuperPid passed to the init/2 function.
init(SuperPid, Frequencies) ->
  process_flag(trap_exit, true),
  loop(SuperPid, Frequencies).

% Hard Coded
get_frequencies() -> [10,11,12,13,14,15].

%% The Main Loop

% The main frequency server loop. Note that in addition to the frequencies, it
% also takes the supervisor PID. The updated state that is generated after
% processing each allocate/deallocate/client death messages is relayed to the
% supervisor, so that in case of the frequency server's death, this data is can
% be used by the supervisor to restart the frequency server in the last known
% state.
loop(SuperPid, Frequencies) ->

  % Print list of frequencies.
  io:format("[SERVER] List of frequencies free/assigned ~p.~n", [Frequencies]),

  receive
    {request, Pid, allocate} ->
      {NewFrequencies, Reply} = allocate(Frequencies, Pid),
      Pid ! {reply, Reply},
      super ! {state, self(), NewFrequencies},
      loop(SuperPid,  NewFrequencies);
    {request, Pid , {deallocate, Freq}} ->
      NewFrequencies = deallocate(Frequencies, Freq),
      Pid ! {reply, ok},
      super ! {state, self(), NewFrequencies},
      loop(SuperPid, NewFrequencies);
    {request, Pid, stop} ->
      Pid ! {reply, stopped},
      exit(stop);
    {'EXIT', SuperPid, _Reason} ->
      exit(stopped_sup);
    {'EXIT', Pid, _Reason} ->
      NewFrequencies = exited(Frequencies, Pid),
      super ! {state, self(), NewFrequencies},
      loop(SuperPid, NewFrequencies)
  end.

%% Functional interface

allocate() ->
    frequency ! {request, self(), allocate},
    receive
	    {reply, Reply} -> Reply
    end.

deallocate(Freq) ->
    frequency ! {request, self(), {deallocate, Freq}},
    receive
	    {reply, Reply} -> Reply
    end.

stop() ->
    frequency ! {request, self(), stop},
    receive
	    {reply, Reply} -> Reply
    end.


%% The Internal Help Functions used to allocate and
%% deallocate frequencies.

allocate({[], Allocated}, _Pid) ->
  {{[], Allocated}, {error, no_frequency}};
allocate({[Freq|Free], Allocated}, Pid) ->
  link(Pid),                                               %%% ADDED
  io:format("[Server] Allocated new frequency ~p to client ~p.~n", [Freq, Pid]),
  {{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}}.

deallocate({Free, Allocated}, Freq) ->
  {value,{Freq,Pid}} = lists:keysearch(Freq,1,Allocated),  %%% ADDED
  unlink(Pid),                                             %%% ADDED
  NewAllocated=lists:keydelete(Freq, 1, Allocated),
  io:format("[Server] Deallocated existing frequency ~p from client ~p.~n", [Freq, Pid]),
  {[Freq|Free],  NewAllocated}.

exited({Free, Allocated}, Pid) ->                %%% FUNCTION ADDED
  case lists:keysearch(Pid,2,Allocated) of
    {value,{Freq,Pid}} ->
      NewAllocated = lists:keydelete(Freq,1,Allocated),
      io:format("[Server] Reclaimed frequency ~p to client ~p.~n", [Freq, Pid]),
      {[Freq|Free],NewAllocated};
    false ->
      {Free,Allocated}
  end.

% Starts the client with the specified mode. The client process is set to
% trap EXITs so that when the frequency server process dies, it suspends itself.
% Also, this allows us to handle a stop command to issued to the frequency
% server by gracefully terminating all the clients.
% The client operates in one of two modes:
% die: the client makes one allocation and then dies normally. This triggers the
% frequency server to reclaim the allocated frequency number.
% normal: The client alternates between allocating a new frequency number and
% deallocating it.
start_client(Type) ->
  spawn(fun() -> process_flag(trap_exit, true), client(Type, null, whereis(frequency)) end).

% Modes: die, normal
client(Type, AllocatedFreq, ServerPid) ->

  % Get PID for logging purposes.
  Pid = self(),

  receive
    {'EXIT', ServerPid, killed} ->
        % Frequency server is dead - suspend myself.
        io:format("[CLIENT ~p] Frequency server ~p is dead...suspending till wakeup call.~n", [Pid, ServerPid]),
        receive
          {NewPid, wakeup} -> client(Type, AllocatedFreq, NewPid)
        end;
    {'EXIT', ServerPid, stop} ->
      % Normal stop signal from system.
      io:format("[CLIENT ~p] Putting client to sleep.~n", [Pid]),
      exit(stop)
    after 0 -> ok
  end,

  case Type of
    die ->
      % Allocate, wait and die. If we do not match the pattern (i.e. all
      % frequencies are depleated, we die abnormally), othewise we terminate
      % normally.
      {ok, Freq} = allocate(),
      io:format("[CLIENT ~p] Allocated ~p and dying normally.~n", [Pid, Freq]),
      timer:sleep(3000);

    normal ->
      % Default behaviour - allocate and hold frequency and then deallocate.
      % If we do not match the pattern (i.e. all frequencies are depleated,
      % we die abnormally).
      case AllocatedFreq of
        null ->
          % We are not allocated a frequency, so let's acquire one.
          {ok, Freq} = allocate(),
          io:format("[CLIENT ~p] Allocated ~p and will release it soon.~n", [Pid, Freq]),
          timer:sleep(1000 + rand:uniform(5000)),
          client(Type, Freq, ServerPid);
        _ ->
          % We are allocated a frequency, so let's relinquish it.
          deallocate(AllocatedFreq),
          client(Type, null, ServerPid)
      end
  end.