在我的代码中,让gen_server:reply
在某些情况下(但不是所有情况下)工作有问题,尽管在我看来,代码在结构上从它工作的领域和不工作的领域来看是相似的。我不知道这是由于一些概念上的误解还是gen_server:reply/
的不完整。
我已经创建了如下所示的MRE代码(带有EUnit测试,并且都准备好即插即用)。我发现测试函数setup_test()
成功,而函数setup_test_move_rs_both_receive()
没有成功。后者为mve_rps_game:move/2
函数创建了一个“挂起”/超时。
为什么会这样?我该如何应对?
-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]).
2条答案
按热度按时间wgmfuz8q1#
好吧,我很好地理解了,我甚至在我的其他帖子中也没有离这里看到的
make_ref()
的使用和概念太远,所以问题是基于pid()
与gen_server
中的reference()
和From
在概念上的不完整。该代码导致超时的原因是
gen_server:reply(OtherRef, Choice)
使用了一个引用,该引用在以前的gen_server:call/3
中保存在gen_server
s状态下。因此,它试图回复一个已经应答的呼叫,而新呼叫没有回复,因为新呼叫的引用/标记没有存储。lmyy7pcs2#
我没有找到解决您的问题的方法,但我可以为您指出代码中出错的地方...
当从
handle_call/3
的第一个子句调用函数make_move/5
时,您传递的是p1_move, p2_move
或p2_move, p1_move
作为最后一个参数,但这些键从未添加到PlayersMap
中,因此maps:get/3
将始终返回badkey
。在这种情况下,您不会回复客户端(即,您返回
{noreply, PlayersMap2}
,并且您不会在任何地方保留来自handle_call/3
的From
......因此,本质上,您不会响应任何对mve_rps_game:move/2
的调用。该函数使用infinity
作为其gen_server:call/3
的超时值,因此......最终测试本身超时。我不完全确定修复 * 应该 * 是什么,但您可能应该从检查如何/何时在
make_move/5
(或handle_call/3
的第一个子句)中响应客户端开始。编辑:发现问题
(For此变更的解释,请检查以下备注)
解决此问题的方法是跟踪每个用户收到的最后一封邮件,而不仅仅是第一封邮件。进行以下更改,您将能够解决超时问题。您的测试仍将失败,但原因不同...
主要的变化是
PlayersMap
内的start
的调整。事实上,那个项目应该被称为last_message
或类似的东西,但我会留给你。