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



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

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) (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}.

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

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

如果我理解正确,这意味着服务器<0.566.0>死了。

您应该从 OTP 设计原则用户指南中的 6.1 简单调试开始。

如果它没有帮助,请尝试使用Runtime_Tools中的 dbg 模块。看看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,也可以尝试观察器。

无论如何,创建最小、完整和可验证的示例将有助于更好地回答您的问题。

最新更新