[英]Why does gen_server:reply/2 work in some instances while causing timeouts in others
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.我在让
gen_server:reply
在我的代码中的某些但不是所有情况下工作时遇到问题,尽管在我看来代码在结构上与它工作的区域相似,但它不工作。 And I don't know if this is due to some conceptual misunderstanding or incompleteness of the gen_server:reply/
.我不知道这是否是由于一些概念上的误解或
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.我已经创建了如下所示的 MRE 代码(使用 EUnit 测试并准备好即插即用)我体验到测试 function
setup_test()
成功而 function setup_test_move_rs_both_receive()
没有。 The latter creates a 'hanging'/time out for the mve_rps_game:move/2
function.后者为
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 .好的,我很清楚,在我的其他帖子中,关于
make_ref()
的使用和概念在这里看到的,我什至没有那么远。 So the problem is based on a conceptual incompleteness of the pid()
vs. reference()
and From
within gen_server
.所以问题是基于
pid()
与reference()
和From
在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
.代码导致超时的原因是因为
gen_server:reply(OtherRef, Choice)
使用了一个引用,该引用保存在gen_server
的 state 之前的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.因此它试图回复一个已经被接听的电话,而新的电话没有,因为新电话的参考/标签没有被存储。
I didn't find a way to solve your issue but I can point you to the place in the code where things are going astray…我没有找到解决您问题的方法,但我可以指出代码中出现问题的地方……
When you call the function make_move/5
from the first clause of handle_call/3
, you're passing is as the last parameters either p1_move, p2_move
or p2_move, p1_move
, but those keys are never added to the PlayersMap
so maps:get/3
will always return badkey
.当您从
handle_call/3
的第一个子句调用 function make_move/5
时,您传递的是p1_move, p2_move
或p2_move, p1_move
作为最后一个参数,但这些键永远不会添加到PlayersMap
中,因此maps:get/3
将始终返回badkey
。
In that case you're not replying to the client (ie you return {noreply, PlayersMap2}
and you don't keep the From
from handle_call/3
anywhere… So, in essence, you never respond to any call to mve_rps_game:move/2
. That function uses infinity
as its time-out value for gen_server:call/3
, so… eventually the tests themselves timeout.在那种情况下,您不会回复客户端(即您返回
{noreply, PlayersMap2}
并且您不会在任何地方保留From
handle_call/3
的发件人……因此,从本质上讲,您永远不会回复对mve_rps_game:move/2
的任何调用mve_rps_game:move/2
. function 使用infinity
作为gen_server:call/3
的超时值,所以……最终测试本身超时。
I'm not entirely sure what the fix should be, but you should probably start by checking how/when to respond to your client in make_move/5
(or in the first clause of handle_call/3
).我不完全确定修复应该是什么,但您可能应该首先检查如何/何时在
make_move/5
(或handle_call/3
的第一个子句)中响应您的客户端。
(For an explanation of this change, check the comments below) (有关此更改的解释,请查看下面的评论)
The way to solve the issue is to keep track of the last message received by each user and not just the first one.解决这个问题的方法是跟踪每个用户收到的最后一条消息,而不仅仅是第一条。 Making the following changes you'll be able to move past the timeout issue.
进行以下更改,您将能够解决超时问题。 Your tests will still fail, but for a different reason…
你的测试仍然会失败,但出于不同的原因......
case CurId of
P1id ->
make_move(Choice, P2Ref, PlayersMap#{start := {From, P2Ref}}, p1_move, p2_move);
P2id ->
make_move(Choice, P1Ref, PlayersMap#{start := {P1Ref, From}}, p2_move, p1_move);
_Other ->
{reply, {error, not_a_player}, PlayersMap}
end;
The main chang is the adjustment of start
within PlayersMap
.主要变化是
PlayersMap
中start
的调整。 As a matter of fact, that item should be called last_message
or something along those lines, but I would leave that up to you.事实上,该项目应该被称为
last_message
或类似的东西,但我会把它留给你。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.