0
votes

I am using ejabberd-17.03 from source code on linux machine.

I created a temporary chatroom programatically from the the server using userA’s jid and send direct invitation to user B which he accepts and join the chatroom.

My use case is that two users A and B are in a chatroom exchange messages. If no user sends the other user any message for 30 seconds then the room sends a randomly selected message to these two users.

I have implemented this as follows:

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

Now everything is working fine except the following case:

1.Chatroom is created and exchange of messages starts between A and B and the timer also starts at this point.

  1. If the user who created the chat room goes offline say at time T(by minimising app and killing it from an android device) and comes back online ,the timer stop, as in the function scheduled to be called at the end of 30 seconds doesn’t gets called.(here comes back online is highlighted because if the user doesn’t comes online the timer works as expected, it is only when the user comes online again , the timer stops and no logs are generated).

    But if online user sends any message now at this point at T then the whole periodic operation of randomly selecting messages and sending them to clients starts again pretty well and ends well.

    But if the online user doesn’t send any message at this point of time T then the timer scheduled never gets called, the the user is kept on waiting.

  2. If the user who got invited to the chatroom goes offline at time T2(say way as in 2) and comes online again then the timer remains active and works as expected.

So I changed the logging level of ejabberd to 5 and saw that offline messages to the offline and online again user is not getting delivered.even though mod_offline is enabled in ejabberd.yml.

Log :

#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

Although I don’t need these offline messages to be delivered perfectly but I am getting tilted towards the thought that whether it can be the reason for stopping my timer(But I can’t understand that why does it only stops when the user who created the room goes offline and comes back and why not when the other user does this?).

Why is this timer getting stopped and how can I keep it running periodically?

2
How many online users you have?Pouriya
I am in the development phase only, so Only 2 or 4 users i.e when 2 users are there there is one chatroom and when 4 are there two chatrooms are there. But the problem remains the same in either 1 chatroom or 2 individual chatrooms.abhishek ranjan

2 Answers

1
votes

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
votes

I recommend to read the mod_muc_room.erl code and use muc's hooks too. For example you can be notified when user sends message only in chatroom (muc_filter_message hook) or when user sends presence (join, leave, etc) in chatroom (muc_filter_presence hook). It's better to have one process for handling timers (like mod_ping). But for large scale you have to use ejabberd_c2s's c2s_handle_info and c2s_terminate hooks for managing timers. Also I recommend to upgrade to Ejabberd 18.06 or at least 17.11 because of this.