Created
April 24, 2017 20:55
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% 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