编辑于 2015-11-25 02:10
我的 ejabberd 版本是 14.12 和 erlang R17B,所以这段代码似乎没用,因为 R17B 中的 erlang:system_info(otp_release) 重新运行“17”
ejabberd_listener.erl
SockOpts2 =
try erlang:system_info(otp_release) >= "R13B" of
true -> [{send_timeout_close, true} | SockOpts];
false -> SockOpts
catch
_:_ -> []
end,
我在监听选项中手动添加了 {send_timeout_close, true},我的问题似乎得到了解决,因为套接字在发送超时的同时关闭,尝试在队列中发送后续消息会收到 {error,enotconn} 响应。当 {gen_event, 'closed'} msg 来临时,c2s 进程正常终止。
编辑于 2015-11-24 03:40
也许我找到了重现此问题的方法:
1. 与 xmpp 客户端建立正常的 c2s 连接
2. 使用一些工具切断客户端的网络,例如。笨拙(从服务器丢弃所有 tcp 数据包)
3. 继续向 c2s 进程发送大数据包
首先,gen_tcp:send 在 sendbuffer 填充之前返回 ok
然后,gen_tcp:send 重新运行 {error,timeout} 因为 sendbuffer 已填充
进程调用 ejabberd_socket:close(Socket) 关闭连接
send_text(StateData, Text) when StateData#state.mgmt_state == active ->
catch ?INFO_MSG("Send XML on stream = ~ts", [Text]),
case catch (StateData#state.sockmod):send(StateData#state.socket, Text) of
{'EXIT', _} ->
(StateData#state.sockmod):close(StateData#state.socket),
error;
_ ->
ok
结尾;
但是 ejabberd_socket:close/1 似乎是一个异步调用,所以 c2s 进程会处理 message_queue 中的下一条消息,继续调用 gen_tcp:send/2,等待一个 send_timeout。
但是此时ejabberd_receiver调用了gen_tcp:close(Socket),socket关闭了,所以之前的gen_tcp:send/2永远不会返回。我已经用这种方法尝试了几次,它发生了 100%。
简而言之,如果我将数据包发送到无法接收数据包的客户端套接字并且发送缓冲区已满,我会在发送超时后收到{错误,超时}。但是,如果另一个异步进程在我等待 gen_tcp:send/2 的发送超时时关闭了套接字,我将永远不会得到响应。
所以,我用 erl 做了这个,而 gen_tcp:send/2 没有响应(在第 3 步切断网络,继续发送数据包,异步关闭)。我想知道这是一个问题还是因为我自己的原因?
下面是原帖
通常在 ejabberd 中,我将消息路由到客户端进程,通过此函数发送到 tcp 套接字。它在大多数时候都运行良好。 模块 ejabberd_c2s.erl
send_text(StateData, Text) when StateData#state.mgmt_state == active ->
catch ?INFO_MSG("Send XML on stream = ~ts", [Text]),
case catch (StateData#state.sockmod):send(StateData#state.socket, Text) of
{'EXIT', _} ->
(StateData#state.sockmod):close(StateData#state.socket),
error;
_ ->
ok
end;
但在某些情况下,c2s pid 像这样在 gen_tcp:send 上阻塞
erlang:process_info(pid(0,8353,11)).
[{current_function,{prim_inet,send,3}},
{initial_call,{proc_lib,init_p,5}},
{status,waiting},
{message_queue_len,96},
{messages ...}
...
大多数情况发生在用户的网络状态不太好时,接收进程应该向 c2s pid 发送 2 条消息,c2s 将终止会话或等待恢复
{'$gen_event',关闭}
{'DOWN',#Ref<0.0.1201.250595>,进程,<0.19617.245>,正常}
我在c2s进程中打印了消息队列,2个消息在队列中,等待处理。不幸的是,队列不再移动,因为进程在处理这些消息之前已经阻塞,如上所述,在尝试执行 gen_tcp:send/2 时堆叠在 prim_inet:send/3。几天后队列变得非常大,当进程要求更多内存时,ejabberd 崩溃。
prim_inet:send/3 source :
send(S, Data, OptList) when is_port(S), is_list(OptList) ->
?DBG_FORMAT("prim_inet:send(~p, ~p)~n", [S,Data]),
try erlang:port_command(S, Data, OptList) of
false -> % Port busy and nosuspend option passed
?DBG_FORMAT("prim_inet:send() -> {error,busy}~n", []),
{error,busy};
true ->
receive
{inet_reply,S,Status} ->
?DBG_FORMAT("prim_inet:send() -> ~p~n", [Status]),
Status
end
catch
error:_Error ->
?DBG_FORMAT("prim_inet:send() -> {error,einval}~n", []),
{error,einval}
end.
似乎端口驱动程序在 erlang:port_command(S, Data, OptList) 之后没有回复 {inet_reply,S,Status} 。gen_tcp:send 函数会阻塞无穷大,谁能解释一下?