为什么我的learnyousomeerlang trade_fsm界面失败了?

时间:2018-02-22 09:34:39

标签: concurrency interface erlang

我正在使用learnyousomeerlang site慢慢学习erlang语言,而我现在正在使用#34;愤怒对抗有限状态机"章,它构建并描述了trade_fsm.erl的工作原理。作为我学习过程的一部分,我决定为这个系统编写一个界面,你可以通过输入控制台命令来控制两个交易方。我认为我在写作方面做得不错,但由于某些原因我无法理解,每当我尝试开始交易时,客户端都会崩溃。这是怎么回事:

5> z3:init("a", "b").
true
6> z3:display_pids().
First player pid: {<0.64.0>}
Second player pid: {<0.65.0>}.
done
7> z3:p1_propose_trade().
{a}: asking user <0.65.0> for a trade

{b}: <0.64.0> asked for a trade negotiation

done
8> z3:display_pids().    
done
9> 

这是我的代码:

-module(z3).
-compile(export_all).

-record(state, {player1,
                player2,
                p1items=[],
                p2items=[],
                p1state,
                p2state,
                p1name="Carl",
                p2name="FutureJim"}).


init(FirstName, SecondName) ->
    {ok, Pid1} = trade_fsm:start_link(FirstName),
    {ok, Pid2} = trade_fsm:start_link(SecondName),
    S = #state{p1name=FirstName, p2name=SecondName,
            player1=Pid1, player2=Pid2,
            p1state=idle, p2state=idle},
    register(?MODULE, spawn(?MODULE, loop, [S])).

display_pids() ->
    ?MODULE ! display_pids,
    done.

p1_propose_trade() ->
    ?MODULE ! {wanna_trade, p1},
    done.

p2_accept_trade() ->
    ?MODULE ! {accept_trade, p2},
    done.

loop(S=#state{}) ->
    receive
        display_pids ->
            io:format("First player pid: {~p}~nSecond player pid: {~p}.~n", [S#state.player1, S#state.player2]),
            loop(S);
        {wanna_trade, Player} ->
            case Player of
                p1 ->
                    trade_fsm:trade(S#state.player1, S#state.player2);
                p2 ->
                    trade_fsm:trade(S#state.player2, S#state.player1);
                _ ->
                    io:format("[Debug:] Invalid player.~n")
            end,
            loop(S);
        {accept_trade, Player} ->
            case Player of
                p1 ->
                    trade_fsm:accept_trade(S#state.player1);
                p2 ->
                    trade_fsm:accept_trade(S#state.player2);
                _ ->
                    io:format("[Debug:] Invalid player.~n")
            end,
            loop(S);
        _ ->
            io:format("[Debug:] Received invalid command.~n"),
            loop(S)
    end.

有谁可以告诉我为什么这段代码失败以及应该如何实现?

1 个答案:

答案 0 :(得分:1)

当您致电z3:p1_propose_trade().时,它会将消息{wanna_trade, p1}发送到已注册的流程z3。

消息在循环函数中被解释,该函数调用转换为trade_fsm:trade(S#state.player1, S#state.player2);的{​​{1}}。此调用是一个同步调用,它等待来自fsm的回复,如果没有收到任何答复,则在30秒后超时。

在状态等待中,您已在语句中捕获到该消息:

gen_fsm:sync_send_event(S#state.player1, {negotiate, S#state.player2}, 30000).

没有回复值返回给调用者。你应该在最后一行使用像

这样的东西
idle({negotiate, OtherPid}, From, S=#state{}) ->
    ask_negotiate(OtherPid, self()),
    notice(S, "asking user ~p for a trade", [OtherPid]),
    Ref = monitor(process, OtherPid),
    {next_state, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};

或显式调用gen_fsm:reply / 2。

我没有在代码中挖掘太多,但是如果你把它改为:

    {reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};

它不会停止并且似乎正常工作。

也许有些人完全了解gen_fsm的行为可以解释场景背后的内容(为什么在超时结束时没有打印输出,为什么shell准备好了新的命令,而它应该等待一个答案?):

  • 如果您手动调用函数idle({negotiate, OtherPid}, From, S=#state{}) -> Reply = ask_negotiate(OtherPid, self()), notice(S, "asking user ~p for a trade", [OtherPid]), Ref = monitor(process, OtherPid), {reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}}; ,您将看到它在达到30秒超时之前不会返回,然后您会收到错误消息。
  • trade(OwnPid, OtherPid)调用它时,30秒后未显示错误消息,但已注册的进程z3死亡。

<强> [编辑]

我已经检查了代码应该如何工作,事实上,似乎没有必要修改fsm代码。当第二个用户接受谈判时,回复应该来自进程2。所以你不能这样做测试(循环正在等待答案,它不能发送accept_trade)。这是一个有效的会议:

z3:p1_propose_trade().

您可以更改“wanna_trade”界面以避免阻止问题

{ok,P1} = trade_fsm:start("a1").
{ok,P2} = trade_fsm:start("a2").
T = fun() -> io:format("~p~n",[trade_fsm:trade(P1,P2)]) end.
A = fun() -> io:format("~p~n",[trade_fsm:accept_trade(P2)]) end.
spawn(T). % use another process to avoid the shell to be locked
A().