【问题标题】:Timer:apply_interval/4 stops when user goes oflline and comes Back again in ejabberdTimer:apply_interval/4 当用户离线并在 ejabberd 中再次返回时停止
【发布时间】:2019-01-27 07:33:01
【问题描述】:

我在 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.Chatroom 被创建,A 和 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 或 4 个用户,即当有 2 个用户时有一个聊天室,当有 4 个用户时有两个聊天室。但问题在 1 个聊天室或 2 个单独的聊天室中仍然存在。

标签: android erlang ejabberd smack ejabberd-hooks


【解决方案1】:

这在documentation for the timer module的最底部提到:

间隔计时器,即通过评估任何函数apply_interval/4send_interval/3send_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;

【讨论】:

  • 是的@legoscia 我也倾向于这就是原因。将实施您的建议,看看它是否有效。还要感谢您指出使用 case 而不是 if 子句。
【解决方案2】:

我建议阅读 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。

【讨论】:

  • 感谢@Pouriya 指出各种各样的钩子。我认为我无缘无故地限制了自己只有几个现在引起问题的钩子。将尝试在合适的地方使用 apt 钩子.非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 2014-05-26
  • 2021-09-20
  • 1970-01-01
  • 2016-07-04
  • 2019-07-20
相关资源
最近更新 更多