Smack 4.4.7: Sending of muc#roomconfig form always failed with NoResponseException even the server reply within ms after IQ-set is received

During aTalk testing, it is found that sending of muc#roomconfig FORM stanza always failed with NoResponseException even the xmpp server replied IQ-result within ms of the IQ-set received.
The test carried out are on the same home network using WiFi.

Below is a log info captured of the whole IQ exchanges process on aTalk and XMPP ejabberd server.
It is seen that the IQ-result received by aTalk always appear only after the NoResponseException; independent of the Smack Reply Timer set e.g. 5-Sec, 10-Sec and 30-Sec.

From ejabberd log, the xmpp server actually returns IQ-result within ms of the received IQ-set; and is well within the smack timeout i.e.: server sends on 07:50:52.217… but aTalk smack timeout on 07:51:03.596.

Note there is time lead of ejabberd on aTalk in IQ send and receive by ejabberd server ~0.6s;
(07:50:53.591 - 07:50:52.203…); likely due to logcat message display delay on aTalk. Both aTalk and server clocks are synced with internet time.

Any comment?

// ============= aTalk logcat info ============ //
07:50:53.581  D  Sending room config form!!!
07:50:53.591  D  SENT (1): 
                 <iq to='chatroom3@conference.atalk.sytes.net' id='XY69C-37' type='set'>
                   <query xmlns='http://jabber.org/protocol/muc#owner'>
                     <x xmlns='jabber:x:data' type='submit'>
                       <field var='FORM_TYPE'>
                         <value>
                           http://jabber.org/protocol/muc#roomconfig
                         </value>
                       </field>
                     </x>
                   </query>
                 </iq>
                 <r xmlns='urn:xmpp:sm:3'/>
                 
07:51:03.596  E  Failed to send config form.
                 org.jivesoftware.smack.SmackException$NoResponseException: No response received within reply timeout. Timeout was 10000ms (~10s). StanzaCollector has been cancelled. Waited for response using: IQReplyFilter: iqAndIdFilter (AndFilter: (OrFilter: (IQTypeFilter: type=error, IQTypeFilter: type=result), StanzaIdFilter: id=XY69C-37)), : fromFilter (OrFilter: (FromMatchesFilter (full): chatroom3@conference.atalk.sytes.net)).
                 	at org.jivesoftware.smack.StanzaCollector.nextResultOrThrow(StanzaCollector.java:281)
                 	at org.jivesoftware.smack.StanzaCollector.nextResultOrThrow(StanzaCollector.java:228)
                 	at org.jivesoftware.smackx.muc.MultiUserChat.sendConfigurationForm(MultiUserChat.java:873)
                 	at net.java.sip.communicator.impl.protocol.jabber.ChatRoomJabberImpl$ParticipantListener.processOwnPresence(ChatRoomJabberImpl.java:2771)
                 	at net.java.sip.communicator.impl.protocol.jabber.ChatRoomJabberImpl$ParticipantListener.processPresence(ChatRoomJabberImpl.java:2744)
                 	at org.jivesoftware.smackx.muc.MultiUserChat$3.processStanza(MultiUserChat.java:309)
                 	at org.jivesoftware.smack.AbstractXMPPConnection.lambda$invokeStanzaCollectorsAndNotifyRecvListeners$8(AbstractXMPPConnection.java:1619)
                 	at org.jivesoftware.smack.AbstractXMPPConnection$$ExternalSyntheticLambda3.run(Unknown Source:6)
                 	at org.jivesoftware.smack.AbstractXMPPConnection$10.run(AbstractXMPPConnection.java:2149)
                 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
                 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
                 	at java.lang.Thread.run(Thread.java:920)
                 	
07:51:03.667  D  RECV (1): 
                 <iq xml:lang='en-US' to='swordfish@atalk.sytes.net/atalk' from='chatroom3@conference.atalk.sytes.net' type='result' id='XY69C-37'/>                 	


// ============= ejabberd log info ============ //
07:50:52.203612+08:00 [notice] (tls|<0.624.0>) Received XML on stream = "<iq to='chatroom3@conference.atalk.sytes.net' id='XY69C-37' type='set'><query xmlns='http://jabber.org/protocol/muc#owner'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE'><value>http://jabber.org/protocol/muc#roomconfig</value></field></x></query></iq><r xmlns='urn:xmpp:sm:3'/>"

