Created
April 14, 2017 18:30
-
-
Save lucasdf/5f1c756ba7fd67558c4e1874963d8cd7 to your computer and use it in GitHub Desktop.
Week 2 - Supervisor Assignment
This file contains hidden or 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 | |
-module(frequency). | |
-export([init/0, stop/0, get_state/0, start_server/0, allocate/0, deallocate/1]). | |
% Usage: | |
% 1> frequency:start_server(). | |
% 2> frequency:allocate(). | |
% 3> frequency:deallocate(10). | |
% 4> frequency:deallocate(1). | |
%% Public APIs | |
% helper method to start the server. | |
start_server() -> | |
register(?MODULE, spawn(?MODULE, init, [])). | |
% helper method to allocate new frequency. | |
allocate() -> | |
?MODULE ! {request, self(), allocate}, | |
receive | |
{reply, Reply} -> Reply | |
end. | |
% helper method to deallocate a frequency. | |
deallocate(Freq) -> | |
?MODULE ! {request, self(), {deallocate, Freq}}, | |
receive | |
{reply, Reply} -> Reply | |
end. | |
% helper method to stop the server | |
stop() -> | |
?MODULE ! {request, self(), stop}, | |
receive | |
{reply, Reply} -> Reply | |
end. | |
% helper method that prints the current state of the server. | |
get_state() -> | |
?MODULE ! {list, self(), all}, | |
receive | |
Any -> | |
io:format('Current state: ~p~n', [Any]) | |
end. | |
%% These are the start functions used to create and | |
%% initialize the server. | |
init() -> | |
Frequencies = {get_frequencies(), []}, | |
loop(Frequencies). | |
% Hard Coded | |
get_frequencies() -> [10,11,12,13,14,15]. | |
%% The Main Loop | |
loop(Frequencies) -> | |
receive | |
{request, Pid, allocate} -> | |
io:format("Allocate request~n"), | |
{NewFrequencies, Reply} = allocate(Frequencies, Pid), | |
Pid ! {reply, Reply}, | |
loop(NewFrequencies); | |
{request, Pid , {deallocate, Freq}} -> | |
io:format("Deallocate request: ~p~n", [Freq]), | |
{NewFrequencies, Reply} = deallocate(Frequencies, Freq, Pid), | |
Pid ! {reply, Reply}, | |
loop(NewFrequencies); | |
{'EXIT', Pid, _Reason} -> | |
io:format('Received exit command with reason: ~p~n', [_Reason]), | |
NewFrequencies = exited(Frequencies, Pid), | |
loop(NewFrequencies); | |
{request, Pid, stop} -> | |
Pid ! {reply, stopped}; | |
{list, Pid, all} -> | |
Pid ! {Frequencies}, | |
loop(Frequencies) | |
end. | |
%% The Internal Help Functions used to allocate and | |
%% deallocate frequencies. | |
allocate({[], Allocated}, _Pid) -> | |
{{[], Allocated}, {error, no_frequency}}; | |
allocate({[Freq|Free], Allocated}, Pid) -> | |
case already_allocated(Allocated, Pid) of | |
true -> | |
{ {[Freq|Free], Allocated}, {error, already_allocated}}; | |
false -> | |
link(Pid), | |
{{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}} | |
end. | |
deallocate({Free, Allocated}, Freq, Pid) -> | |
case pid_has_frequency(Allocated, Pid, Freq) of | |
true -> | |
unlink(Pid), | |
NewAllocated=lists:keydelete(Freq, 1, Allocated), | |
{ {[Freq|Free], NewAllocated}, ok}; | |
false -> | |
{ {Free, Allocated}, {error, not_allocated}} | |
end. | |
% already_allocated returns: | |
% true: Pid has been allocated a frequency | |
% false: Pid has not been allocated a frequency | |
% Maybe a better solution could be to have a separated list containing only Pid who have been allocated a frequency or to invert and use {Pid, Frequency} instead of {Frequency, Pid}. | |
already_allocated([], _Pid) -> | |
% If no frequency has been allocated (first parameter is an empty list) then this Pid has not been allocated any frequency. | |
false; | |
already_allocated(Allocated, Pid) -> | |
Pred = fun({_, APid}) -> APid =:= Pid end, | |
case lists:any(Pred, Allocated) of | |
true -> true; | |
false -> false | |
end. | |
% pid_has_frequency returns: | |
% true: Pid has been allocated the frequency Frequency | |
% false: Pid has not been allocated the frequency Frequency | |
pid_has_frequency(Allocated, Pid, Frequency) -> | |
Pred = fun({AFrequency, APid}) -> (APid =:= Pid) and (AFrequency =:= Frequency) end, | |
case lists:any(Pred, Allocated) of | |
true -> true; | |
false -> false | |
end. | |
% exited callback. it should deallocate the dead client and return the new state of the server. | |
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. |
This file contains hidden or 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
-module(frequency_supervisor). | |
-export([init/0, supervisor/0]). | |
init() -> | |
spawn(frequency_supervisor, supervisor, []). | |
supervisor() -> | |
process_flag(trap_exit, true), | |
Pid = spawn_link(frequency, init, []), | |
register(frequency, Pid), | |
receive | |
{'EXIT', Pid, normal} -> | |
ok; | |
{'EXIT', Pid, _Reason} -> | |
io:format("Supervisor: the frequency server has died. Restarting it..."), | |
init() | |
end. |
This file contains hidden or 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
-module(frequency_test). | |
-export([all/0]). | |
-define(TESTED_MODULE, frequency). | |
% Usage: | |
% 1> frequency_test:all(). | |
all() -> | |
% start the test named process. | |
start_test_server(), | |
% first request to allocate. Should be allocated the frequency 10. | |
test_allocate({reply, {ok, 10}}), | |
% Trying to allocated another frequency. Should receive 'already_allocated' error | |
test_allocate({reply,{error,already_allocated}}), | |
% Trying to deallocate frequency not allocated to this client. Should receive 'not_allocated' error. | |
test_deallocate({reply,{error,not_allocated}}, 11), | |
% Trying to deallocate the frequency allocated before. Should succeed. | |
test_deallocate({reply, ok}, 10), | |
% tests are over, stop the named process. | |
stop_test_server(), | |
tests_success. | |
% test the allocate function on 'frequency' process | |
test_allocate(ExpectedAnswer) -> | |
?TESTED_MODULE ! {request, self(), allocate}, | |
receive | |
ExpectedAnswer -> true; | |
Any -> | |
io:format("Expected ~p ~n", [ExpectedAnswer]), | |
io:format("Got ~p instead ~n", [Any]), | |
exit(tests_not_success) | |
end. | |
% test the deallocate function on 'frequency' process | |
test_deallocate(ExpectedAnswer, Freq) -> | |
?TESTED_MODULE ! {request, self(), {deallocate, Freq}}, | |
receive | |
ExpectedAnswer -> true; | |
Any -> | |
io:format("Expected ~p ~n", [ExpectedAnswer]), | |
io:format("Got ~p instead ~n", [Any]), | |
exit(tests_not_success) | |
end. | |
% start_test_server will start the frequency server for the tests. We check if the 'frequency' process already exists and if it does we kill it and then start a new one. We want to work on a fresh new process to guarantee the tests integrity. | |
start_test_server() -> | |
case whereis(?TESTED_MODULE) of | |
undefined -> | |
% The named process 'frequency' does not exist. Just start a new one. | |
?TESTED_MODULE:start_server(); | |
Pid -> | |
% The named process 'frequency' already exists. Send stop message, unregister it and then start it again. | |
unregister(?TESTED_MODULE), | |
Pid ! {request, self(), stop}, | |
receive | |
{reply, stopped} -> true | |
end, | |
?TESTED_MODULE:start_server() | |
end. | |
% stop_test_server will stop the testing server. | |
stop_test_server() -> | |
case whereis(?TESTED_MODULE) of | |
undefined -> true; | |
Pid -> | |
% The named process 'frequency' already exists. Send stop message, unregister it and then start it again. | |
unregister(?TESTED_MODULE), | |
Pid ! {request, self(), stop}, | |
receive | |
{reply, stopped} -> true | |
end | |
end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment