I have problems getting gen_server:reply
to work in some but not all cases in my code although the code seems to me to be similar in structure from the areas it works and it doesn't. And I don't know if this is due to some conceptual misunderstanding or incompleteness of the gen_server:reply/
.
I have created MRE code as seen below (with EUnit tests and all ready to plug and play) I experience that the test function setup_test()
succeeds whereas the function setup_test_move_rs_both_receive()
doesn't. The latter creates a 'hanging'/time out for the mve_rps_game:move/2
function.
Why is that, and how can I deal with it?
-module(mve_rps_game).
-behaviour(gen_server).
-include_lib("eunit/include/eunit.hrl").
-export([init/1, handle_cast/2, handle_call/3, start/0, setup_game/2, move/2, test_all/0]).
%------------------------Internal functions to game coordinator module
make_move(Choice, OtherRef, PlayersMap, CurMoveKey, OtherMoveKey) ->
case maps:get(OtherMoveKey, PlayersMap, badkey) of
badkey ->
PlayersMap2 = maps:put(CurMoveKey, Choice, PlayersMap),
{noreply, PlayersMap2};
OtherChoice ->
io:format(user, "~n Choice, OtherChoice, CurPid, OtherRef: ~w ~w ~w ~w~n",[Choice, OtherChoice, self(), OtherRef]),
gen_server:reply(OtherRef, Choice),
{reply, OtherChoice, PlayersMap}
end.
%-----------------Init game-coordinator and its handle_call functions
init(_Args) ->
PlayersMap = #{},
{ok,PlayersMap}.
handle_call({move, Choice}, From, PlayersMap = #{start:= {P1Ref, P2Ref}}) ->
{P1id, _} = P1Ref,
{P2id, _} = P2Ref,
{CurId, _} = From,
case CurId of
P1id ->
make_move(Choice, P2Ref, PlayersMap, p1_move, p2_move);
P2id ->
make_move(Choice, P1Ref, PlayersMap, p2_move, p1_move);
_Other ->
{reply, {error, not_a_player}, PlayersMap}
end;
handle_call({set_up, Name}, From, PlayersMap) ->
case maps:is_key(prev_player, PlayersMap) of
%Adds req number of rounds as a key containing player name and ref for postponed reply
false ->
PlayersMap2 = maps:put(prev_player,{Name, From}, PlayersMap),
{noreply, PlayersMap2};
%Sends message back to previous caller and current caller to setup game
true ->
case maps:get(prev_player, PlayersMap, badkey) of
{OtherPlayer, OtherRef} ->
gen_server:reply(OtherRef, {ok, Name}),
PlayersMap2 = maps:remove(prev_player, PlayersMap),
%Make key start to indicate that game is going on, and with References to two players.
PlayersMap3 = PlayersMap2#{start => {From, OtherRef}},
{reply, {ok, OtherPlayer}, PlayersMap3};
_ ->
{reply, error, PlayersMap}
end
end.
handle_cast(_Msg, State) ->
{noreply, State}.
%------------- CALLS to Rps game -------------------
start() ->
{ok, Pid} = gen_server:start(?MODULE,[], []),
{ok, Pid}.
setup_game(Coordinator, Name) ->
gen_server:call(Coordinator, {set_up, Name}, infinity).
move(Coordinator, Choice) ->
gen_server:call(Coordinator, {move, Choice}, infinity).
%--------------- EUnit Test section ------------
setup_test() ->
{"Queue up for a game",
fun() ->
{ok, CoordinatorPid} = mve_rps_game:start(),
Caller = self(),
spawn(fun() -> State = mve_rps_game:setup_game(CoordinatorPid, "player1"),
Caller ! State end),
timer:sleep(1000),
{ok, OtherPlayer} = mve_rps_game:setup_game(CoordinatorPid, "player2"),
ProcessState =
receive
State -> State
end,
?assertMatch(ProcessState, {ok, "player2"}),
?assertMatch({ok, OtherPlayer}, {ok, "player1"})
end}.
queue_up_test_move_rs_both_receive() ->
{"Testing that both players recieve answer in rock to scissor",
fun() ->
{ok, CoordinatorPid} = mve_rps_game:start(),
Caller = self(),
spawn(fun() ->
{ok, _OtherPlayer} = mve_rps_game:setup_game(CoordinatorPid, "player1"),
State = mve_rps_game:move(CoordinatorPid, rock),
Caller ! State
end),
timer:sleep(1000),
{ok, _OtherPlayer} = mve_rps_game:setup_game(CoordinatorPid, "player2"),
Result2 = mve_rps_game:move(CoordinatorPid, scissor),
io:format(user, "Result2: ~w~n",[Result2]),
?assertMatch(Result2, rock),
ProcessState = receive
State -> State
end,
?assertMatch(ProcessState, win)
end}.
test_all() ->
eunit:test(
[
setup_test()
,queue_up_test_move_rs_both_receive()
], [verbose]).
Ok, I finely figured out, and I was not even that far off in my other posts regarding the use and concept of make_ref()
seen here. So the problem is based on a conceptual incompleteness of the pid()
vs. reference()
and From
within gen_server
.
The reason why the code causes a timeout is because the gen_server:reply(OtherRef, Choice)
uses a reference that was saved in the gen_server
s state in a former gen_server:call/3
. Therefore it is trying to reply to a call that already was answered, whereas the new call is not, because the reference/tag of the new call isn't stored.