Таймер:apply_interval/4 останавливается, когда пользователь выходит из сети и снова возвращается в ejabberd

Я использую ejabberd-17.03 из исходного кода на Linux-машине.

Я программно создал временный чат с сервера, используя jid пользователя A, и отправил прямое приглашение пользователю B, которое он принял, и присоединился к чату.

Мой вариант использования заключается в том, что два пользователя A и B находятся в чате и обмениваются сообщениями. Если ни один пользователь не отправляет другому пользователю какое-либо сообщение в течение 30 секунд, комната отправляет случайно выбранное сообщение этим двум пользователям.

Я реализовал это следующим образом:

start(_Host, _Opts) ->
   ejabberd_hooks:add(user_send_packet, _Host, ?MODULE, myMessage, 95).

stop(_Host) ->
   ejabberd_hooks:delete(user_send_packet, _Host, ?MODULE, myMessage,95).

depends(_Host, _Opts)->[{?MODULE,soft}].

mod_opt_type(_Option)->
   ok.

myMessage({#message{from = From, to = To, body= Body} =Packet, C2SState}) ->
   {UserA,UserB}=select_user(Packet),
   PacketType=returnPacketType(Packet),
   if
      (PacketType==normal) ->
         dosomething(),
         {Timer_Result,Ref_or_Reason} = timer:apply_interval(30000, ?MODULE, func(), [Arguments]),
         if
            (Timer_Result == ok)->
               ets:insert(ref_table, {Key, Ref_or_Reason});
            (Timer_Result == error)->
               io:format(" Could not delete user after timeout, Reason is ~p~n",[Ref_or_Reason])
         end;
      (PacketType==groupchat)->
         do_something_else(),
         {Timer_Result,Ref_or_Reason} = timer:apply_interval(30000, ?MODULE, func(), [Arguments]),
         if
            (Timer_Result == ok)->
               replace_old_ref_with_new(Key, Ref_or_Reason, ref_table);
            (Timer_Result == error)->
               io:format(" Could not delete user after timeout, Reason is ~p~n",[Ref_or_Reason])
         end
   end
   if
      (somecondition()==true)->
         delete_ref(Key, ref_table);
      True->
         do_nothing
   end,    
   {Packet, C2SState}.

Теперь все работает нормально, за исключением следующего случая:

1. Создается чат, и начинается обмен сообщениями между A и B, и в этот момент также запускается таймер.

  1. Если пользователь, создавший чат, уходит в автономный режим, скажем, в момент времени T (сворачивая приложение и удаляя его с устройства Android) и возвращается в сеть, таймер останавливается, как в запланированной функции. в конце 30 секунд не вызывается. (здесь возвращается в сеть выделено, потому что, если пользователь не подключается к сети, таймер работает должным образом, только когда пользователь снова подключается к сети, таймер останавливается и нет журналов генерируются).

    Но если онлайн-пользователь отправляет какое-либо сообщение сейчас в этот момент в T, то вся периодическая операция по случайному выбору сообщений и их отправке клиентам начинается снова довольно хорошо и заканчивается хорошо.

    Но если онлайн-пользователь не отправляет никаких сообщений в этот момент времени T, то запланированный таймер никогда не вызывается, и пользователь продолжает ждать.

  2. Если пользователь, которого пригласили в чат, выходит из сети в момент времени T2 (скажем, как в случае 2) и снова подключается к сети, то таймер остается активным и работает как положено.

Поэтому я изменил уровень ведения журнала ejabberd на 5 и увидел, что офлайн-сообщения офлайн-пользователю и снова онлайн-пользователю не доставляются, даже несмотря на то, что mod_offline включен в ejabberd.yml.

Журнал :

#message{
    id = <<>>,type = error,lang = <<"en">>,
    from = 
        {jid,<<"fWiTvj973AB”>>,<<“example.com">>,<<"Smack">>,<<"fwitvj973ab”>>,
            <<"example.com">>,<<"Smack">>},
    to = 
        {jid,<<"ac5a6b8c-66b8-4da7-8b1a-0f3ecb1e5gfd”>>,
            <<"conference.example.com">>,<<"cXWmOrqEESd”>>,
            <<"ac5a6b8c-66b8-4da7-8b1a-0f3ecb1e5gfd">>,
            <<"conference.example.com">>,<<"cXWmOrqEESd">>},
    subject = [#text{lang = <<>>,data = <<>>}],
    body = [#text{lang = <<>>,data = <<"\"cXWmOrqEESd\"">>}],
    thread = undefined,
    sub_els = 
        [{xmlel,<<"q">>,[{<<"xmlns">>,<<"ns:custom”>>}],[]},
         #stanza_error{
             type = cancel,code = 503,by = <<>>,
             reason = 'service-unavailable',
             text = 
                 #text{lang = <<"en">>,data = <<"User session terminated">>},
             sub_els = []}],
    meta = #{}}

ejabberd.yml

###.  ============
###'  SHAPER RULES

shaper_rules:
  ## Maximum number of offline messages that users can have:
  max_user_offline_messages:
    - 5000: admin
    - 100

###.  =======
###'  MODULES

##
## Modules enabled in all ejabberd virtual hosts.
##
modules:
 mod_offline:
    db_type: sql
    access_max_user_messages: max_user_offline_messages
    store_empty_body: unless_chat_state

Хотя мне не нужно, чтобы эти автономные сообщения доставлялись идеально, но я склоняюсь к мысли, что может ли это быть причиной остановки моего таймера (но я не могу понять, почему он останавливается только тогда, когда пользователь, создавший комната отключается и возвращается, а почему бы и нет, когда это делает другой пользователь?).

Почему этот таймер останавливается и как я могу периодически запускать его?


person abhishek ranjan    schedule 21.08.2018    source источник
comment
Сколько у вас онлайн-пользователей?   -  person Pouriya    schedule 21.08.2018
comment
Я нахожусь только на этапе разработки, поэтому только 2 или 4 пользователя, т.е. когда 2 пользователя там есть один чат, а когда 4 есть два чата. Но проблема остается одинаковой как в 1 чате, так и в 2 отдельных чатах.   -  person abhishek ranjan    schedule 22.08.2018


Ответы (2)


Это упоминается в самом низу документации для модуля timer:

Интервальный таймер, то есть таймер, созданный путем оценки любой из функций apply_interval/4, send_interval/3 и send_interval/2, связан с процессом, для которого таймер выполняет свою задачу.

Таким образом, timer:apply_interval связывает сервер таймера с процессом, который запустил таймер, и таймер будет отменен, когда вызывающий процесс завершится.

По-видимому, таймер создается из процесса, который управляет подключением пользователя, поэтому, когда пользователь отключается, таймер автоматически отменяется.

Вы можете обойти это, создав длительный процесс, который управляет этим таймером.


Несвязанная проблема со стилем: в Erlang case обычно понятнее, чем if. Этот фрагмент кода:

 {Timer_Result,Ref_or_Reason} = timer:apply_interval(30000, ?MODULE, func(), [Arguments]),
 if
    (Timer_Result == ok)->
       ets:insert(ref_table, {Key, Ref_or_Reason});
    (Timer_Result == error)->
       io:format(" Could not delete user after timeout, Reason is ~p~n",[Ref_or_Reason])
 end;

может быть записано как:

 case timer:apply_interval(30000, ?MODULE, func(), [Arguments]) of
    {ok, Ref} ->
       ets:insert(ref_table, {Key, Ref});
    {error, Reason} ->
       io:format(" Could not delete user after timeout, Reason is ~p~n",[Reason])
 end;
person legoscia    schedule 22.08.2018
comment
да, @legoscia, я тоже склонялся к этому, чтобы быть причиной. Выполню ваше предложение и посмотрю, сработает ли оно. Также спасибо за указание на использование case вместо if. - person abhishek ranjan; 23.08.2018

Я рекомендую прочитать код mod_muc_room.erl и использовать хуки muc. Например, вы можете получать уведомления, когда пользователь отправляет сообщение только в чате (крюк muc_filter_message) или когда пользователь отправляет сообщение о присутствии (присоединиться, выйти и т. д.) в чате (хук muc_filter_presence). Лучше иметь один процесс для обработки таймеров (например, mod_ping). Но для больших масштабов вы должны использовать хуки ejabberd_c2s c2s_handle_info и c2s_terminate для управления таймерами. Также я рекомендую перейти на Ejabberd 18.06 или хотя бы 17.11 из-за этого.

person Pouriya    schedule 22.08.2018
comment
Спасибо @Pouriya за такое указание на разнообразный набор крючков. Я думаю, что без причины я ограничился только несколькими крючками, которые теперь вызывают проблемы. Постараюсь использовать подходящие крючки в подходящих местах. Большое спасибо. - person abhishek ranjan; 23.08.2018