C# – NamedPipeServerStream/async reliable disconnect issues

cnamed-pipes

We're using named pipes to communicate between a C# .Net service and a native C++ application. The service creates a message mode pipe, then kicks off a timer.

     m_pipeServer = new NamedPipeServerStream ("Cyber_Srv_EventPipe",
                                             PipeDirection.InOut,
                                             1,
                                             PipeTransmissionMode.Message,
                                             PipeOptions.Asynchronous,
                                             4096,
                                             4096,
                                             pipeSa);
     m_OutputQueue = new List<My_Message>();

In the timer tick routine is the main service loop, which looks like this:

     do
     {
        if (!m_bClientAttached)
        {
           try
           {
              m_pipeServer.WaitForConnection ();
              m_bClientAttached = true;
           }
           catch (InvalidOperationException invope)
           {
              sDebug = string.Format ("Pipe wait exception InvOpEx: {0}",
                                      invope.Message);
              DebugMessage (sDebug);
           }
        }

        // the message-pumping part of the loop. 

        if (m_bClientAttached)
        {
           try
           {
              if (!m_bReadInProgress)
              {
                 m_bReadInProgress = true;
                 m_pipeServer.BeginRead (byNewRead, 0, byNewRead.GetLength (0),
                                       new AsyncCallback (this.PipeReadComplete),
                                       m_iReadCount);
              }

              if (m_OutputQueue.Count () > 0)
              {
                 if (!m_bWriteInProgress)
                 {
                    m_bWriteInProgress = true;
                    My_Message opmsg = m_OutputQueue.ElementAt (0);
                    m_pipeServer.BeginWrite (opmsg.ToByteArray (), 0,
                                             (int)(opmsg.MsgLength),
                                             new AsyncCallback (this.PipeWriteComplete),
                                             m_iWriteCount);
                 }
              }
           }
           catch (IOException ioe)
           {
              sDebug = string.Format ("Main loop raised exception: {1}",
                                      ioe.Message);
              DebugMessage (sDebug);
              DetachClientPipe();
           }
           Thread.Sleep(1);
        }

     } while (m_bRunning);

     m_pipeServer.Close ();
  }

The read and write completion routines look like this:

  private void PipeReadComplete (IAsyncResult iAR)
  {
     string sDebug;
     int iByteCount;
     My_Message ipmsg = new My_Message();
     try
     {
        iByteCount = m_pipeServer.EndRead (iAR);
        if (iByteCount > 0) 
        {
           ipmsg.FromByteArray(byNewRead);
           m_bReadInProgress = false;
           ... process message ...
        }
        else
        {
           try
           {
              DebugMessage ("PRC: Zero bytes read, disconnect pipe");
              DetachClientPipe();
           }
           catch (InvalidOperationException invope)
           {
              sDebug = string.Format ("PRC - Pipe disconnect exception: {0}",
                                      invope.Message);
              DebugMessage (sDebug);
           }
        }
     }
     catch (IOException e)
     {
        sDebug = string.Format ("PRC: Read {0} raised exception: {1}",
                                (int)(iAR.AsyncState),
                                e.Message);
        DebugMessage (sDebug);
        DetachClientPipe();
     }
  }

  // ------------------------------------------------------------------

  private void PipeWriteComplete (IAsyncResult iAR)
  {
     string sDebug;
     try
     {
        m_pipeServer.EndWrite (iAR);
        lock (m_OutputQueue)
        {
           m_OutputQueue.RemoveAt(0);
        }
        m_bWriteInProgress = false;
     }
     catch (IOException e)
     {
        sDebug = string.Format ("Write {0} raised exception: {1}",
                                (int)(iAR.AsyncState),
                                e.Message);
        DebugMessage (sDebug);
        DetachClientPipe();
     }
  }

  // ------------------------------------------------------------------

  private void DetachClientPipe()
  {
     if (m_pipeServer.IsConnected)
     {
        m_pipeServer.Disconnect();
     }
     m_bClientAttached = false;
  }

The client side code is known good code, re-used. So here's the problem. The client can connect fine. We then shut down the client, everything is fine. We start it up and conect again. All fine, then we close it and start it again. Boom – error 231, pipe busy. the server will now generate pipe busy errors on any connection attempt until hell freezes over, or we restart the service. Then we're back to two connections again.

I've been staring at this code for three straight days now, and I have no idea why it does this. I can't seem to see the wood for the trees, and I could use a fresh pair of eyes or three. Problem is no-one else in the team knows much of any C#.

Update

The reason this fails on the third connect attempt appears to be that on the first disconnect the PipeReadComplete returns and I get zero bytes read, so I detach the pipe and all is well. BUT… on the second disconnection, PipeReadComplete does NOT get called, so I don't force the disconnect. Weird.

Best Answer

Bob, for a quick fix: was wondering, have you tried setting the server instance parameter to more than 1 and see if it still fails after 2 tries? Instead of 1, put 10 and see if it will help things. Also, it will help if you post the unmanaged code as well. I'm currently doing the same thing, windows service plus unmanaged dll IPC.

    m_pipeServer = new NamedPipeServerStream  ("Cyber_Srv_EventPipe",    
                                         PipeDirection.InOut,              
                                         10,
                                         PipeTransmissionMode.Message,  
                                         PipeOptions.Asynchronous,                                                 
                                         4096,
                                         4096,
                                         pipeSa);       

Or you actually need to have only one server pipe instance at all times?