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.
Related
Suppose I have a gen_server:
-module(myserver).
-behavior(gen_server).
-export([init/1, handle_call/3]).
init(Arg) -> % ...
handle_call(Request, From, State) -> % ...
Compiling this naturally produces a warning:
myserver.erl:2: Warning: undefined callback function handle_cast/2 (behaviour 'gen_server')
How do I deal with this warning if I my server isn't going to need handle_cast/2? Do I define a placeholder function just to satisfy the compiler? For example:
handle_cast(_Request, _State) -> unimplemented.
Or do I ignore/suppress the warning message?
What is the proper way to write a gen_server where not all callback functions will be used?
We should NOT ignore the warning message, this makes us difficult to trace errors/bugs in our application in future. Leaving the placeholder function is a better way, but it's should like below:
handle_cast(_Msg, State) ->
{noreply, State}.
By default, this function can't data to the caller, so you can avoid to crash your application when you or your colleagues use gen_sever:cast/2.
I didn't have specific way to write gen_sever. In a big project that I worked for, they just leave the placeholder like below to use in the future. They don't care much about callback functions is used or not. We should take care about normal function is used or not rather than callback functions.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
How do I deal with this warning if I my server isn't going to need
handle_cast/2?
Then your server isn't a gen_server, so you can remove the line:
-behavior(gen_server).
which is what causes the compiler to check that the gen_server callback functions are present.
The bottom line is that OTP requires a contract between your code and the OTP behaviors. If you don't want to adhere to the contract, then you can't use OTP behaviors. In other words, you are required to implement ALL the callback functions.
Is it recommended to add logging to the supposedly unused callback
function so that their inadvertent use would appear in the logs?
It's up to you. It sounds like a good idea to me.
Consider I have a FSM implemented with gen_fsm. For a some Event in a some StateName I should write data to database and reply to user the result. So the following StateName is represented by a function:
statename(Event, _From, StateData) when Event=save_data->
case my_db_module:write(StateData#state.data) of
ok -> {stop, normal, ok, StateData};
_ -> {reply, database_error, statename, StateData)
end.
where my_db_module:write is a part of non-functional code implementing actual database write.
I see two major problems with this code: the first, a pure functional concept of FSM is mixed by part of non-functional code, this also makes unit testing of FSM impossible. Second, a module implementing a FSM have dependency on particular implementation of my_db_module.
In my opinion, two solutions are possible:
Implement my_db_module:write_async as sending an asynchronous message to some process handling database, do not reply in statename, save From in StateData, switch to wait_for_db_answer and wait result from db management process as a message in a handle_info.
statename(Event, From, StateData) when Event=save_data->
my_db_module:write_async(StateData#state.data),
NewStateData=StateData#state{from=From},
{next_state,wait_for_db_answer,NewStateData}
handle_info({db, Result}, wait_for_db_answer, StateData) ->
case Result of
ok -> gen_fsm:reply(State#state.from, ok),
{stop, normal, ok, State};
_ -> gen_fsm:reply(State#state.from, database_error),
{reply, database_error, statename, StateData)
end.
Advantages of such implementation is possibility to send arbitrary messages from eunit modules without touching actual database. The solution suffers from possible race conditions, if db reply earlier, that FSM changes state or another process send save_data to FSM.
Use a callback function, written during init/1 in StateData:
init([Callback]) ->
{ok, statename, #state{callback=Callback}}.
statename(Event, _From, StateData) when Event=save_data->
case StateData#state.callback(StateData#state.data) of
ok -> {stop, normal, ok, StateData};
_ -> {reply, database_error, statename, StateData)
end.
This solution doesn't suffer from race conditions, but if FSM uses many callbacks it really overwhelms the code. Although changing to actual function callback makes unit testing possible it doesn't solves the problem of functional code separation.
I am not comfortable with all of this solutions. Is there some recipe to handle this problem in a pure OTP/Erlang way? Of may be it is my problem of understating principles of OTP and eunit.
One way to solve this is via Dependency Injection of the database module.
You define your state record as
-record(state, { ..., db_mod }).
And now you can inject db_mod upon init/1 of the gen_server:
init([]) ->
{ok, DBMod} = application:get_env(my_app, db_mod),
...
{ok, #state { ..., db_mod = DBMod }}.
So when we have your code:
statename(save_data, _From,
#state { db_mod = DBMod, data = Data } = StateData) ->
case DBMod:write(Data) of
ok -> {stop, normal, ok, StateData};
_ -> {reply, database_error, statename, StateData)
end.
we have the ability to override the database module when testing with another module. Injecting a stub is now pretty easy and you can thus change the database code representation as you see fit.
Another alternative is to use a tool like meck to mock the database module when you are testing, but I usually prefer making it configurable.
In general though, I tend to split the code which is complex into its own module so it can be tested separately. I rarely do much unit testing of other modules and prefer large-scale integration tests to handle errors in such parts. Take a look at Common Test, PropEr, Triq and Erlang QuickCheck (The latter is not open source, nor is the full version free).
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.
I have erlang gen_fsm, my first state:
begin({Nick}, _From, State) ->
{reply, true, next_state, State}.
Then i have:
next_state(_Event, _From, State) ->
io:format("Test \n"),
{reply, ok, begin, State}.
But i don't seen Test note in shell
How correctly transit to a new state?
First of all, ensure that begin is the actual initial state of your FSM. You specify the initial state of your FSM by returning, in your init function, something like:
{ok, begin, State}
Where begin is your initial state.
Also, note that you're defining a Module:StateName/3 function, which will be called any time a gen_fsm:sync_send_event is performed on your FSM. If you're trying to send events to the FSM using gen_fsm:send_event, you should instead define a function Module:StateName/2, which is its asynchronous version.
Finally, try to debug your modules by tracing them, rather than adding printouts. It's much simpler and it avoids you recompiling your code time after time.
More information are avilable here.
you may find some examples here:
http://spawnlink.com/articles/an-introduction-to-gen_fsm-erlybanks-atm/index.html
and here:
http://pdincau.wordpress.com/2010/09/07/an-introduction-to-gen_fsm-behaviour/
Hope it helps
Is it possible to obtain the current state of a gen_server process (presumably by sending some system message)? It could be useful when debugging.
Of course, I can add a message which returns the current state to handle_call:
get_state(Server) -> gen_server:call(Server, '$get_state').
%% in every gen_server I want to debug
...
handle_call('$get_state', _From, State) ->
{reply, State, State};
...
but is there something built-in (even if it is a bit hacky)?
Use sys:get_status/1,2 function. It's definition is:
get_status(Name,Timeout) ->
{status, Pid, {module, Mod}, [PDict, SysState, Parent, Dbg, Misc]}
SysState will contain state of the process. It works for all processes using OTP behaviors and other processes implementing proc_lib and sys requirements.
There is actually a function that returns the state directly: sys:get_state/1,2. It accepts pid or name of the process and can optionally be given a timeout.