Skip to content

Instantly share code, notes, and snippets.

@fswalker
Created April 24, 2017 20:55
Show Gist options
  • Save fswalker/6cd86f901ac7fa1e13cdcc4c4a0bc2f8 to your computer and use it in GitHub Desktop.
Save fswalker/6cd86f901ac7fa1e13cdcc4c4a0bc2f8 to your computer and use it in GitHub Desktop.
Solution of assignment no. 2 in Week 2 of Concurrent Programming in Erlang MOOC
%% 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
% Using the observer, set up a system of frequency server and at least two clients, and kill the frequency server when the clients are in possession of a frequency – you can make the clients “sleep” at any point using the timer:sleep/1 function – observe that the clients are killed too.
% How would you modify the clients so that they are not affected by the server terminating? How could you then shut down the entire system if you needed to?
-module(frequency).
-export([loop/1,allocate/0,deallocate/1,stop/0]).
-export([ start_server_supervisor/0
, server_supervisor/0
]).
-export([ init_client/2
, start_client/2
, run_simulation/0
]).
% Hard Coded
get_frequencies() -> [10,11,12,13,14,15].
% this function starts server supervisor
server_supervisor() ->
io:format("Server supervisor (~p) started~n", [self()]),
% we turn on flag in order to translate exit signals to messages and handle them properly
process_flag(trap_exit, true),
% create Frequencies list
Frequencies = {get_frequencies(), []},
% create new server process and link it immediately to this supervisor, register using frequency name
register(frequency, spawn_link(frequency, loop, [Frequencies])),
% recursive call with new frequencies
server_supervisor(Frequencies).
% this function accepts frequencies and waits for the proper signal
server_supervisor(Frequencies) ->
process_flag(trap_exit, true),
FrequencyPid = whereis(frequency),
receive
{update, NewFrequencies, FrequencyPid} ->
io:format("Server supervisor (~p) received update signal (~p) from server (~p)~n", [self(), NewFrequencies, FrequencyPid]),
server_supervisor(NewFrequencies);
{stop} ->
io:format("Server supervisor (~p) received stop msg~n", [self()]),
stop(),
{stop, supervisor};
{'EXIT', Pid, Reason} ->
io:format("Server supervisor (~p) received exit signal (~p) from process (~p)~n", [self(), Reason, Pid]),
register(frequency, spawn_link(frequency, loop, [Frequencies])),
server_supervisor(Frequencies);
Msg ->
io:format("Server supervisor (~p) ignoring (~p)~n", [self(), Msg]),
server_supervisor(Frequencies)
end.
% this function is used to initialize server supervisor
start_server_supervisor() ->
io:format("Registering server supervisor~n"),
register(frequency_supervisor, spawn(frequency, server_supervisor, [])).
%% The Main Loop
loop(Frequencies) ->
io:format("Server (~p) running with Freqs (~p)~n", [self(), Frequencies]),
receive
{request, Pid, allocate} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
Pid ! {reply, Reply},
% inform your supervisor, that the state has changed! - we have new frequencies table
send_update_to_supervisor(NewFrequencies, self()),
loop(NewFrequencies);
{request, Pid , {deallocate, Freq}} ->
NewFrequencies = deallocate(Frequencies, Freq),
Pid ! {reply, ok},
% inform your supervisor, that the state has changed! - we have new frequencies table
send_update_to_supervisor(NewFrequencies, self()),
loop(NewFrequencies);
{request, Pid, stop} ->
Pid ! {reply, stopped},
% inform your supervisor, that the server has stopped! The whole system will be shutdown
frequency_supervisor ! {stop, self()};
{'EXIT', Pid, _Reason} -> %%% CLAUSE ADDED
NewFrequencies = exited(Frequencies, Pid),
loop(NewFrequencies)
end.
send_update_to_supervisor(NewFrequencies, ServerPid) ->
SupervisorPid = whereis(frequency_supervisor),
case SupervisorPid of
undefined -> error;
_ -> frequency_supervisor ! {update, NewFrequencies, ServerPid}
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
{{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),
{[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),
{[Freq|Free],NewAllocated};
false ->
{Free,Allocated}
end.
% make client, client loop use process_flag
init_client(Delay, Sleep) ->
process_flag(trap_exit, true),
client(Delay, Sleep).
client(Delay, Sleep) ->
timer:sleep(Delay),
receive
{'EXIT', _Pid, _Reason} ->
io:format("~p: Client exit before allocate~n", [self()]),
exit
after 0 ->
case whereis(frequency) of
undefined ->
io:format("~p: server died, exiting... ~n", [self()]);
_ ->
{Status, Freq} = allocate(),
io:format("~p: allocate result (~p, ~p)~n", [self(), Status, Freq]),
timer:sleep(Sleep),
receive
{'EXIT', _Pid, Reason} ->
io:format("~p: Client exit before deallocate: ~p~n", [self(), Reason]),
exit
after 0 ->
case Status of
ok ->
deallocate(Freq),
io:format("~p: Client deallocate OK - continue client~n", [self()]),
client(Delay, Sleep);
error ->
io:format("~p: Client deallocate error - stop client~n", [self()]),
error
end
end
end
end.
start_client(Delay, Sleep) ->
spawn(frequency, init_client, [Delay, Sleep]).
run_simulation() ->
start_server_supervisor(),
% initialize client - after 4s allocates frequency and after 4s deallocates and repeat
start_client(4000, 4000),
% initialize client - after 5s allocates frequency and after 5s deallocates and repeat
start_client(5000, 5000),
timer:sleep(6000),
% test killing server - supervisor should restore it
exit(whereis(frequency), kill),
% add another client and test
start_client(2000, 2000),
timer:sleep(3000),
% wrong messages should be consumed and ignored in order to prevent inbox of the process getting too big
frequency_supervisor ! {test, wrong, message},
frequency_supervisor ! {test, wrong, message2},
timer:sleep(1000),
% kill
% exit(whereis(frequency_supervisor), kill).
% or shutdown
frequency_supervisor ! {stop}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment