Skip to content

Instantly share code, notes, and snippets.

@Xdeon
Created July 11, 2020 02:31
Show Gist options
  • Select an option

  • Save Xdeon/ddf681d75c74a8e4fc648f995fa27be3 to your computer and use it in GitHub Desktop.

Select an option

Save Xdeon/ddf681d75c74a8e4fc648f995fa27be3 to your computer and use it in GitHub Desktop.
frequency server with exceptions
-module(frequency).
-export([start/0, allocate/0, deallocate/1, stop/0, init/0]).
start() ->
Pid = spawn(?MODULE, init, []),
register(?MODULE, Pid),
Pid.
allocate() ->
send_receive(allocate).
deallocate(Freq) ->
send_receive({deallocate, Freq}).
stop() ->
send_receive(stop).
send_receive(Info) ->
try ?MODULE ! {request, self(), Info} of
_ ->
receive
{reply, Reply} -> Reply;
{error, Error} -> erlang:error(Error)
after 500 ->
erlang:error(timeout)
end
catch error:badarg ->
erlang:error(serverdown)
end.
init() ->
erlang:process_flag(trap_exit, true),
Frequencies = {get_frequencies(), []},
loop(Frequencies).
get_frequencies() ->
[10, 11, 12, 13, 14, 15].
loop(Frequencies) ->
receive
Info ->
case handle_info(Info, Frequencies) of
stop -> ok;
NewFrequencies -> loop(NewFrequencies)
end
end.
handle_info({request, Pid, _}=Info, Frequencies) ->
try handle(Info, Frequencies) of
{reply, Reply, NewFrequencies} ->
Pid ! {reply, Reply},
NewFrequencies;
{stop, Reply, _} ->
Pid ! {reply, Reply},
stop
catch error:Error ->
Pid ! {error, Error},
Frequencies
end;
handle_info({'EXIT', Pid, _Reason}, Frequencies) ->
exited(Frequencies, Pid);
handle_info(Unknown, Frequencies) ->
io:format("received unknown message :~p~n", [Unknown]),
Frequencies.
handle({request, Pid, allocate}, Frequencies) ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
{reply, Reply, NewFrequencies};
handle({request, _Pid, {deallocate, Freq}}, Frequencies) ->
NewFrequencies = deallocate(Frequencies, Freq),
{reply, ok, NewFrequencies};
handle({request, _Pid, stop}, Frequencies) ->
{stop, stopped, 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 ->
erlang:error(unallocated_frequency)
end.
exited({Free, Allocated}, Pid) ->
case lists:keytake(Pid, 2, Allocated) of
{value, {Freq, Pid}, NewAllocated} ->
{[Freq|Free], NewAllocated};
false ->
{Free, Allocated}
end.
% tests
-include_lib("eunit/include/eunit.hrl").
frequency_test() ->
Server = start(),
% test naming
?assertEqual(Server, whereis(?MODULE)),
% test allocate
?assertEqual({ok, 10}, allocate()),
% test duplicate allocate
?assertEqual({error, already_allocated}, allocate()),
% test inproper deallocate
?assertMatch({'EXIT', {unallocated_frequency, _}}, catch deallocate(11)),
?assertEqual({error, already_allocated}, allocate()),
% test proper deallocate
?assertEqual(ok, deallocate(10)),
?assertEqual({ok, 10}, allocate()),
% test stop
?assertEqual(stopped, stop()),
ok.
serverdown_test() ->
?assertMatch({'EXIT', {serverdown, _}}, catch allocate()).
client_fail_test() ->
start(),
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).
@elbrujohalcon
Copy link

This is truly an excellent code. Kudos!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment