Skip to content

Instantly share code, notes, and snippets.

@jonalmeida
Last active April 20, 2017 06:05
Show Gist options
  • Save jonalmeida/f6807e283f6bd07e3d2d79abea1c284d to your computer and use it in GitHub Desktop.
Save jonalmeida/f6807e283f6bd07e3d2d79abea1c284d to your computer and use it in GitHub Desktop.
Supervision in the frequency server

Supervision in the frequency server

To give a design understanding of my code. There is a server, supervisor and client that all have their own loops. The server can be started without the supervisor with frequency:start/0. If the server fails, it will not be restarted. To start the server with a supervisor, you can do so with frequency:sup_start/0. To simulate clients, use frequency:sim_clients(N). You can simulate N number of clients running. The function returns the Pids of the spawned processes.

Things to note:

  • Because of the timeouts, you'll notice that if you spawn 3 process, you won't always use 3 frequencies. Some times a process will be sleeping so only 2 or 1 process may be running. You can alter the sleep calls accordingly to change this.
  • I register the atom io with the main pid (i.e. the shell) so that logging can be sent back to it with ease.
  • I've added a lot of logging during each attempt/failure so it might be too verbose for you. Comment some out accordingly.

We’ve seen the frequency server, introduced models for its clients and seen how the system behaves in various modes using the observer tool. In this assignment we’ll look at how to add a supervisor to a server/client system, and observe how it behaves.


Adding a supervisor

Program a supervisor process that can be used to start the frequency server, and restart it whenever it has terminated unexpectedly.

In adding the supervisor you will need to think about the state of the server when it is restarted. The following discussion may help you to think about that.

In a typical client-server scenario, the clients are not under the control of the supervisor: any client can connect to the server as long as it knows the name (or the Pid) of the server. So, we can’t expect the supervisor to restart the clients; on the other hand we can ensure that if the server terminates unexpectedly then the clients do too.


Using the observer

Using the observer tool as described in the previous exercise, observe how your system behaves when some of the constituent processes – including the supervisor itself – are killed.

Recall that to run the observer, type observer:start() in the Erlang shell, and that we use exit(Pid,killed) to kill the process with pid Pid.

