[英]Why does gen_server:reply/2 work in some instances while causing timeouts in others
我在讓gen_server:reply
在我的代碼中的某些但不是所有情況下工作時遇到問題,盡管在我看來代碼在結構上與它工作的區域相似,但它不工作。 我不知道這是否是由於一些概念上的誤解或gen_server:reply/
的不完整。
我已經創建了如下所示的 MRE 代碼(使用 EUnit 測試並准備好即插即用)我體驗到測試 function setup_test()
成功而 function setup_test_move_rs_both_receive()
沒有。 后者為mve_rps_game:move/2
function 創建了一個“掛起”/超時。
為什么會這樣,我該如何處理?
-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]).
好的,我很清楚,在我的其他帖子中,關於make_ref()
的使用和概念在這里看到的,我什至沒有那么遠。 所以問題是基於pid()
與reference()
和From
在gen_server
中的概念不完整性。
代碼導致超時的原因是因為gen_server:reply(OtherRef, Choice)
使用了一個引用,該引用保存在gen_server
的 state 之前的gen_server:call/3
中。 因此它試圖回復一個已經被接聽的電話,而新的電話沒有,因為新電話的參考/標簽沒有被存儲。
我沒有找到解決您問題的方法,但我可以指出代碼中出現問題的地方……
當您從handle_call/3
的第一個子句調用 function make_move/5
時,您傳遞的是p1_move, p2_move
或p2_move, p1_move
作為最后一個參數,但這些鍵永遠不會添加到PlayersMap
中,因此maps:get/3
將始終返回badkey
。
在那種情況下,您不會回復客戶端(即您返回{noreply, PlayersMap2}
並且您不會在任何地方保留From
handle_call/3
的發件人……因此,從本質上講,您永遠不會回復對mve_rps_game:move/2
的任何調用mve_rps_game:move/2
. function 使用infinity
作為gen_server:call/3
的超時值,所以……最終測試本身超時。
我不完全確定修復應該是什么,但您可能應該首先檢查如何/何時在make_move/5
(或handle_call/3
的第一個子句)中響應您的客戶端。
(有關此更改的解釋,請查看下面的評論)
解決這個問題的方法是跟蹤每個用戶收到的最后一條消息,而不僅僅是第一條。 進行以下更改,您將能夠解決超時問題。 你的測試仍然會失敗,但出於不同的原因......
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;
主要變化是PlayersMap
中start
的調整。 事實上,該項目應該被稱為last_message
或類似的東西,但我會把它留給你。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.