erlangrebar3

rebar3, supervisor, behaviour(application)


I have the following straightforward UDP Server:

Now, my peer UDP Client is going to send a malformed data intentionally.

I can write a case clause to match any message and simply ignore any malformed message.

However, it will not help me if there is eventual bugs.

I have read somewhere: "Don't program defensively, let it crash, then fix it".

    =ERROR REPORT==== 19-Jul-2020::21:15:29.872000 ===
    Error in process <0.93.0> with exit value:
    {{case_clause,<<0>>},[{server,loop,1,[{file,"server.erl"},{line,16}]}]}

Cool, it crash, But I want my server to restart automatically now :-)

I have read that a process called "supervisor" can monitor my server and restarts it when it detects that it died.

So, I have used "rebar3" because it helped me a lot when I compile several files with just 1 single line 'rebar3 compile'.

It creates automatically a /src/ with 3 files, but only 2 are interesting me for now:

In addition, I have read the documentations but I'm still far from understanding.

Can anyone advise please, or transform my 19 lines of code server.erl to server_app.erl and supervised by a server_sup.erl?

N.B: I'm not looking for a gen_server, I see it a lot but am I obligated to transform this to a gen_server also, or only application+supervisor is ok for my requirement?

Thanks in advance,

Best Regards,


Solution

  • Supervisors are part of the OTP supervision tree, which handles all the restart policies and such. Although it's possible not to use gen_servers for its modules, I'd avise against it: gen_servers provide a handy abstraction for the most common server operations (and they handle name registering, sys messages and other goodies out of the box).

    Although the 'let it crash' lemma is common in Erlang, it does not mean that you don't have to foresee issues your code may face or that you only care about the happy case. I'd hate to see any sytem crashing due to someone's malformed/malicious nc -u.

    Keep in mind that if the supervisor's restart limit is reached, it will die too, eventually reaching the application's top supervisor, which crashes the VM upon death.

    Let's get to the code (very little editing over what rebar3 new app generates):

    The application requires no editions:

    -module(server_app).
    
    -behaviour(application).
    -export([start/2, stop/1]).
    
    start(_StartType, _StartArgs) ->
        server_sup:start_link().
    
    stop(_State) ->
        ok.
    

    The supervisor has a little more configuration, but that's it:

    -module(server_sup).
    
    -behaviour(supervisor).
    -export([start_link/0]).
    -export([init/1]).
    
    start_link() ->
        supervisor:start_link(?MODULE, []).
    
    init([]) ->
        SupFlags = #{strategy => one_for_all,
                     intensity => 0,
                     period => 1},
        ChildSpecs = [#{id => my_server,
                        start => {server, start_link, [12345]},
                        type => worker
                       }],
        {ok, {SupFlags, ChildSpecs}}.
    

    And the server.erl needs some modifications in the start function:

    -module(server).
    -export([start_link/1]).
    
    start_link(Port) ->
        {ok, spawn_link(fun()->run_it(Port) end)}.
    
    run_it(Port) ->
        {ok, Skt} = gen_udp:open(Port, [binary]),
        loop(Skt).
    
    loop(Skt) ->
        receive
            {udp, Skt, _, _, Bin} ->
                case Bin of
                    <<0:32>> ->
                        io:fwrite("~p~n", [{"Good Format: ", Bin}]),
                        loop(Skt)
                end
    

    The server.erl as gen_server would be something like:

    -module(server).
    
    -export([start_link/1]).
    
    -behaviour(gen_server).
    -export([
             init/1,
             handle_cast/2,
             handle_call/3,
             handle_info/2
            ]).
    
    start_link(Port) ->
        gen_server:start_link(?MODULE, Port, []).
    
    init(Port) ->
        gen_udp:open(Port, [binary]).
    
    handle_call(_Call, _From, State) ->
        {reply, ok, State}.
    
    handle_cast(_Msg, State) ->
        {noreply, State}.
    
    handle_info({udp, Skt, _, _, Bin}, Skt) ->
        case Bin of
            <<0:32>> -> io:fwrite("~p~n", [{"Good Format: ", Bin}])
        end,
        {noreply, Skt};
    handle_info(_Msg, State) -> % Messages that are not {udp, Skt, _, _, _} are discarded
        {noreply, State}.