Not logged in. · Lost password · Register
Forum: agsXMPP RSS
m_PendingSend in ClientSocket.cs not being reset
Avatar
qbert #1
Member since Oct 2008 · 1 post
Group memberships: Members
Show profile · Link to this post
Subject: ClientSocket does not properly clean up in #Disconnect()
I was writing a client that needed to maintain a constant connection to the jabber server. To do this I detect when there is a disconnect event, or no current connection, etc. and reconnect. Before connecting I would call XmppClientConnection#Close followed by XmppClientConnection#SocketDisconnect. I discovered that this would not work. I first thought it was an error with my code so I began to only call XmppClientConnection#Close when the XmppState was Connected or SessionStarted. However, this resulted in the client sometimes unable to regain a connection.

I went back to my initial problem and found that calling XmppClientConnection#Close before the XmppClientConnection#Open would result in never reaching the XmppState.SessionStarted and it would stay stuck in the Connected state.

I traced the through what happens when you call the XmppClientConnection#Close method. It’s a call to the base method XmppConnection#Close. Which calls XmppConnection#Send. This results in calling XmppConnection#m_ClientSocket#Send. For an XmppClientConnection this is a ClientSocket object. This is where it gets interesting.

~ line 554 of ClientSocket.cs

                    if (m_PendingSend)
                    {
                        m_SendQueue.Enqueue(bData);                       
                    }
                    else
                    {
                        m_PendingSend = true;                       
                        try
                        {
                            m_NetworkStream.BeginWrite(bData, 0, bData.Length, new AsyncCallback(EndSend), null);
                        }
                        catch(Exception ex)
                        {
                            Disconnect();
                        }
                    }

On the send, if not set, m_PendingSend is set true then the send is attempted. If there is an exception, such as the socket isn’t connected, or any other error then ClientSocket#Disconnect() is called. So if you haven’t called ClientSocket#Connect yet (null exception) or if the socket is disconnect or otherwise bad (IOException) m_PendingSend will continue to be set true. Then when you subsequently call ClientSocket#Connect to reconnect it creates a new socket but m_PendingSend hasn’t been reset. So then when XmppClientConnection attempts to initialize the stream no data is ever sent to the server.
A fix is therefore needed in the ClientSocket#Disconnect function.

        public override void Disconnect()
        {
            base.Disconnect();

            lock (this)
            {
                m_PendingSend = false;
                m_SendQueue.Clear();
            }

            // return right away if have not created socket
            if (_socket == null)
                return;

            try
            {
                // first, shutdown the socket
                _socket.Shutdown(SocketShutdown.Both);
            }
            catch {}
           
            try
            {
                // next, close the socket which terminates any pending
                // async operations
                _socket.Close();
            }
            catch {}

      FireOnDisconnect();           
        }

The new code is right after the base.Disconnect() and before the first if statement. This resets m_PendingSend and clears the send queue readying the class for the next Connect call.

TEST PROGRAM:

Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

namespace ReconnectProblem
{
    class Program
    {
        private static StreamWriter str;
        static void Main(string[] args)
        {

            str = new StreamWriter(new FileStream("log_" + System.Diagnostics.Process.GetCurrentProcess().Id + ".txt",
                                            FileMode.OpenOrCreate, FileAccess.Write));

            Program.log("Type 'exit' to exit, 'con' to connect, and 'dis' to disconnect");

            JabberClient c = new JabberClient();

            string line;
            while(true)
            {
                line = Console.ReadLine();

                switch(line)
                {
                    case "exit":
                        Program.log("Exiting...");
                        return;
                        break;
                    case "con":
                            c.Connect();
                        break;
                    case "dis":
                            c.Disconnect();
                        break;
                    default:
                        break;
                }
            }

           
           
        }

        public static void log(string line)
        {
            Console.WriteLine(line);
            str.WriteLine(line);
            str.Flush();
        }
    }

JabberClient.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using agsXMPP;


namespace ReconnectProblem
{
  internal class JabberClient
  {
    protected agsXMPP.XmppClientConnection clientConnection = null;
      protected int connecting = 0;

    public JabberClient()
    {
      clientConnection = new agsXMPP.XmppClientConnection();

      clientConnection.OnLogin += clientConnection_OnLogin;
      clientConnection.OnClose += clientConnection_OnClose;
      clientConnection.OnError += clientConnection_OnError;
            clientConnection.OnMessage += clientConnection_OnMessage;
      clientConnection.OnRosterEnd += clientConnection_OnRosterEnd;
      clientConnection.OnReadXml += clientConnection_OnReadXml;
      clientConnection.OnWriteXml += clientConnection_OnWriteXml;

            clientConnection.ClientSocket.OnConnect += ClientSocket_OnConnect;
            clientConnection.OnSocketError += clientConnection_OnSocketError;
            clientConnection.OnAuthError += clientConnection_OnAuthError;
            clientConnection.OnXmppError += clientConnection_OnXmppError;
        clientConnection.OnXmppConnectionStateChanged += clientConnection_OnXmppConnectionStateChanged;
    }

        void clientConnection_OnXmppError(object sender, agsXMPP.Xml.Dom.Element e)
        {
            Program.log("Jabber: OnXmppError: " + e);
        }

