Skip to content

Instantly share code, notes, and snippets.

@elbrujohalcon
Created July 7, 2020 14:58
Show Gist options
  • Save elbrujohalcon/42f1d7fe656c71a558d4d2a88439e634 to your computer and use it in GitHub Desktop.
Save elbrujohalcon/42f1d7fe656c71a558d4d2a88439e634 to your computer and use it in GitHub Desktop.
Frequency with Errors
%% @doc Frequency server that "transmits" errors from server to client
-module frequency.
-export [test/0].
-export [start/0, allocate/0, deallocate/1].
-export [init/0].
test() ->
%% Ensure the server is killed... This is ugly
catch exit(whereis(frequency), kill),
timer:sleep(100),
undefined = whereis(frequency),
%% If the server is not running, we get the proper error
try frequency:allocate() of
Result1 -> erlang:error({unexpected_result, Result1})
catch
error:server_down -> ok
end,
%% Start the server and allocate a freq
frequency:start(),
Frequency = frequency:allocate(),
%% Try to deallocate another freq, we get an error
try frequency:deallocate(Frequency + 1) of
Result2 -> erlang:error({unexpected_result, Result2})
catch
error:unallocated_frequency -> ok
end,
%% Allocate all the frequencies
[_|OtherFrequencies] = get_frequencies(), %% Cheating... I know ;)
ok = lists:foreach(fun(_) -> frequency:allocate() end, OtherFrequencies),
%% Try to allocate another frequency, get an error
try frequency:allocate() of
Result3 -> erlang:error({unexpected_result, Result3})
catch
error:no_more_frequencies -> ok
end,
ok.
start() ->
register(frequency, spawn(frequency, init, [])).
allocate() ->
cmd(allocate).
deallocate(Freq) ->
cmd({deallocate, Freq}).
%% @doc This function encapsulates the logic to send commands to the server,
%% receive its result and "decode" it into the expected behavior.
cmd(Command) ->
try frequency ! {request, self(), Command} of
{request, _, Command} ->
receive
{reply, Result} -> Result;
{error, Error} -> erlang:error(Error);
{exit, Reason} -> erlang:exit(Reason)
after 5000 ->
erlang:error(timeout)
end
catch
error:badarg ->
erlang:error(server_down)
end.
init() ->
loop({get_frequencies(), []}).
get_frequencies() -> [10,11,12,13,14,15].
loop(State) ->
receive
{request, From, Command} ->
try handle(Command, From, State) of
{Result, NewState} -> %% If a command produces a result
From ! {reply, Result}, %% send it back to the client as is
loop(NewState)
catch
throw:{Result, NewState} -> %% If a command returns early
From ! {reply, Result}, %% ALSO send the result to the client
loop(NewState);
Kind:Error:Stack -> %% If there is an error, we need to make THE CLIENT fail
From ! {Kind, Error}, %% NOT the server, so we "encode" the error
%% and we send it back to the client
io:format("Server ~p handling ~p: ~p~n\t~p~n", [Kind, Command, Error, Stack]),
loop(State)
end;
UnknownMessage ->
%% If we don't even know what process sent us this thing that we don't want
%% we can only log an error and hope for the best...
io:format("Unknown message received in Server: ~p~n", [UnknownMessage]),
loop(State)
end.
handle(allocate, _From, {[], _}) ->
erlang:error(no_more_frequencies);
handle(allocate, From, {[Freq|Freqs], Allocated}) ->
{Freq, {Freqs, [{Freq, From} | Allocated]}};
handle({deallocate, Freq}, From, {Freqs, Allocated}) ->
case lists:keytake(Freq, 1, Allocated) of
{value, {Freq, From}, OtherAllocated} ->
{ok, {[Freq|Freqs], OtherAllocated}};
_ ->
erlang:error(unallocated_frequency)
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment