Skip to content

Instantly share code, notes, and snippets.

@Xdeon
Last active July 11, 2020 02:12
Show Gist options
  • Save Xdeon/ad98a90a4c9dc9332c9d28edc6d15d3a to your computer and use it in GitHub Desktop.
Save Xdeon/ad98a90a4c9dc9332c9d28edc6d15d3a to your computer and use it in GitHub Desktop.
frequency server hardened with links
-module(frequency_hardened).
-export([start/0, allocate/0, deallocate/1, stop/0, init/0]).
start() ->
Pid = spawn(?MODULE, init, []),
register(?MODULE, Pid),
Pid.
allocate() ->
?MODULE ! {request, self(), allocate},
client_receive().
deallocate(Freq) ->
?MODULE ! {request, self(), {deallocate, Freq}},
client_receive().
stop() ->
?MODULE ! {request, self(), stop},
client_receive().
client_receive() ->
receive
{reply, Reply} -> Reply
after 500 ->
{error, timeout}
end.
init() ->
erlang:process_flag(trap_exit, true),
Frequencies = {get_frequencies(), []},
loop(Frequencies).
get_frequencies() ->
[10, 11, 12, 13, 14, 15].
loop(Frequencies) ->
receive
{request, Pid, allocate} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
Pid ! {reply, Reply},
loop(NewFrequencies);
{request, Pid, {deallocate, Freq}} ->
NewFrequencies = deallocate(Frequencies, Freq),
Pid ! {reply, ok},
loop(NewFrequencies);
{'EXIT', Pid, _Reason} ->
NewFrequencies = exited(Frequencies, Pid),
loop(NewFrequencies);
{request, Pid, stop} ->
Pid ! {reply, stopped}
end.
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 ->
{Free, Allocated}
end.
exited({Free, Allocated}, Pid) ->
case lists:keytake(Pid, 2, Allocated) of
{value, {Freq, Pid}, NewAllocated} ->
{[Freq|Free], NewAllocated};
false ->
{Free, Allocated}
end.
-module(scenario).
-export([setup/0, stop/1, client/2, kill_server/0, random_elem/1]).
% Use this module to exercise the behaviour of the
% hardened frequency server.
% Calling setup will launch the server and two clients: alice and bob.
setup() ->
frequency_hardened:start(),
[spawn(?MODULE, client, [Id, []]) || Id <- names()].
kill_server() ->
exit(whereis(frequency_hardened), kill).
stop(Clients) ->
[Pid ! stop || Pid <- Clients],
catch frequency_hardened:stop(),
ok.
names() ->
[alice, bob].
% A client, parametrised by its name (optional, but useful instrumentation),
% and the list of frequencies currently allocated to that process. Needed
% to produce calls to deallocate/1 that don't fail.
% Could also
% - parameterise on the ratio of allocates to deallocates
% - deal with case when no frequencies available: here a client fails
% - add stop commands.
client(Id, Freqs) ->
erlang:process_flag(trap_exit, true),
loop(Id, Freqs).
loop(Id, Freqs) ->
receive
{'EXIT', _, _} ->
io:format("Frequency server down. Exit.~n"),
% since the server is down, all API calls would fail
% hope the client shutdown gracefully
ok;
stop ->
io:format("client ~w asked to stop. Exit.~n", [Id]),
ok
% Notice it is very likely that API calls to frequency server fail due to the name can not be found
% when we kill the server manually
% this can not be avoided in current implementation
after 0 ->
case rand:uniform(2) of
1 ->
case frequency_hardened:allocate() of
{ok, Freq} ->
io:format("Frequency ~w allocated to client ~w.~n", [Freq, Id]),
timer:sleep(1000),
loop(Id, [Freq|Freqs]);
{error, no_frequency} ->
io:format("No frequency available for client ~w. Exit.~n", [Id]),
exit(no_frequency);
{error, already_allocated} ->
io:format("Frequency already allocated to client ~w. Continue.~n", [Id]),
timer:sleep(1000),
loop(Id, Freqs)
end;
2 ->
Len = length(Freqs),
case Len of
0 ->
io:format("No frequencies to deallocate by client ~w.~n", [Id]),
timer:sleep(1000),
loop(Id, Freqs);
_ ->
Freq = lists:nth(rand:uniform(Len), Freqs),
frequency_hardened:deallocate(Freq),
io:format("Frequency ~w deallocated by client ~w.~n", [Freq, Id]),
timer:sleep(1000),
loop(Id, lists:delete(Freq, Freqs))
end
end
end.
% for debugging purposes: chooses a random element of a non-empty list.
random_elem([]) ->
empty;
random_elem(Xs) ->
Len = length(Xs),
lists:nth(rand:uniform(Len), Xs).
@elbrujohalcon
Copy link

Nice!!

Tip: you should take a look at lists:keytake/3 🧐

@Xdeon
Copy link
Author

Xdeon commented Jul 11, 2020

Nice!!

Tip: you should take a look at lists:keytake/3 🧐

Thanks for the hint!

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