Skip to content

Instantly share code, notes, and snippets.

@colinbankier
Created April 21, 2017 13:43
Show Gist options
  • Save colinbankier/38e652f5a4beb32a6fe0a80cc8e67741 to your computer and use it in GitHub Desktop.
Save colinbankier/38e652f5a4beb32a6fe0a80cc8e67741 to your computer and use it in GitHub Desktop.
Frequency 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(frequency).
-export([start/0, init/0, allocate/0, deallocate/1]).
%% These are the start functions used to create and
%% initialize the server.
start() ->
case whereis(?MODULE) of
undefined -> register(?MODULE, spawn(?MODULE, init, []));
_Pid -> already_running
end.
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} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
Pid ! {reply, ?MODULE, Reply},
loop(NewFrequencies);
{request, Pid , {deallocate, Freq}} ->
{NewFrequencies, Reply} = deallocate(Frequencies, Freq, Pid),
Pid ! {reply, ?MODULE, Reply},
loop(NewFrequencies);
{request, Pid, stop} ->
Pid ! {reply, ?MODULE, stopped}
end.
%% Clears messages currently in the mailbox.
%% Only matches messages in {reply, frequency, ...} format to only
%% clear messages in the client process that were sent by the
%% frequency server. It should not clear other messages in the
%% client process.
clear() ->
receive
{reply, ?MODULE, Msg} ->
io:format("Clearing message ~w~n", [Msg]),
clear()
after 0 ->
ok
end.
%% The Internal Help Functions used to allocate and
%% deallocate frequencies.
%% Allocates frequency.
%% Only allows each client process to have one allocated frequency.
%% If a second allocation is attempted for a client process, an
%% error {error, already_has_allocation} is returned.
allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
allocate({[Freq|Free], Allocated}, Pid) ->
AllocatedToPid = lists:filter(fun({_Freq, AllocatedPid}) -> AllocatedPid == Pid end, Allocated),
case length(AllocatedToPid) of
0 -> {{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}};
_ -> {{[Freq|Free], Allocated}, {error, already_has_allocation}}
end.
%% Deallocates a frequency
%% Only allows a process to deallocate a frequency
%% allocated to its own PID.
deallocate({Free, Allocated}, Freq, Pid) ->
case lists:member({Freq, Pid}, Allocated) of
true ->
NewAllocated = lists:delete({Freq, Pid}, Allocated),
{{[Freq|Free], NewAllocated}, ok};
false ->
{{Free, Allocated}, {error, allocated_to_another_pid}}
end.
%% Functional interface
%% Allocates a new frequency.
%% Before allocating, clears old messages from previous attempts.
%% Note, this still potentially leaves the frequency client and server
%% in an inconsistent state if a frequency is allocated in the server,
%% but the client didn't get the response, and doesn't know what it is.
%% It then cannot allocate a new one, or deallocate its current one.
allocate() ->
clear(),
?MODULE ! {request, self(), allocate},
receive
{reply, ?MODULE, Reply} -> Reply
after 5000 ->
{timeout, no_reply_from_server}
end.
%% Deallocates the given frequency.
deallocate(Freq) ->
clear(),
?MODULE ! {request, self(), {deallocate, Freq}},
receive
{reply, ?MODULE, Reply} -> Reply
after 5000 ->
{timeout, no_reply_from_server}
end.
-module(frequency_test).
-export([frequency_client/1]).
-import('frequency', [start/0]).
-include_lib("eunit/include/eunit.hrl").
frequency_server_test_() ->
{setup,
fun stop_frequency_server/0,
fun() ->
{inorder,
[
allocate_frequency(),
deallocate_frequency(),
cant_deallocate_frequency_not_owned()
]}
end}.
stop_frequency_server() ->
case whereis(frequency) of
undefined -> ok;
Pid ->
exit(Pid, kill),
timer:sleep(100)
end.
frequency_client(Func) ->
Self = self(),
spawn(fun() ->
Result = Func(),
Self ! Result
end),
receive
Msg -> Msg
end.
allocate_frequency() ->
stop_frequency_server(),
frequency:start(),
Result1 = frequency_client(fun() -> frequency:allocate() end),
?assertEqual({ok, 10}, Result1),
Result2 = frequency_client(fun() -> frequency:allocate() end),
?assertEqual({ok, 11}, Result2).
deallocate_frequency() ->
stop_frequency_server(),
frequency:start(),
?assertEqual(ok, frequency_client(fun() ->
frequency:allocate(),
frequency:deallocate(10)
end)),
?assertEqual({ok, 10}, frequency_client(fun() -> frequency:allocate() end)).
cant_deallocate_frequency_not_owned() ->
stop_frequency_server(),
frequency:start(),
Result1 = frequency_client(fun() -> frequency:allocate() end),
?assertEqual({ok, 10}, Result1),
Result2 = frequency_client(fun() -> frequency:deallocate(10) end),
?assertEqual({error, allocated_to_another_pid}, Result2).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment