Skip to content

Instantly share code, notes, and snippets.

@lucasdf
Created April 14, 2017 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lucasdf/5f1c756ba7fd67558c4e1874963d8cd7 to your computer and use it in GitHub Desktop.
Save lucasdf/5f1c756ba7fd67558c4e1874963d8cd7 to your computer and use it in GitHub Desktop.
Week 2 - Supervisor 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).
-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.
-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.
-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