07:50:52.217373+08:00 [notice] (tls|<0.624.0>) Send XML on stream = "<iq xml:lang='en-US' to='swordfish@atalk.sytes.net/atalk' from='chatroom3@conference.atalk.sytes.net' type='result' id='XY69C-37'/>"

Found the following change fixes the problem.

Early on, the sending of muc#roomconfig FORM is processed within the
PresenceListener#processOwnPresence(Presence presence) using the same thread. This seems to cause NoResponseException.

After moving and execute the muc#roomconfig FORM sending in a new Thread() as shown in the code below, the problem is resolved.

        /**
         * Processes a <code>Presence</code> packet addressed to our own occupant JID.
         * with either MUCUser extension or MUCInitialPresence extension (error)
         *
         * @param presence the packet to process.
         */
        private void processOwnPresence(Presence presence) {
            MUCUser mucUser = presence.getExtension(MUCUser.class);
            if (mucUser != null) {
                // lastPresenceSent = presence;
                MUCAffiliation affiliation = mucUser.getItem().getAffiliation();
                MUCRole role = mucUser.getItem().getRole();

                // if status 201 is available means that room is created and locked till we send the configuration
                if ((mucUser.getStatus() != null)
                        && mucUser.getStatus().contains(MUCUser.Status.ROOM_CREATED_201)) {
                    new Thread() {
                        @Override
                        public void run() {
                            try {
                                TextSingleFormField formField
                                        = FormField.buildHiddenFormType("http://jabber.org/protocol/muc#roomconfig");
                                DataForm dataForm = DataForm.builder(DataForm.Type.form).addField(formField).build();
                                mMultiUserChat.sendConfigurationForm(new FillableForm(dataForm));

                                // Sending null also picked up the options OperationSetMultiUserChatJabberImpl#createChatRoom and sent
                                // mMultiUserChat.sendConfigurationForm(null);
                            } catch (XMPPException | NoResponseException | NotConnectedException |
                                     InterruptedException e) {
                                Timber.e(e, "Failed to send config form.");
                            }
                        }
                    }.start();

                    // Update mNickName here as it is used in fireLocalUserRoleEvent before joinAs() is triggered.
                    EntityFullJid from = presence.getFrom().asEntityFullJidIfPossible();
                    mNickName = from.getResourceOrEmpty();

                    opSetMuc.addSmackInvitationRejectionListener(mMultiUserChat, ChatRoomJabberImpl.this);
                    if (affiliation == MUCAffiliation.owner) {
                        setLocalUserRole(ChatRoomMemberRole.OWNER, true);
                    }
                    else {
                        setLocalUserRole(ChatRoomMemberRole.MODERATOR, true);
                    }
                }
                else {
                    // this is the presence for our own initial mRole and affiliation,
                    // as smack do not fire any initial events lets check it and fire events
                    if (role == MUCRole.moderator
                            || affiliation == MUCAffiliation.owner
                            || affiliation == MUCAffiliation.admin) {
                        ChatRoomMemberRole scRole = ChatRoomJabberImpl.smackRoleToScRole(role, affiliation);
                        setLocalUserRole(scRole, true);
                    }

                    if (!presence.isAvailable() && role == MUCRole.none
                            && affiliation == MUCAffiliation.none) {
                        Destroy destroy = mucUser.getDestroy();

                        // the room is unavailable to us, there is no message we will just leave
                        if (destroy == null) {
                            leave();
                        }
                        else {
                            leave(destroy.getJid(), aTalkApp.getResString(R.string.service_gui_CHATROOM_DESTROY_MESSAGE,
                                    destroy.getReason()));
                        }
                    }
                }
            }
            // Check for presence error (which use MUCInitialPresence extension)
            else if (Presence.Type.error == presence.getType()) {
                String errMessage = presence.getError().toString();
                addMessage(errMessage, ChatMessage.MESSAGE_ERROR);
            }
        }

This is basically the same issue as in Thread stuck in MultiUserChat.enter() forever - #15 by Flow

You are performing a blocking operation in a synchronous listener, which creates a deadlock that is resolved once the blocking operation times out.

Probably somewhere at

             at net.java.sip.communicator.impl.protocol.jabber.ChatRoomJabberImpl$ParticipantListener.processOwnPresence(ChatRoomJabberImpl.java:2771)
             at net.java.sip.communicator.impl.protocol.jabber.ChatRoomJabberImpl$ParticipantListener.processPresence(ChatRoomJabberImpl.java:2744)

which is within the listener, the room join should be decoupled from the listener’s control flow. Alternatively, use an asynchronous listener.

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.