用户离线时,Timer:apply_interval / 4停止,并在ejabberd中再次返回

时间:2018-08-21 06:51:50

标签: android erlang ejabberd smack ejabberd-hooks

我正在从Linux机器上的源代码使用ejabberd-17.03。

我使用用户A的jid从服务器上以编程方式创建了一个临时聊天室,并将直接邀请发送给用户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,并看到没有发送给脱机和再次在线用户的脱机消息。即使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 个答案:

答案 0 :(得分:1)

This is mentioned at the very bottom of the documentation for the timer module:

An interval timer, that is, a timer created by evaluating any of the functions apply_interval/4, send_interval/3, and send_interval/2 is linked to the process to which the timer performs its task.

So timer:apply_interval links the timer server to the process that started the timer, and the timer will be cancelled when the calling process exits.

Apparently the timer gets created from the process that manages the user's connection, so when the user disconnects, the timer is cancelled automatically.

You could work around this by spawning a long-running process that manages this timer.


An unrelated style issue: in Erlang, case is usually clearer than if. This piece of code:

 {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;

could be written as:

 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;

答案 1 :(得分:1)

我建议阅读mod_muc_room.erl代码并也使用muc的钩子。例如,当用户仅在聊天室中发送消息时(muc_filter_message挂钩),或者当用户在聊天室中发送在线状态(加入,离开等)(muc_filter_presence挂钩)时,您会收到通知。最好有一个进程来处理计时器(例如mod_ping)。但是对于大规模而言,您必须使用ejabberd_c2s的c2s_handle_info和c2s_terminate挂钩来管理计时器。另外,由于this,我还建议升级到Ejabberd 18.06或至少17.11。