简体   繁体   English

为什么 gen_server:reply/2 在某些情况下工作而在其他情况下导致超时

[英]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()Fromgen_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_movep2_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的第一个子句)中响应您的客户端。

Edit: Found the Issue编辑:发现问题

(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 .主要变化是PlayersMapstart的调整。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM