Why am I getting this result (the same thread enters twice before exiting)?

I inherited a socket callback routine that I am trying to debug. It performs asynchronous TCP communication with EndReceive (), immediately followed by BeginReceve (), so it always listens. It is populated with “when hit” breakpoints that display a message and continue diagnostics.

Since this is an asynchronous callback, it can be called multiple times by the system for a single BeginReceive (), even if it is in the middle of processing a previous callback. This is usually on a new thread, so I have a lock (critical section) for everything. But sometimes it seems that it returns to the same stream even before it exits. How is this possible? What am I doing wrong?

He's my trail (using "When Hit" breakpoints).

Socket_DataArrival() – Entering … managedThreadID=11
Socket_DataArrival() - first statement in lock . . . managedThreadID=11
Socket_DataArrival() - EndReceive() ... managedThreadID=11
Socket_DataArrival() - BeginReceive() . . . managedThreadID=11
Socket_DataArrival() – Entering … managedThreadID=11
Socket_DataArrival() - first statement in lock . . . managedThreadID=11
Socket_DataArrival() - EndReceive() ... managedThreadID=11
Socket_DataArrival() - BeginReceive() . . . managedThreadID=11
Socket_DataArrival() - exiting at end of routine . . . managedThreadID=11
Socket_DataArrival() - exiting at end of routine . . . managedThreadID=11

      

and here's a subroutine (proprietary code commented out) ...

   private void Socket_DataArrival(IAsyncResult ar)
   {
       StateObject stateObject;
       int bytesReceived;
       int managedThreadId = Thread.CurrentThread.ManagedThreadId;

       lock (inputLock)
       {                 // "Entering..."
           try
           {
               _Receiving = false;  //"first statement in lock"
               stateObject = (StateObject)ar.AsyncState;
               bytesReceived = stateObject.sSocket.EndReceive(ar);    
               _Receiving = true;

               _StateObject = new StateObject(2048, _TCPConn);  //2048 = arbitrary number
               _StateObject.tag = "Socket_DataArrival ";

               _TCPConn.BeginReceive(
                    _StateObject.sBuffer,
                    0,
                    _StateObject.sBuffer.Length,
                    SocketFlags.None,
                    new AsyncCallback(Socket_DataArrival),
                    _StateObject);

            }
            catch (Exception exc)
            {
                subs.LogException("Socket_DataArrival", exc);
            }

            // proprietary stuff goes here

        }  // end critical section
        return;
    }

      

inputLock is defined at the class level as.,

private Object inputLock = new Object();

      

How can the same thread enter it a second time before it exits the first time?

+3


source to share


3 answers


When you call BeginReceive (), you resolve the next callback (on that thread). Place BeginReceive after the lock is released.



+1


source


It looks like when you call BeginReceive()

and the data is already available, you immediately get a callback and on the thread called BeginReceive()

.

This sounds perfectly plausible, given that the underlying I / O model is almost guaranteed to be both an I / O completion port and operating systems after Windows XP (so all currently supported operating systems) you can tell IOCPs to "skip completion port processing on success" and instead immediately return the asynchronous call data to the caller.

So my guess is that this means what BeginReceive()

is calling in WSARecv()

, and that completes immediately because the data is available and so the calling code executes the callback immediately on the calling thread. If there was no data, then it WSARecv()

will return IO_PENDING

, and the I / O will eventually complete (when the data arrives), and one of the threads that is associated with the IOCP socket will then handle the completion and call the handler.



Some data flow patterns are more likely to cause this than others, and of course depend on the network and how the data flows. This also happens much more frequently with asynchronous transfers ...

The best way to fix this is to force the I / O handlers to simply place all completions on a queue, which can only be processed by one thread, and which cannot be processed recursively. Then that leaves you with a design where this problem cannot happen. I wrote about such a project here for the ACCU Overload post.

+3


source


A lock

only prevents another thread from entering the critical section, not the same one.

Since you are calling _TCPConn.BeginReceive

internally Socket_DataArrival

with the same callback (as well Socket_DataArrival

), this callback is simply injected again to get the data from _TCPConn

.

Try using a different callback for _TCPConn

or try calling BeginReceive()

outside Socket_DataArrival()

.

+2


source







All Articles