跨节点调用Erlang gen_server时出现noproc错误求助
问题场景
同一机器上运行两个Erlang节点,共享cookie,双向net_adm:ping/1连通正常。back@<ip>节点上的gen_server通过rebar3的supervisor启动无报错,front@<ip>节点用global:registered_names/0能看到注册名back_server。但调用erpc:call('back@<ip>', back_server, getState, [])时,虽然能打印出getState called,却抛出错误:
** exception exit: {exception,{noproc,{gen_server,call,[back_server,get]}}}
问题代码
-module(back_server). -behaviour(gen_server). -export([start/0,start/3,stop/0,setState/1,getState/0]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). start() -> gen_server:start_link({global, ?MODULE}, ?MODULE, [], []). start(Registration_type,Name,Args) -> gen_server:start_link({Registration_type, Name}, ?MODULE, Args, []). stop() -> gen_server:call(?MODULE, stop). setState(NewState)-> io:format("setState called~n"), gen_server:cast(?MODULE,{update,NewState}). getState()-> io:format("getState called~n"), gen_server:call(?MODULE,get). init([]) -> {ok,started}. handle_call(get, _From, State) -> {reply,State,State}; handle_call(stop, _From, _State) -> {stop,normal, stopped, down}. handle_cast({update,Value}, State) -> io:format("changing state from ~p to ~p~n",[State,Value]), {noreply,Value}; handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}.
原因分析与解决方法
问题核心是:你的gen_server用全局注册方式启动({global, ?MODULE}),但getState/0、setState/1、stop/0这些函数内部调用gen_server时,用的是本地注册名?MODULE。
当erpc:call远程触发back_server:getState()时,这个函数在back@<ip>节点执行,此时gen_server:call(?MODULE, get)会去本地进程注册表找back_server,但你的进程只注册在全局表里,本地表没有对应记录,所以报错noproc(找不到进程)。
只需要把这些函数里的调用目标改成全局注册的格式即可:
修改后的关键代码片段:
stop() -> gen_server:call({global, ?MODULE}, stop). setState(NewState)-> io:format("setState called~n"), gen_server:cast({global, ?MODULE},{update,NewState}). getState()-> io:format("getState called~n"), gen_server:call({global, ?MODULE},get).
这样,函数执行时会正确通过全局注册表找到对应的gen_server进程,就不会再报找不到进程的错误了。
补充说明:既然global:registered_names/0能看到back_server,说明supervisor启动gen_server时确实用了全局注册(不管是调用start/0还是start/3传了global类型),这部分没问题,无需修改启动逻辑。
内容的提问来源于stack exchange,提问作者Lee