%% 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).
-export([start/0, allocate/0, deallocate/1, stop/0, sup_start/0, sim_clients/1]).
-export([init/0, sup_init/0, sim_client_init/0]).
%% These are the start functions used to create and
%% initialize the server.
start() ->
% Registering io for sending messages back to the shell.
register(io, self()),
register(frequency,
spawn(frequency, init, [])).
init() ->
process_flag(trap_exit, true),
Frequencies = {get_frequencies(), []},
loop(Frequencies).
% Hard Coded
get_frequencies() -> [10,11,12,13,14,15].
%% The Main Loop
loop(Frequencies) ->
receive
{request, Pid, allocate} ->
%% Sleep timer for testing the clear/0 call.
%timer:sleep(10000),
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
Pid ! {reply, Reply},
loop(NewFrequencies);
{request, Pid, {deallocate, Freq}} ->
NewFrequencies = deallocate(Frequencies, Freq),
Pid ! {reply, ok},
loop(NewFrequencies);
{'EXIT', Pid, _Reason} ->
io:format("Received exit for Pid ~w", [Pid]),
NewFrequencies = exited(Frequencies, Pid),
loop(NewFrequencies);
{request, Pid, stop} ->
Pid ! {reply, stop}
end.
%% Functional interface
allocate() ->
server_request(allocate).
deallocate(Freq) ->
server_request({deallocate, Freq}).
stop() ->
server_request(stop).
server_request(Msg) ->
Server = whereis(frequency),
case Server of
undefined ->
{error, no_server};
_ ->
frequency ! {request, self(), Msg},
receive
{reply, Reply} -> Reply
end
end.
%% Called when a process dies. The server finds the associated frequency
%% that was allocated for it, and removes it from the allocated list.
exited({Free, Allocated}, Pid) ->
case lists:keysearch(Pid, 2, Allocated) of
{value, {Freq, Pid}} ->
NewAllocated = lists:keydelete(Freq, 1, Allocated),
{[Freq|Free], NewAllocated};
false ->
{Free, Allocated}
end.
%% The Internal Help Functions used to allocate and
%% deallocate frequencies.
%% The main allocate function that checks if a process already has a
%% frequency allocated for it. If it does, we do not do anything else,
%% otherwise we make a new allocation, and link it with the server process
%% so we can monitor when the process dies.
allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
allocate({[Freq|Free], Allocated}=Freqs, Pid) ->
IsAllocated = lists:keymember(Pid, 2, Allocated),
case IsAllocated of
true ->
{Freqs, {error, already_allocated}};
false ->
link(Pid),
{{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}}
end.
%% Deallocates a frequency for a given process and unlinks it
%% from the server process.
deallocate({Free, Allocated}, Freq) ->
case lists:keysearch(Freq,1,Allocated) of
{value,{Freq,Pid}} ->
unlink(Pid),
NewAllocated=lists:keydelete(Freq, 1, Allocated),
{[Freq|Free], NewAllocated};
false ->
{Free, Allocated}
end.
%% This is the supervisor start function, similar to the server start/0.
%% The 'sup' stands for supervisor.
sup_start() ->
% Registering io for sending messages back to the shell.
register(io, self()),
spawn(frequency, sup_init, []).
sup_init() ->
process_flag(trap_exit, true),
register(frequency,
spawn_link(frequency, init, [])),
supervisor_loop().
%% This is the supervisor loop function. It waits for an exit message,
%% which can only come from a server we spawned.in sup_init/0, and then
%% restarts the process along with registering the 'frequency' atom.
supervisor_loop() ->
receive
{'EXIT', _Pid, _Reason} ->
io ! io:format("Server shutdown. Attempting restart..~n"),
register(frequency, spawn_link(frequency, init, [])),
supervisor_loop();
stop ->
ok
end.
%% Simulation functions for acting like a client.
%% Like the supervisor and server, we have an init function to catch exits,
%% and a client loop that listens for an exit signal from a server.
%% If there is no pending message, we continue randomizing activity
%% and (ah ah ah) staying alive until the server is back up.
sim_client_init() ->
process_flag(trap_exit, true),
sim_client_loop(no_freq_set). % We start without a frequency
%% The `Freq` is the one we allocate which we hold on to for deallocation.
sim_client_loop(Freq) ->
receive
{'EXIT', Pid, _Reason} ->
io:format("Server ~w has shutdown~n", [Pid]),
% I'm not sure if this eplicity call to unlink is required, but
% it doesn't seem to cause issues otherwise.
unlink(Pid),
simulate_client(no_freq_set)
after 0 ->
simulate_client(Freq)
end.
%% Randomly decides to allocate, deallocate, and timeout along the way.
%% Continues to do this forever until the process is killed.
%% If allocating, we check if already allocated first.
simulate_client(Freq) ->
%% Using random to get either 1 to 3.
case rand:uniform(3) of
1 -> % We simulate allocation
sim_client_allocate(Freq),
timer:sleep(rand:uniform(5000));
2 -> % We sleep, simulating activity/randomness
io ! io:format("~w zzz..~n", [self()]),
timer:sleep(rand:uniform(5000));
3 -> % We simulate deallocation
io ! io:format("~w attempt dealloc~n", [self()]),
sim_client_deallocate(Freq),
timer:sleep(rand:uniform(5000))
end,
sim_client_loop(Freq).
%% Simulates allocation requests and handles responses accordingly.
sim_client_allocate(Freq) ->
Reply = allocate(),
case Reply of
{ok, New} ->
io ! io:format("Allocating ~w with freq ~w~n", [self(), New]),
simulate_client(New);
{error, Reason} ->
io ! io:format("Errored: ~w~n", [Reason]),
simulate_client(Freq)
end.
%% Simulates deallocation requests and handles responses accordingly.
sim_client_deallocate(Freq) ->
case Freq of
no_freq_set -> % This means we haven't allocated a frequency yet.
io ! io:format("no_freq_set ~w~n", [self()]),
no_freq_set;
_ ->
io ! io:format("calling dealloc now ~w~n", [self()]),
io ! io:format("dealloc resp: ~w~n", [deallocate(Freq)])
end.
%% Helper function to let you spawn `N` number of clients at once.
sim_clients(N) ->
sim_clients(N, []).
sim_clients(0, List) ->
List;
sim_clients(N, List) ->
Pid = spawn(frequency, sim_client_init, []),
sim_clients(N - 1, [Pid|List]).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment