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

问题描述:

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

10秒后,时钟会超时,该代码被称为:

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) ([email protected])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}. 

我感到困惑的是它似乎是从调用表(主机变量)崩溃超时但我没有看到有关表代码的任何堆栈跟踪。

s:s(Pid, Msg)是方便gen_server:call(Pid, Msg)

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

编辑:因为timeout

改变了timeout原子clockdone具有二郎特殊情况。

现在得到这个崩溃:

=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}]}]} 

这要是我的理解对不对,意味着服务器< 0.566.0>死亡。

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

如果它没有帮助尝试使用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}而不是抛出一个例外,但我不记得细节,不知道这是你的麻烦的原因。如果你喜欢GUI,你也可以试试observer

无论如何创建Minimal, Complete, and Verifiable example将有助于很多获得更好的答案你的问题。

+0

谢谢。我更改了'timeout'原子''我自己的'clockdone'的原子,并且出现超时异常,所以看起来我的服务器因为其他原因而死亡。 – quantumpotato