Таймер:apply_interval/4 останавливается, когда пользователь выходит из oflline и снова возвращается в 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.Чат создан, и обмен сообщениями начинается между А и В, и в этот момент также запускается таймер.

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

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

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

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

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

Журнал:

#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

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

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

2 ответа

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

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

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

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

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


Несвязанная проблема стиля: в Эрланге, 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;

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

Другие вопросы по тегам