gen_server call timeout
erlang 的 gen_server call 默认有超时, 若在指定的超时内没有收到返回. 则会 exit(timeout).
gen_server call
gen.erl:160
do_call(Process, Label, Request, Timeout) when is_atom(Process) =:= false ->
Mref = erlang:monitor(process, Process),
%% OTP-21:
%% Auto-connect is asynchronous. But we still use 'noconnect' to make sure
%% we send on the monitored connection, and not trigger a new auto-connect.
%%
erlang:send(Process, {Label, {self(), Mref}, Request}, [noconnect]),
receive
{Mref, Reply} ->
erlang:demonitor(Mref, [flush]),
{ok, Reply};
{'DOWN', Mref, _, _, noconnection} ->
Node = get_node(Process),
exit({nodedown, Node});
{'DOWN', Mref, _, _, Reason} ->
exit(Reason)
after Timeout ->
erlang:demonitor(Mref, [flush]),
exit(timeout)
end.
什么时候应该捕获 timeout?
显然, 如果等待一段时间后, 没有收到消息, 有两种可能.
- 对端未能返回消息.
- 对端返回了消息. 但调用端尚未收到.
catch timeout 异常和处理过期的 {Mref, Reply} 返回
如果捕获了 timeout 异常, 对方又返回了消息, 该消息还是会发送到调用者的 pid 信箱中.
若是一个 gen_server, 需要在 handle_info 中处理 (建议忽略) 该消息. 避免因为没有在 handle_info 中处理 {Mref, Reply} 消息而崩溃.
幂等
超时不意味着调用失败, 可能调用成功, 只是请求超时. 可以使用幂等接口设计应对超时失败.
合适的超时时间
假设有如下调用链.
a_node -5000ms> b_node -5000ms> outer_services
若 outer_services, 即不可控的外部服务超时, 会导致 a_node, b_node 的 call 全部超时. 建议在 b_node 上对外使用较小的超时时间. 避免外部超时让整条调用链都出现超时错误.