%% Inspired by "Programming Erlang",
%% published by The Pragmatic Bookshelf.
%% Copyrights apply to this code.
%% We make no guarantees that this code is fit for any purpose.
%% Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
-module(server).
-export([start/2, start/3, start_on_node/3, rpc/2]).
start(Name, Mod) ->
%% like start/3 but call Mod:init() for getting the initial state
start(Name, Mod, Mod:init()).
start(Name, Mod, State) ->
%% start a server with the given initial State and register it as
%% Name
register(Name, spawn(fun() -> loop(Mod, State) end)).
start_on_node(Node, Mod, State) ->
%% link start/3 but start the process on the given Node and not
%% register it (only local processes could be registered)
spawn(Node, fun() -> loop(Mod, State) end).
wait_response(_, Ref) ->
%% wait for a specific reponse form the server
receive
{Ref, crash} -> exit(rpc);
{Ref, ok, Response} -> Response
end.
rpc(Client, Request) ->
%% make a call to the given client (make a reference to find the
%% corresponding response)
Ref = make_ref(),
Client ! {Ref, self(), Request},
wait_response(Client, Ref).
loop(Mod, OldState) ->
%% main lopp
receive
%% pattern, that is matched if a message is directly sended to
%% a server (like register and login called form the client
%% over the dispatcher)
{From, Request} ->
try Mod:handle(Request, OldState) of
{Response, NewState} ->
From ! {ok, Response},
loop(Mod, NewState)
catch
%% log error if handle function is not working
%% correctly and report the crash to the sender
_: Why ->
log_the_error(Request, Why),
From ! {crash},
loop(Mod, OldState)
end;
%% pattern is matched if rpc/2 is used, response is send with
%% the received reference to match the correct response
{Ref, From, Request} when is_reference(Ref) ->
try Mod:handle(Request, OldState) of
{Response, NewState} ->
From ! {Ref, ok, Response},
loop(Mod, NewState)
catch
%% log error if handle function is not working
%% correctly and report the crash to the sender
_: Why ->
log_the_error(Request, Why),
From ! {Ref, crash},
loop(Mod, OldState)
end;
%% handle messages if a linked process emits an error (no
%% response to sender nessessary, sender is dead)
{'EXIT', From, Why} ->
try Mod:handle({'EXIT', From, Why}, OldState) of
NewState ->
loop(Mod, NewState)
catch
%% log error if handle function is not working
%% correctly and report the crash to the sender
_: Why ->
log_the_error('EXIT', Why),
loop(Mod, OldState)
end
end.
log_the_error(Request, Why) ->
%% helper function to log an error
io:format("Server request ~p caused exception ~p~n", [Request, Why]).