Skip to content

Instantly share code, notes, and snippets.

@Xdeon
Created July 26, 2020 01:37
Show Gist options
  • Save Xdeon/05ac80eb636dcdec3cbff547aad31d04 to your computer and use it in GitHub Desktop.
Save Xdeon/05ac80eb636dcdec3cbff547aad31d04 to your computer and use it in GitHub Desktop.
frequency server based on gen_server
%% 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(gf).
-behaviour(gen_server).
% an implementation of this is included.
-export([start_link/0]).
% you need to implement these functions.
-export([init/1, handle_call/3, handle_cast/2]).
% these are implemented for you.
-export([handle_info/2, terminate/2, code_change/3]).
% you will need to implement these.
-export([allocate/0, deallocate/1, add_frequencies/1, report/0, stop/0]).
%% These are the start functions used to create and
%% initialize the server.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) ->
erlang:process_flag(trap_exit, true),
Frequencies = {get_frequencies(), []},
{ok, Frequencies}.
% Hard Coded
get_frequencies() -> [10,11,12,13,14,15].
%% Functional interface
allocate() ->
gen_server:call(?MODULE, allocate).
deallocate(Freq) ->
gen_server:cast(?MODULE, {deallocate, Freq}).
report() ->
gen_server:call(?MODULE, report).
add_frequencies(New) ->
gen_server:call(?MODULE, {add_frequencies, New}).
stop() ->
gen_server:call(?MODULE, stop).
handle_call(allocate, {Pid, _}, Frequencies) ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
{reply, Reply, NewFrequencies};
handle_call(report, _From, Frequencies={Free, Allocated}) ->
{reply, {length(Free), length(Allocated)}, Frequencies};
handle_call({add_frequencies, New}, _From, Frequencies) ->
{NewFrequencies, Reply} = add(Frequencies, New),
{reply, Reply, NewFrequencies};
handle_call(stop, _From, Frequencies) ->
{stop, normal, stopped, Frequencies};
handle_call(Call, _From, Frequencies) ->
io:format("unknown call: ~p~n", [Call]),
{noreply, Frequencies}.
handle_cast({deallocate, Freq}, Frequencies) ->
NewFrequencies = deallocate(Frequencies, Freq),
{noreply, NewFrequencies};
handle_cast(Cast, Frequencies) ->
io:format("unknown cast: ~p~n", [Cast]),
{noreply, Frequencies}.
handle_info({'EXIT', Pid, _Reason}, Frequencies) ->
NewFrequencies = exited(Frequencies, Pid),
{noreply, NewFrequencies};
handle_info(Info, Frequencies) ->
io:format("unknown info: ~p~n", [Info]),
{noreply, Frequencies}.
%% The Internal Help Functions used to allocate and
%% deallocate frequencies.
allocate({Freqs, Allocated}, Pid) ->
case lists:keymember(Pid, 2, Allocated) of
true -> {{Freqs, Allocated}, {error, already_allocated}};
false -> do_allocate({Freqs, Allocated}, Pid)
end.
do_allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
do_allocate({[Freq|Free], Allocated}, Pid) ->
link(Pid),
{{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}}.
deallocate({Free, Allocated}, Freq) ->
case lists:keytake(Freq, 1, Allocated) of
{value, {Freq, Pid}, NewAllocated} ->
unlink(Pid),
{[Freq|Free], NewAllocated};
false ->
% do nothing since this is a cast
{Free, Allocated}
end.
add({Free, Allocated}, New) ->
case check_duplication(Free++Allocated, New) of
ok -> {{Free++New, Allocated}, ok};
Error -> {{Free, Allocated}, Error}
end.
check_duplication(_, []) -> ok;
check_duplication(AllFreqs, [Freq|Rest]) ->
case lists:member(Freq, AllFreqs) of
true -> {error, {duplicated_frequency, Freq}};
false -> check_duplication(AllFreqs, Rest)
end.
exited({Free, Allocated}, Pid) ->
case lists:keytake(Pid, 2, Allocated) of
{value, {Freq, Pid}, NewAllocated} ->
{[Freq|Free], NewAllocated};
false ->
{Free, Allocated}
end.
% default implementations
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% tests
-include_lib("eunit/include/eunit.hrl").
frequency_test() ->
{ok, Server} = start_link(),
% test naming
?assertEqual(Server, whereis(?MODULE)),
% test allocate
?assertEqual({ok, 10}, allocate()),
% test duplicate allocate
?assertEqual({error, already_allocated}, allocate()),
% test inproper deallocate
?assertMatch(ok, deallocate(11)),
?assertEqual({error, already_allocated}, allocate()),
% test proper deallocate
?assertEqual(ok, deallocate(10)),
?assertEqual({ok, 10}, allocate()),
% test stop
?assertEqual(stopped, stop()),
?assertEqual(undefined, whereis(?MODULE)),
ok.
client_fail_test() ->
_ = start_link(),
Self = self(),
spawn(fun() -> client(Self) end),
% test allocate
receive
{reply, Reply} -> ?assertEqual({ok, 10}, Reply)
end,
timer:sleep(300),
% client failed and allocated frequency should have been released
?assertEqual({ok, 10}, allocate()),
stop(),
ok.
client(Pid) ->
Reply = allocate(),
Pid ! {reply, Reply},
timer:sleep(100),
exit(bye).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment