Erlang服务器在从计时器模块超时后崩溃

时间:2017-09-05 00:21:54

标签: timer erlang gen-server

我有一个表模块。当它作为gen_server启动时,它会从Clock模块创建2个服务器 - 一个用于X播放器,另一个用于O播放器。

10秒后,Clock将超时,并调用此代码:

updateTick(Delta, {{Time, ticking, _}, Host}) ->
  Now = Time - Delta,
  case Now of
    Now when Now > 0 ->
      {{Now, ticking, intervalRef()}, Host};
    _ ->
      gen_server:call(Host, timeout),
      {{0, out, none}, Host}
  end;

我希望后一条款可以解雇。

这是我得到的崩溃:

Eshell V8.2  (abort with ^G) (tunnel@127.0.0.1)1> Clock.{{1000,paused,none},<0.532.0>} Clock.{{20.823899999999803,ticking,#Ref<0.0.1.603>},<0.532.0>}

=ERROR REPORT==== 4-Sep-2017::20:10:19 ===
** Generic server <0.536.0> terminating
** Last message in was {tick,25.099}
** When Server state == {{20.823899999999803,ticking,#Ref<0.0.1.603>},
                         <0.532.0>}
** Reason for termination ==
** {{timeout,{gen_server,call,[<0.532.0>,timeout]}},
    [{gen_server,call,2,[{file,"gen_server.erl"},{line,204}]},
     {clock,updateTick,2,[{file,"src/clock.erl"},{line,27}]},
     {clock,handle_call,3,[{file,"src/clock.erl"},{line,35}]},
     {gen_server,try_handle_call,4,[{file,"gen_server.erl"},{line,615}]},
     {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,647}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

使用timeout调用gen_server是表服务器。时钟保持对表的引用,以便在时钟超时时向它们发送timeout消息,因此表可以取消其他消息并执行其他逻辑。

这是我的完整clock.erl

-module(clock).
-compile(export_all).

-define(STARTING, 1000).
-define(INTERVAL, 250).

init([Host]) -> 
  {ok, defaultState(Host)}.

defaultState(Host) ->
  {{?STARTING, paused, none}, Host}.

tickPid(Pid, Then) ->
  Delta = timer:now_diff(erlang:timestamp(), Then) / 10000,
  s:s(Pid, {tick, Delta}).

intervalRef() ->
 {ok, {_, Tref}} = timer:apply_after(?INTERVAL, ?MODULE, tickPid, [self(), erlang:timestamp()]),
 Tref.

updateTick(Delta, {{Time, ticking, _}, Host}) ->
  Now = Time - Delta,
  case Now of
    Now when Now > 0 ->
      {{Now, ticking, intervalRef()}, Host};
    _ ->
      s:s(Host, timeout),
      {{0, out, none}, Host}
  end;

updateTick(_, State) ->
  State.

handle_call({tick, Delta}, _, State) ->
  State2 = updateTick(Delta, State),
  {reply, State2, State2};
handle_call(info, _, State) ->
  {reply, State, State};
handle_call(pause, _, {{Time, ticking, Tref}, Host}) ->
  timer:cancel(Tref),
  State2 = {{Time, paused, none}, Host},
  {reply, State2, State2};
handle_call(start, _, {{Time, paused, _}, Host}) ->
  {ok, Tref} = timer:apply_after(?INTERVAL, ?MODULE, tickPid, [self(), erlang:timestamp()]),
  State2 = {{Time, ticking, Tref}, Host},
  {reply, State2, State2};
handle_call(stop, _From, State) ->
  {stop, normal, shutdown_ok, State};
handle_call(_, _, State) ->
  {reply, State, State}.

terminate(_, State = {{_,_, none}, _}) ->
    io:format("Clock.~p~n", [State]),
    ok;
terminate(_, State = {{_,_,Tref}, _}) ->
    timer:cancel(Tref),
    io:format("Clock.~p~n", [State]),
    ok.
code_change(_OldVsn, State, _Extra) ->
    {ok, State}. 
handle_cast(_, State) ->
    {noreply, State}.
handle_info(Msg, State) ->
  io:format("Unexpected message: ~p~n",[Msg]),
  {noreply, State }.

go(Host) ->
  gen_server:start_link(?MODULE, [Host], []).

和完整table.erl

-module(table).
-compile(export_all).
-behavior(gen_server).

-define(POSTGAME_TIMEOUT, 6000).

otherPlayer(x) -> o;
otherPlayer(o) -> x.

processRecentTaken(true) -> 1;
processRecentTaken(false) -> 0.

processResult({error, Error, _Board}, State) ->
  {{error, Error}, State};
processResult({playing, NewBoard, Slices, RecentTaken},   {Board, Status, Players}) ->
  % update recent taken
  CurrentPlayer = maps:get(current_player, Status),
  OtherPlayer = otherPlayer(CurrentPlayer),
  {OClock, OActions} = maps:get(OtherPlayer, Players),
  s:s(OActions, {recent_bonus, processRecentTaken(RecentTaken)}),
  % update slice bonus
  {CClock, CActions} = maps:get(CurrentPlayer, Players),
  Made = s:s(CActions, {made, Slices}),
  Status2 = case Made of
    {over, _} ->
      s:s(Board, {cycle, OtherPlayer}),
      s:s(CClock, pause),
      s:s(OClock, start),
      maps:put(current_player, OtherPlayer, Status);
    _ -> Status
  end,
  {{ok, NewBoard}, {Board, Status2, Players}};
processResult({Win, NewBoard, _Slices, _RecentTaken}, State) ->
  {{ok, NewBoard}, winGame(Win, State)}.

markAsFinished(Pid, _Timestamp) ->
  s:s(Pid, finished).

winGame(x, State) ->
  winGame(xWin, State);
winGame(o, State) ->
  winGame(oWin, State);
winGame(Win, {Board, Status, Players}) ->
  CurrentPlayer = maps:get(current_player, Status),
  OtherPlayer = otherPlayer(CurrentPlayer),
  {OClock, _} = maps:get(OtherPlayer, Players),
  CurrentPlayer = maps:get(current_player, Status),
  {CClock, _} = maps:get(CurrentPlayer, Players),
  s:s(OClock, pause),
  s:s(CClock, pause),
  Status2 = maps:put(result, Win, Status),
  {ok, _Tref} = timer:apply_after(?POSTGAME_TIMEOUT, ?MODULE, markAsFinished, [self(), erlang:timestamp()]),
  {Board, Status2, Players}.

handle_call({place, Action, Player, Position}, _, State = {Board, Status, _Players}) ->
% TODO: check for is playing
  CurrentPlayer = maps:get(current_player, Status),
  Result = s:s(Board, {place, CurrentPlayer, {Action, Player, Position}}),
  {Response, State2} = processResult(Result, State),
  {reply, Response, State2};
handle_call(timeout, TimeoutPid, State = {_Board, _Status, Players}) ->
  TimeoutPlayer = getPlayerForClockPid(TimeoutPid, Players),
  WinningPlayer = otherPlayer(TimeoutPlayer),
  {Res, State2} = winGame(WinningPlayer, State),
  {reply, Res, State2};
handle_call(finished, _, {Board, Status, Players}) ->
  Status2 = maps:put(result, finished, Status),
  State2 = {Board, Status2, Players},
  {reply, State2, State2};
handle_call(assign_players, _, {Board, Status}) ->
  Players = createPlayers(self()),
  State2 = {Board, Status, Players},
  {reply, State2, State2};
handle_call(info, _, State = {Board, Status, #{x := X, o := O}}) ->
  BoardInfo = s:s(Board, info),
  RX = playerInfo(X),
  RO = playerInfo(O),
  Res = #{board => BoardInfo,
          status => Status, 
          players => #{x => RX, o => RO}},
  {reply, Res, State};
handle_call(_, _, State) ->
  {reply, State, State}.

playerInfo({Clock, Actions}) ->
  {Next, Current} = s:s(Actions, info),
  {{Time, _ ,_}, _} = s:s(Clock, info),
  #{clock => Time, actions => #{next => Next, current => Current}}.

getPlayerForClockPid(ClockPid, Players) ->
  getPlayerForClockPid(ClockPid, Players, maps:keys(Players)).
getPlayerForClockPid(ClockPid, Players, [H | T]) ->
  case maps:get(H, Players) of
    {ClockPid, _} -> H,
    getPlayerForClockPid(ClockPid, Players, T)
  end.

actionProcess(x) -> actions:go(1);
actionProcess(o) -> actions:go(2).

playerProcesses(Pid, Player) ->
  {ok, Clock} = clock:go(Pid),
  {ok, Actions} = actionProcess(Player),
  {Clock, Actions}.

playerNames() ->
  [x, o].

createPlayers(Self) ->
  createPlayers(Self, playerNames(), #{}).
createPlayers(_Self, [], Players) ->
  Players;
createPlayers(Self, [H | T], Players) ->
  createPlayers(Self, T, maps:put(H, playerProcesses(Self, H), Players)).

defaultStatus() ->
  #{current_player => x,
    result => playing}.

init([]) -> 
  {ok, Board} = board:go(),
  Status = defaultStatus(),
  {ok, {Board, Status}}.

go() ->
  {ok, Pid} = gen_server:start_link(?MODULE, [], []),
  s:s(Pid, assign_players),
  {ok, Pid}.

terminate(_, State = {_Board, Status, Players}) ->
  % gen_server:stop(Board),
  CurrentPlayer = maps:get(current_player, Status),
  OtherPlayer = otherPlayer(CurrentPlayer),
  {OClock, OActions} = maps:get(OtherPlayer, Players),
  CurrentPlayer = maps:get(current_player, Status),
  {CClock, CActions} = maps:get(CurrentPlayer, Players),
  gen_server:stop(OClock),
  gen_server:stop(CClock),
  gen_server:stop(OActions),
  gen_server:stop(CActions),
  io:format("Table Terminating.~p~n", [State]),
  ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}. 

handle_cast(_, State) ->
    {noreply, State}.

handle_info(Msg, State) ->
  io:format("Unexpected message: ~p~n",[Msg]),
  {noreply, State}.

我感到困惑的是,它似乎是因为超时调用Table(Host变量)而崩溃,但我没有看到有关Table代码的堆栈跟踪。

s:s(Pid, Msg)gen_server:call(Pid, Msg)提供了便利。

如何调试导致崩溃的原因?

编辑:

timeout原子更改为clockdone,因为timeout在Erlang中有特殊情况。

现在崩溃了:

=ERROR REPORT==== 5-Sep-2017::11:37:07 ===
** Generic server <0.570.0> terminating
** Last message in was {tick,25.1116}
** When Server state == {{20.927900000000147,ticking,#Ref<0.0.5.642>},
                         <0.566.0>}
** Reason for termination ==
** {{timeout,{gen_server,call,[<0.566.0>,clockdone]}},
    [{gen_server,call,2,[{file,"gen_server.erl"},{line,204}]},
     {clock,updateTick,2,[{file,"src/clock.erl"},{line,27}]},
     {clock,handle_call,3,[{file,"src/clock.erl"},{line,35}]},
     {gen_server,try_handle_call,4,[{file,"gen_server.erl"},{line,615}]},
     {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,647}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

如果我理解正确,则表示服务器&lt; 0.566.0&gt;死亡。

1 个答案:

答案 0 :(得分:2)

您应该从6.1 Simple Debugging开始使用OTP Design Principles User's Guide

如果无效,请尝试使用dbg中的Runtime_Tools模块。看gen_server:call/2。似乎在https://github.com/erlang/otp/blob/master/lib/stdlib/src/gen_server.erl#L206处生成了异常,表示catch gen:call(Host, '$gen_call', timeout)由于某种原因返回{'EXIT',{timeout,{gen_server,call,[<0.532.0>,timeout]}}}。你可以更深入地了解它为什么会发生。我依稀记得有一些技巧或问题导致gen:call/3可以返回{'EXIT',Reason}而不是抛出异常,但我不记得细节,也不知道这是你麻烦的原因。< / p>

如果您喜欢GUI,也可以尝试observer

无论如何,创建Minimal, Complete, and Verifiable example可以帮助您更好地回答您的问题。