Skip to content

Instantly share code, notes, and snippets.

@darcros
Created July 3, 2020 12:49
Show Gist options
  • Save darcros/925272526614b140a960b49e5732934b to your computer and use it in GitHub Desktop.
Save darcros/925272526614b140a960b49e5732934b to your computer and use it in GitHub Desktop.
Concurrent Programming in Erlang - The University of Kent - chapter 2.12
-module(frequency_hardened).
-export([init/0, start/0, stop/0, allocate/0, deallocate/1]).
-define(TIMEOUT, 5000).
%% server implementation
allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
allocate({[Freq | Free], Allocated}, Pid) ->
link(Pid),
{{Free, [{Freq, Pid} | Allocated]}, {ok, Freq}}.
deallocate({Free, Allocated}, {Freq, Pid}) ->
case lists:keyfind(Freq, 1, Allocated) of
{Freq, Pid} ->
NewAllocated = lists:delete({Freq, Pid}, Allocated),
unlink(Pid),
{{[Freq | Free], NewAllocated}, ok};
{Freq, _} ->
{{Free, Allocated}, unauthorized};
false ->
{{Free, Allocated}, not_allocated}
end.
exited({Free, Allocated}, Pid) ->
{ToFree, NewAllocated} = lists:partition(fun({_Freq, OtherPid}) -> Pid == OtherPid end, Allocated),
NewFree = [Freq || {Freq, _Pid} <- ToFree],
{NewFree ++ Free, NewAllocated}.
loop(Frequencies) ->
io:format("[server] frequencies: ~w~n", [Frequencies]),
receive
{request, Pid, allocate} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
Pid ! {reply, Reply},
loop(NewFrequencies);
{request, Pid, {deallocate, Freq}} ->
{NewFrequencies, Reply} = deallocate(Frequencies, {Freq, Pid}),
Pid ! {reply, Reply},
loop(NewFrequencies);
{request, Pid, stop} ->
Pid ! {reply, stopped};
{'EXIT', Pid, _} ->
NewFrequencies = exited(Frequencies, Pid),
loop(NewFrequencies)
end.
get_inital_frequencies() ->
[10, 11, 12, 13].
init() ->
process_flag(trap_exit, true),
loop({get_inital_frequencies(), []}).
%% private functions
clear() ->
receive
_Msg -> clear()
after 0 ->
ok
end.
wait_reply() ->
receive
{reply, Rep} -> Rep
after ?TIMEOUT ->
clear(),
{error, timout}
end.
%% API
start() ->
register(frequency_hardened, spawn(frequency_hardened, init, [])).
stop() ->
frequency_hardened ! {request, self(), stop},
wait_reply().
allocate() ->
frequency_hardened ! {request, self(), allocate},
wait_reply().
deallocate(Frequency) ->
frequency_hardened ! {request, self(), {deallocate, Frequency}},
wait_reply().
-module(scenario).
-export([client/2, start_client/1, demo/0]).
%% --- helper functions ---
pick_random_weigthed(Choiches) ->
Max = lists:foldl(fun({Weigth, _Elem}, Acc) -> Weigth + Acc end, 0, Choiches),
Rand = rand:uniform(Max),
pick_random_weigthed(Choiches, Rand).
pick_random_weigthed([{Weigth, Elem} | Rest], Num) ->
case Weigth >= Num of
true -> Elem;
false -> pick_random_weigthed(Rest, Num - Weigth)
end.
%% --- client implementation ---
try_allocate(Id, Frequencies) ->
case frequency_hardened:allocate() of
{ok, Freq} ->
io:format("[client: ~w] Allocated new frequency: \"~w\"~n", [Id, Freq]),
[Freq | Frequencies];
Error ->
io:format("[client: ~w] Could not allocate new frequency: \"~w\"~n", [Id, Error]),
Frequencies
end.
try_deallocate(Id, []) ->
io:format("[client: ~w] Cannot deallocate frequency: no allocated frequencies~n", [Id]),
[];
try_deallocate(Id, [Freq | Allocated]) ->
case frequency_hardened:deallocate(Freq) of
ok ->
io:format("[client: ~w] Deallocated frequency: \"~w\"~n", [Id, Freq]),
Allocated;
Error ->
io:format("[client: ~w] Could not deallocate frequency \"~w\", error: \"~w\"~n", [Id, Freq, Error]),
[Freq, Allocated]
end.
crash(Id, _Frequencies) ->
io:format("[client: ~w] Crashing~n", [Id]),
exit(foo).
client(Id, Frequencies) ->
Action = pick_random_weigthed([
{50, fun try_allocate/2},
{50, fun try_deallocate/2},
{1, fun crash/2}
]),
NewFrequencies = Action(Id, Frequencies),
timer:sleep(1000),
client(Id, NewFrequencies).
%% --- demo ---
%% creates a client and links this process to it
start_client(Id) -> spawn_link(scenario, client, [Id, []]).
%% whenever a client dies, restarts it
loop(N) ->
receive
{'EXIT', _Pid, _Reason} ->
start_client(N + 1),
loop(N + 1);
_ ->
loop(N)
end.
%% creates three clients, if one dies starts another
demo() ->
process_flag(trap_exit, true),
frequency_hardened:start(),
start_client(1),
start_client(2),
start_client(3),
loop(3).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment