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.
Related
In the LYSE book the author handles the termination of the server as follows:
%% Synchronous call
close_shop(Pid) -> gen_server:call(Pid, terminate).
handle_call(terminate, _From, Cats) ->
{stop, normal, ok, Cats}.
terminate(normal, Cats) ->
[io:format("~p was set free.~n",[C#cat.name]) || C <- Cats],
ok.
So it returns a stop value from the handle_call callback.
Here is how I wrote it:
close_shop(Pid) -> gen_server:stop(Pid).
terminate(_Reason, {Cats, Money}) ->
io:format("Made $~w~n", [Money]),
[io:format("~p was set free.~n",[C#cat.name]) || C <- Cats].
Is this not a good practice then to call gen_server:stop() directly?
It is not a bad practice to call gen_server:stop/1,3 directly. It does an almost same thing as the example from LYSE but without calling handle_call/3 from your module. Try and check it out. You can even read the source code to be sure.
Is it possible to inspect the internal state of a gen_server after a callback function has been called? I would rather not change the API of my server here.
You could use sys:get_state/1 which works nicely with all gen's.
Maybe, you would found useful another approach to unit-testing gen_servers.
Instead of running gen_server process and testing its behaviour, you can directly test gen_server callbacks and then inspect its state transitions.
For example:
-module(foo_server).
%% Some code skipped
handle_call({do_stuf, Arg}, _From, State) ->
NewState = modify_state(
{reply, {stuf_done, Arg}, NewState}.
%% Some code skipped
-ifdef(TEST)
do_stuf_test_() ->
{setup,
fun() ->
{ok, InitState} = foo_server:init(SomeInitParams),
InitState
end,
fun(State) ->
ok = foo_server:terminate(shutdown, State)
end,
fun(State) ->
Result = foo_server:handle_call({do_stuf, hello}, undefined, State),
[
?_assertMatch({reply, {stuf_done, hello}, _}, Result)
]
end
}
}.
-endif.
See discussion of this approach here
Also, if you dealing with realy complex states and state transitions, maybe you would be found proper helpful.
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 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.
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