процесс заблокирован в gen_tcp send/2 из-за команды порта нет возврата

отредактировано 25.11.2015 02:10

Моя версия ejabberd — 14.12 и erlang R17B, поэтому этот код кажется бесполезным, потому что erlang:system_info(otp_release) в R17B повторно запускает «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'}, процесс c2s завершается нормально.

отредактировано 24.11.2015 03:40



Возможно, я нашел способ воспроизвести эту проблему:
1. построить обычное соединение c2s с клиентом xmpp
2. отключить сеть клиента с помощью некоторых инструментов, например. неуклюже (отбрасывает все TCP-пакеты с сервера)
3. Продолжайте отправлять большие пакеты процессу c2s

Сначала gen_tcp:send возвращает значение ok до заполнения буфера отправки
Затем gen_tcp:send перезапускается {ошибка, время ожидания} из-за заполнения буфера отправки
процесс вызывает 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), сокет закрыт, поэтому предыдущий 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;

Но в некоторых случаях pid c2s блокируется на gen_tcp: отправить так

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

В большинстве случаев случалось, когда состояние сети пользователя было не очень хорошим, процесс получателя должен был отправить 2 сообщения pid c2s, а c2s завершал сеанс или ждал возобновления.

{'$gen_event',closed}
{'DOWN',#Ref‹0.0.1201.250595>,process,‹0.19617.245>,normal} < бр/>

Я напечатал очередь сообщений в процессе c2s, 2 сообщения находятся в очереди, ожидая обработки. К сожалению, очередь больше не перемещается, потому что процесс был заблокирован перед обработкой этих сообщений, как описано выше, сложенных в prim_inet:send/3, когда tring выполняет gen_tcp:send/2. Очередь становится очень большой через несколько дней, и 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.


Похоже, что драйвер порта не ответил {inet_reply,S,Status} после erlang:port_command(S, Data, OptList) . функция gen_tcp:send заблокирует бесконечность. Кто-нибудь может это объяснить?


person Gang Zhao    schedule 26.10.2015    source источник
comment
Разве это не более или менее то, что вы ожидаете? Если сетевые условия плохие, драйвер должен так или иначе ставить передачи в очередь, а очередь может быть только такой большой...   -  person Michael    schedule 26.10.2015
comment
Может быть, вы можете решить проблему, установив gen_tcp high_watermark?   -  person Michael    schedule 26.10.2015
comment
И/или high_msgq_watermark.   -  person Michael    schedule 26.10.2015
comment
@Michael Дело не в быстром или медленном, проблема в том, что функция никогда не возвращается после того, как я вызываю gen_tcp:send/2   -  person Gang Zhao    schedule 26.10.2015
comment
Блокирующий сокет всегда будет блокироваться до тех пор, пока данные, которые вы записываете, не будут скопированы в буфер ядра. Обычно это происходит быстро, но если буфер ядра заполнен, он не может вернуться, пока он не опустеет достаточно для копирования данных, которые вы записываете.   -  person Michael    schedule 26.10.2015


Ответы (1)


Это зависит от версии Erlang, которую вы используете. Опция тайм-аута при отправке gen_tcp не используется в старой версии ejabberd, потому что в то время она не была доступна в Erlang. Кроме того, вы должны использовать самую последнюю версию Erlang, так как в самом Erlang были исправлены некоторые ошибки, связанные с этими параметрами.

person Mickaël Rémond    schedule 26.10.2015
comment
Я использую ejabberd-14.12 и otp-17.4, кажется, что они оба имеют тайм-аут отправки gen_tcp в исходном коде. - person Gang Zhao; 27.10.2015
comment
Я нашел способ воспроизвести проблему и отредактировал свой вопрос, можете ли вы поискать меня, спасибо. - person Gang Zhao; 25.11.2015