在对游戏进行测试时,发现在某台服务器上,经常会出现一个错误,而其他服务器表现正常,于是分析日志文件,发现以下错误:
{badarg,[{erlang,is_process_alive,[<6703.38.0>],[]}
检查代码后,发现错误发生在如下的情景下:
玩家连接tcpserver,tcpserver产生玩家状态机实例,然后在状态机中rpc call大厅服务,大厅服务保存了当时的玩家状态机pid,之后的操作均会用到这个pid。
当进行开启了2个tcpserver(即多用户入口)时,由于使用了 is_process_alive来确保pid是可用的,导致这个函数直接报错。
erlang在节点间进行pid通信时,会对pid进行一个换算,在这台特定的服务器上支持了2个入口,所以导致其他服务器正常,而这个服务器报错,编写一个测试实例来进行测试。
nodeserver
%% Copyright
-module(nodeserver).
-author("xj").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
%% API
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% gen_server callbacks
-record(state, {pids}).
init(_Args) ->
{ok, #state{pids = []}}.
handle_call({join}, From, State) ->
Pids = State#state.pids,
Pid = element(1, From),
New_Pids = [Pid | Pids],
io:format("pids is:~p~n", [New_Pids]),
{reply, Pid, State#state{pids = New_Pids}}.
handle_cast({msg, Msg}, State) ->
Pids = State#state.pids,
lists:foreach(fun(X) ->
io:format("pid is allow~p~n",[erlang:is_process_alive(X)]),
X ! Msg
end, Pids),
{noreply, State}.
handle_info(_Request, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
tcpserver
%% Copyright
-module(tcpserver).
-author("xj").
-behaviour(gen_server).
%% API
-export([start_link/0,rpc/0,rpc/2]).
%% gen_server
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
%% API
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% gen_server callbacks
-record(state, {}).
init(_Args) ->
{ok, #state{}}.
handle_call({join}, _From, State) ->
Info = gen_server:call({nodeserver,'server@127.0.0.1'},{join}),
{reply, Info,State}.
handle_cast({msg,Msg}, State) ->
gen_server:cast({nodeserver,'server@127.0.0.1'},{msg,Msg}),
{noreply, State}.
handle_info(Info, State) ->
io:format("msg:~p~n",[Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% RPC
rpc() ->
gen_server:call(?MODULE,{join}).
rpc(msg,Msg) ->
gen_server:cast(?MODULE,{msg,Msg}).
在节点S开启nodeserver,在节点A和B分别启动一个tcpserver,然后分别执行加入,再执行广播,确实也遇到了相同的错误。
在去掉 is_process_alive后,消息正常到达A和B。
接下来想分析 is_process_alive是如何工作的,经过查找资料,is_process_alive只能判断本节点的进程,而我的需求明显是远程节点,所以添加一个函数来代替它:
Node = node(Pid),
rpc:call(Node,erlang, is_process_alive, [Pid]).
完成,收工。