        void clientConnection_OnXmppConnectionStateChanged(object sender, XmppConnectionState state)
        {
            Program.log("Jabber: OnXmppConnectionStateChanged: " + state);
        }

      public bool IsConnected
    {
      get
      {
        Program.log("Jabber: state: " + this.clientConnection.XmppConnectionState);
        if (clientConnection.XmppConnectionState == agsXMPP.XmppConnectionState.SessionStarted) {
          return true;
        }
        else {
          return false;
        }
      }
    }

    public void Connect()
    {
      if(!IsConnected && Interlocked.CompareExchange(ref connecting, 1, 0) == 0) {
                Program.log("Jabber Connect(): Connecting");
                Disconnect();
        clientConnection.Server = "grasshopper";
        clientConnection.Username = "test";
        clientConnection.Password = "test";
                clientConnection.Port = 5222;
                clientConnection.Resource = "test";
        clientConnection.ConnectServer = "192.168.1.11";

        clientConnection.Open();

        Interlocked.Exchange(ref connecting, 0); // reset
      }
      else {
        Program.log("Jabber Connect(): Already connected or already connecting");
      }
    }

    public void Disconnect()
    {
        Program.log("Jabber: Disconnect()");
        try
        {
                Program.log("Jabber: Disconnect(): Close()");
            clientConnection.Close();
        }
        catch (Exception e)
        {
                Program.log("Jabber: Close(): Exception: " + e.Message);
        }

            try
            {
                Program.log("Jabber: Disconnect(): SocketDisconnect()");
                clientConnection.SocketDisconnect();
            } catch(Exception e)
            {
                Program.log("Jabber: ClientSocket.Disconnect(): Exception:" + e.Message);
            }
    }

      public void Send(agsXMPP.Xml.Dom.Element e)
    {
      if (IsConnected) {
        clientConnection.Send(e);
      }
      else {
        Program.log("Jabber: not connected, current state: " + this.clientConnection.XmppConnectionState);
      }
    }

      void ClientSocket_OnConnect(object sender)
      {
          Program.log("Jabber: clientSocket: OnConnect");
      }

      private void clientConnection_OnReadXml(object sender, string xml)
    {
      Program.log("Jabber: OnReadXml: " + xml);
    }

    private void clientConnection_OnWriteXml(object sender, string xml)
    {
      Program.log("Jabber: OnWriteXml: " + xml);
    }

    private void clientConnection_OnMessage(object sender, agsXMPP.protocol.client.Message msg)
    {
      Program.log("Jabber: OnMessage: " + msg.ToString());
    }

    private void clientConnection_OnError(object sender, Exception ex)
    {
      Program.log("Jabber: OnError: " + ex.Message);
    }

    private void clientConnection_OnClose(object sender)
    {
      Program.log("Jabber: OnClosed");
    }

    private void clientConnection_OnLogin(object sender)
    {
      Program.log("Jabber: Logged in");
    }

    private void clientConnection_OnRosterEnd(object sender)
    {
      clientConnection.Show = agsXMPP.protocol.client.ShowType.NONE;
      clientConnection.Status = "Online";
      clientConnection.Priority = 0;
      clientConnection.SendMyPresence();
    }

      void clientConnection_OnAuthError(object sender, agsXMPP.Xml.Dom.Element e)
      {
          Program.log("Jabber: OnAuthError: " + e);
            this.Disconnect();
      }

      void clientConnection_OnSocketError(object sender, Exception ex)
      {
          Program.log("Jabber: OnSocketError: " + ex.Message + ": " + ex.GetBaseException().Message);
      }
  }
}

If you run this code with the current version of the agsXMPP library it will never connect to the jabber server. However, if you add in the fix to the ClientSocket#Disconnect function then it will work as expected.

This was a bit of an exasperating bug to figure out but the fix is quite simple and removes all problems I’ve observed.
Avatar
Alex #2
Member since Feb 2003 · 4297 posts · Location: Germany
Group memberships: Administrators, Members
Show profile · Link to this post
Hello qbert,

thanks for finding this bug, I appreciate your help.

Of course m_PendingSend and the Queue must be reset.
I have commented it in the code and started a discussion in the MSDN Forums:
  1. // .NET 2.0 SSL Stream issues when sending multiple async packets
  2. // http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=124213&SiteID=1
  3. if (m_PendingSend)
  4. {
  5.     m_SendQueue.Enqueue(bData);                       
  6. }

When a client sends data pretty fast then the Async SslStream is raising errors. This is why the data gets queued in this case and sent in the async send callback.
I am not available today and tomorrow. But I will try to fix this in the evenings and commit the code to SVN then.

Thanks,
Alex
Avatar
Alex #3
Member since Feb 2003 · 4297 posts · Location: Germany
Group memberships: Administrators, Members
Show profile · Link to this post
sorry for the delay. Its fixed and in SVN.

Alex
Close Smaller – Larger + Reply to this post:
Verification code: VeriCode Please enter the word from the image into the text field below. (Type the letters only, lower case is okay.)
Smileys: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Special characters:
Forum: agsXMPP RSS