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.
Related
I have a gen_server in my cavv application that I need to start first to execute a call to. I want to use a command dispatcher for this. For a short example, this it the gen_server's API:
a gen_server: cavv_user
-module(cavv_user).
-behavior(gen_server).
-define(SERVER(UserId), {via, gproc, {n, l, {?MODULE, UserId}}}).
start_link(UserId) ->
gen_server:start_link(?SERVER(UserId), ?MODULE, [UserId], []).
change_email_address(UserId, EmailAddress) ->
gen_server:call(?SERVER(AggregateId), {execute_command, #change_user_email_address{user_id=UserId, email_address=EmailAddress}}).
Before I can call cavv_user:change_email_address(). I need to start the cavv_user. I do this is as a simple_one_for_one child in a supervisor, like so:
a supervisor: cavv_user_sup
-module(cavv_user_sup).
-behaviour(supervisor).
-define(CHILD(ChildName, Type, Args), {ChildName, {ChildName, start_link, Args}, temporary, 5000, Type, [ChildName]}).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_child(UserId) ->
supervisor:start_child(?SERVER, [UserId]).
init([]) ->
RestartStrategy = {simple_one_for_one, 1, 5},
Children = [?CHILD(cavv_user, worker, [])],
{ok, { RestartStrategy, Children} }.
The problem I am now facing is how to dispatch commands to a cavv_user. I want to make sure the proper user is started first using start_child, and then call the cavv_user:change_email_address().
I have found this anwser, to use a dispatcher: Erlang: what supervision tree should I end with writing a task scheduler?
So I created a command dispatcher and end up with a cavv_user_dispatcher and a cavv_user_dispatcher_sup that in turn contains the cavv_user_dispatcher and the earlier cavv_user_sup:
cavv_user_dispatch_sup
| |
cavv_user_dispatcher |
(gen_server) |
|
|
cavv_user_sup
| | |
cavv_user_1...cavv_user_N
The cavv_user_dispatcher
This works beautifully.
The problem I am facing now is, how do I properly write the code in cavv_user_dispatcher? I am facing a problem with code duplication. How to properly call start_child and call the appropriate API of cavv_user?
Should I use some kind of Fun like so?
-module(cavv_user_dispatcher).
dispatch_command(UserId, Fun) ->
gen_server:call(?SERVER, {dispatch_command, {UserId, Fun}}).
handle_call({dispatch_command, {UserId, Fun}}, _From, State) ->
cavv_user_sup:start_child(UserId),
Fun(), %% How to pass: cavv_user:change_email_address(..,..)?
{reply, ok, State};
Or duplicate the cavv_user's API like so?
-module(cavv_user_dispatcher).
change_user_email_address(UserId, EmailAddress) ->
gen_server:call(?SERVER, {change_user_email_address, {UserId, EmailAddress}}).
handle_call({change_user_email_address, {UserId, EmailAddress}}, _From, State) ->
cavv_user_sup:start_child(UserId),
cavv_user:change_email_address(UserId, EmailAddress),
{reply, ok, State};
Or should I re-use the command records from cavv_user into some kind of util to properly build them and pass them around? Maybe some better way to pass the function I want to call at cavv_user?
I would like to solve the problem in the best Erlang way as possible, without code duplication.
Is your dispatcher supposed to handle other commands?
If yes then then how will the next command will come, I mean will the requester know the process pid of the user or not?
if yes then you need 2 functions, one to create a user, it will return the pid to the requester for next call, and one to handle next requests by sending the command to the given pid
if no, then you need also 2 functions, one to create the a user and store the user_id along with the user process pid and one to handle next request by retrieving the process pid and then forward it the command (I suppose this is what you want to do).
if no then you don't need to handle any command and should pass directly the email address when creating the user process. Note that this is true for all cases since you need a different interface to create a user.
I would modify your code this way (not tested, it is too late :o) !)
-module(cavv_user_dispatcher).
create_user(UserId,UserMail) ->
gen_server:call(?SERVER,{new_user,UserId,UserMail}).
% Args is a list of argument, empty if
% F needs only one argument (the user Pid)
dispatch_command(UserId, Fun, Args) ->
gen_server:call(?SERVER, {dispatch_command, {UserId, Fun,Args}}).
handle_call({dispatch_command, {UserId, Fun,Args}}, _From, State) ->
Pid = get_pid(UserId,State),
Answer = case Pid of
unknown_user_id -> unknown_user_id;
_ -> apply(Fun,[Pid|Args]),
ok
end,
{reply, Answer, State};
handle_call({new_user,UserId,UserMail},_From,State) ->
% verify that the user id does not already exists
CheckId = check_id(UserId,State),
{Answer,NewState} = case CheckId of
false -> {already_exist,State};
true -> {ok,Pid} = cavv_user_sup:start_child(UserId,UserMail)
{ok,[{UserId,Pid}|State]}
% State must be initialized as an empty list in the init function.
{reply, Answer, NewState};
...
get_pid(UserId,State) ->
proplists:get_value(UserId, State, unknown_user_id).
check_id(UserId,State) ->
not proplists:is_defined(UserId, State).
and the user supervisor mus be modified this way:
start_child(UserId,UserMail) -> % change arity in the export
supervisor:start_child(?SERVER, [UserId,UserMail]).
and then the user server:
start_link(UserId,UserMail) ->
gen_server:start_link(?SERVER(UserId), ?MODULE, [UserId,UserMail],[]).
init([UserId,UserMail]) ->
{ok,[{user_id,UserId},{user_mail,UserMail}]}.
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.
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 a gen_server which exports a function like this:
my_function(Param) ->
gen_server:cast(?SERVER, {forward, Param}).
and has an handle_cast like this:
handle_cast({forward, Param}, #state{peer=Socket} = State) ->
gen_tcp:send(Socket, Param),
{noreply, State}.
In most of cases the peer that is connected using gen_tcp will reply with one among different messages, and I handle the reply in the handle_info
handle_info({tcp, Socket, Data}) ->
io:format("Received : ~p~n", [Data]),
{noreply, State}.
Is there any recommended way to test this kind of scenario in erlang?
You can use setup/teardown functions in order to do something like that:
my_test_() -> {
setup,
fun() -> my_server:start_link() end,
fun(_) -> my_server:stop() end,
fun() ->
%% here you do all the message sending and such
end
}.
You also need to export a stop/0 function which will be responsible for call/cast the gen_server in order to stop it.
You can find out more infos on fixtures here
HTH,
Alin
I have gen_server:
start(UserName) ->
case gen_server:start({global, UserName}, player, [], []) of
{ok, _} ->
io:format("Player: " ++ UserName ++ " started");
{error, Error} ->
Error
end
...
Now i want to write function to stop this gen server. I have:
stop(UserName) ->
gen_server:cast(UserName, stop).
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
I run it:
start("shk").
Player: shk startedok
stop(shk).
ok
start("shk").
{already_started,<0.268.0>}
But:
stop(player).
ok
is work.
How can i run gen_server by name and stop by name?
Thank you.
First: You must always use the same name to address a process, "foo" and foo are different, so start by having a strict naming convention.
Second: When using globally registered processes, you also need to use {global, Name} for addressing processes.
In my opinion you should also convert the stop function to use gen_server:call, which will block and let you return a value from the gen_server. An example:
stop(Name) ->
gen_server:call({global, Name}, stop).
handle_call(stop, _From, State) ->
{stop, normal, shutdown_ok, State}
This would return shutdown_ok to the caller.
With this said, the global module is rather limited and alternatives like gproc provides much better distribution.
I don't have the docs infront of me, but my guess would be that you need to wrap the username in a global tuple within the gen_server cast.