erlang: gen-server and state - erlang

As an exercise to test my understanding of sequential Erlang I wrote a simple-minded html tag editor.
It maintains a current position in the text, e.g. edit state, in a record like this:
-record(ed_txt, {a=[], b=[], c=[]).
...where a holds text above the current paragraph, b holds the current paragraph, and c holds text below the current paragraph.
It is called like this:
tag(App, FileName) ->
{ok, File1} = get_file(App, FileName),
{ok, File2} = file_to_paragraphs(File1),
{ok, Record} = create_record(File2),
....
tag(Record) ->
{ok, InputTuple} = get_input(Record), % gets edit command
...
do(Command, Record)
The function do/2 is recursive function that looks like this:
do("h", Record) -> tag(help(Record));
do("down", Record) -> tag(down(Record));
do("up",Record) -> tag(up(Record));
do("h1", Record) -> tag(wrap("H1", Record));
...
do("quit", Record) -> {ok, quit(Record)};
do(_, Record) -> tag(Record).
Now I'd like to turn this module into an OTP gen-server, but realize that I don't really understand how to maintain state.
I created a state record:
-record(state, {app, filename, a=[], b=[], c=[]}).
My thought is to use a one-to-one supervisor so each user is working in his/her own process, ditch the edit loop, and use handle_call (or handle_cast) to call the respective
edit functions.
But what confuses me is this:
As long as the process is running, presumably user state is maintained in the process, but both handle_call and handle_cast take State as a parameter and return State.
Does this mean that I also have to maintain state for each user OUTSIDE of their respective processes, in ets perhaps?
Or is there a better way to do this altogether?
Many thanks,
LRP

Related

Can you use a list (or other collection) to specify what messages to receive in Erlang?

E.g. suppose I have a list that looks something roughly like this:
Handlers = [{foo, FooHandler}, {bar, BarHandler} | Etc()]
The best that I can come up with is this:
receive
Message ->
Handler = find_matching_handler(Message, Handlers),
Handler(Message)
end
The problem with this is that if Message does not match anything in Handlers, it's too late: I've taken it out of the mailbox.
I guess if there's a way to put a message back into the mailbox (into the save queue) without reordering, then that would take care of it. Simply resending to self() would reorder. It would also not restart the receive, and even if it did, you might get stuck in a spin loop until a message of interest arrives. Is there a way to put a message into the mailbox's save queue?
Another near solution that I thought of was to use match guard, but IIUC, you can only use BIFs in guards, which seems to preclude using find_matching_handler (unless there is a BIF for that).
Another near solution: map matching:
receive
M when Handlers#{M := Handler} -> Handler(M) % booyah?
end
Alas, I have not found an incantation that satisfies Erlang...
Match on the message:
loop() ->
receive
{foo, Data} ->
handle_foo(Data),
loop();
{bar, Data} ->
handle_bar(Data),
loop()
end.
This is the basic way of distinguishing between message forms.
You can also be less direct and match in a function head you pass all messages to:
loop() ->
receive
Message ->
handle_message(Message),
loop()
end.
handle_message({foo, Data}) ->
foo(Data),
ok;
handle_message({bar, Data}) ->
bar(Data),
ok.
A combination of the first and second forms is sort of the way gen_server type callback modules are structured in OTP. The message handlers receive a slightly more complex set of arguments and exist in their own module (the part you write), and the actual receive occurs in the generic gen_server module.
You can use a selective receive pattern to periodcally scan the mailbox for handler messages. Something like this:
check_msg_handlers(Handlers) ->
[check_handler(X) || X <- Handlers],
timer:sleep(500),
check_msg_handlers(Handlers).
check_handler(Handler) ->
receive
{_Handler={M,F}, Msg} ->
M:F(Msg)
after
0 ->
no_msg
end.
Note the receive X -> Y after -> N no_msg end, this is the selective receive. When using a timeout of N=0 it effectively becomes a scan of the mailbox to see if the X message is present or not, i.e. it becomes a non-blocking receive. The order of the messages is preserved after the scan as required in your case.
The LYSE chapter More On Multiprocessing has a section on selective receives that is very good.

How to call a unique registered gen_server process?

I'm new to Erlang and I'm trying to build a simple game server (learning purpose).
I have one client controller (supervisor) that creates multiple client handlers (gen_server). I have also one game controller (supervisor) that creates multiple game handlers (gen_server).
One game handler (game table) will then communicate with several client handlers (players). I create the client handlers like this:
client_handler.erl:
start_link(ClientId, UniqueId) ->
ClientHandlerId = utils:create_id(?MODULE, UniqueId), //client_handler_0
State = #state{client_id = ClientId, client_handler_id = ClientHandlerId},
gen_server:start_link({global, ClientHandlerId}, ?MODULE, State, []).
game_handler.erl:
start_link(ClientHandlerId, GameId, UniqueId) ->
GameHandlerId = utils:create_id(?MODULE, UniqueId), //game_handler_0
State = #state{client_handler_id = ClientHandlerId, game_id = GameId, game_handler_id = GameHandlerId},
gen_server:start_link({global, GameHandlerId}, ?MODULE, State, []).
My problem is that I want to talk between client_handler and game_handler without neither of them knowing about their inner structure. For now I use:
client_handler.erl:
gen_server:cast(game_handler_0, {make_move, MoveData}).
and this works great. However, I would like to use:
client_handler.erl:
game_handler_0:make_move(MoveData)
So I can build separate API:s for each module (only use -export functions). Is this possible?
Best regards,
xqtr
EDIT: typo
Yes, it is considered good practice to abstract away implementation details in that way. Just move the cast call into a function in game_handler.erl, something like:
make_move(UniqueId, MoveData) ->
GameHandlerId = utils:create_id(?MODULE, UniqueId),
gen_server:cast(GameHandlerId, {make_move, MoveData}).
and export make_move/2. (Or perhaps forget about UniqueId, and pass GameHandlerId directly to the make_move function.)

Is there any State variable in Mochiweb?

I have skimmed through the Mochiweb code, but have not found any sign of the State variable.
Does something similar to gen_server's State variable exist in Mochiweb?
I need to store some small amount of state-related server-side (not session-related) data on the server and I do not want to use ETS or Mnesia for that.
I think you have somewhat a misunderstanding of what gen_server state is.
First, let me explain briefly how mochiweb works.
Mochiweb doesn't produce a gen_server process per client. Instead, it just spawns a new process using proc_lib:spawn/3 and creates a parametrized module, which is, basically, a tuple of the following kind:
{mochiweb_request, #Port<0.623>, get, "/users", {1, 1}, []}
which is
{mochiweb_request, Socket, Method, RawPath, HTTPVersion, Headers}
This tuple is used as an argument to a function that you pass as a loop parameter to mochiweb_http:start/1. So, when this "loop" function is called, it will look like this:
handle_request(Req) ->
%% The pattern matching below just shows what Req really is
{mochiweb_request, _, _, _, _, _} = Req,
...
Now, to explanation of gen_server state.
Basically, gen_server is a process with approximately the following structure. Of course, IRL it's more complicated, but this should give you the general idea:
init(Options)
State = ...
loop(Module, State).
loop(Module, State)
NewState = receive
{call, Msg, From} -> Module:handle_call(Msg, From, State)
{cast, Msg} -> Module:handle_cast(Msg, State)
Info -> Module:handle_info(Info, State)
end,
loop(Module, NewState).
So, state is just an argument that you drag through all the function calls and change inside your loop. It doesn't actually matter if your process is a gen_server or not, it doesn't have what lifetime it has. In the following example the term [1, 2, 3] is a state too:
a() ->
b([1, 2, 3], now()).
b(State, Timestamp) ->
Result = do_something(Timestamp)
c(State, Result).
c(State, Payload) ->
exit({State, Payload}).
Now, back to mochiweb.
If you need to create a state of your own, you can just add an extra function argument:
handle_request(Req) ->
User = Req:get(path),
UserData = load_user_data(User),
handle_request(Req, UserData).
handle_request(Req, UserData) ->
...
Now UserData is a state too. You can loop this process, or let it respond and end right away – but you won't lose UserData as long as you pass it as an argument.
Finally, if you really want to make this process a gen_server (which is really unreasonable in most cases), you can use gen_server:enter_loop/3 function that will make your current process a gen_server. And The 3rd argument of this function will be your state that will be stored inside the started gen_server.

Erlang: extended gen_server

I want to extend gen_server (create a gen_server_extra) with some additional functionality. The requirements are:
The gen_server_extra processes should behave like a regular gen_server's. E.g, they should accept calls via gen_server:call, integrate with SASL, fit OTC supervision tree, etc.
gen_server_extra processes should have an additional functionality, provided by gen_server_extra. That basically means some of the messages will be handled by gen_server_extra code, without passing them to the callback module. The rest of the messages are passed to callback module as is.
gen_server_extra functionality requires its own state which should be hidden from the callback module.
What is the simplest approach to do that?
The best, most modular approach would be to implement a new behavior in a module (e.g. gen_ext_server) and wrap the gen_server behavior from there.
First, make sure your behavior is identical to gen_server:
-module(gen_ext_server).
-behavior(gen_server).
% Exports...
behaviour_info(Type) -> gen_server:behaviour_info(Type).
Implement all callbacks needed for gen_server, keep the name of the callback module that implements your behavior in your state:
init([Mod|ExtraArgs]) ->
% ...
ModState = Mod:init(ExtraArgs),
#state{mod = Mod, mod_state = ModState, internal = [...]}
Then, in each gen_server callback, implement your behavior and then call the callback module if needed:
handle_call(internal, _From, State) ->
% Do internal stuff...
{reply, ok, State};
handle_call(Normal, From, State = #state{mod = Mod, mod_state = ModState}) ->
case Mod:handle_call(Normal, From, ModState) of
{reply, Reply, NewState} ->
{reply, Reply, #state{mod_state = NewState};
... ->
...
end.
Implement similar functionality for handle_cast/2, handle_info/2, terminate/1 etc.
well, i would not call it customization, but rather a new behavior. You need to define your own behavior. A tutorial which will take you through this is found at trapexit.org.
However, the requirements are not very proper.
The main essence of giving the call back functions access to the server state is to write normal sequential code manipulating the state of your application when and where you want without interrupting the concurrency details.
Otherwise if that is the way to go, implement your own behavior.

Declaration of ETS in Erlang

The following code gives me an error: "syntax error before: Some_ets"
-module(tut).
-export([incr/1]).
Some_ets = ets:new(?MODULE, [bag]).
incr(X) ->
X+1.
But I am able to declare the ETS within a function, like:
-module(tut).
-export([incr/1]).
incr(X) ->
Some_ets = ets:new(?MODULE, [bag]),
X+1.
Can't I declare a ETS outside a function?
No - unlike other languages there isn't a concept of static initialization - there's no appropriate time for an Erlang system to execute that piece of code.
Erlang does have the concept of a parameterized module however, and that may be what you're after. Have a look here http://www.lshift.net/blog/2008/05/18/late-binding-with-erlang which is a good write up of that - it would allow you to instantiate an "instance" of your tut module bound to a given ets table and save passing around that handle explicitly in your module function calls.
Or if you are into OTP you could have the handle to the ets table passed around in the state variable:
init(_) ->
Some_ets = ets:new(?MODULE, [bag]),
{ok, Some_ets}.
and then use it in your handle_call methods:
get_ets_handle() ->
gen_server:call(?MODULE, {getETSHandle}, infinity).
handle_call({getETSHandle}, _From, Some_ets) ->
{reply, Some_ets, Some_ets}.
You can't do variable assignments like that in a module. See here.

Resources