簡體   English   中英

為什么 gen_server:reply/2 在某些情況下工作而在其他情況下導致超時

[英]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()Fromgen_server中的概念不完整性。

代碼導致超時的原因是因為gen_server:reply(OtherRef, Choice)使用了一個引用,該引用保存在gen_server的 state 之前的gen_server:call/3中。 因此它試圖回復一個已經被接聽的電話,而新的電話沒有,因為新電話的參考/標簽沒有被存儲。

我沒有找到解決您問題的方法,但我可以指出代碼中出現問題的地方……

當您從handle_call/3的第一個子句調用 function make_move/5時,您傳遞的是p1_move, p2_movep2_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;

主要變化是PlayersMapstart的調整。 事實上,該項目應該被稱為last_message或類似的東西,但我會把它留給你。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM