Windows – Duplex named pipe hangs on a certain write

multithreadingnamed-pipeswindows

I have a C++ pipe server app and a C# pipe client app communicating via Windows named pipe (duplex, message mode, wait/blocking in separate read thread).

It all works fine (both sending and receiving data via the pipe) until I try and write to the pipe from the client in response to a forms 'textchanged' event. When I do this, the client hangs on the pipe write call (or flush call if autoflush is off). Breaking into the server app reveals it's also waiting on the pipe ReadFile call and not returning.
I tried running the client write on another thread — same result.

Suspect some sort of deadlock or race condition but can't see where… don't think I'm writing to the pipe simultaneously.

Update1: tried pipes in byte mode instead of message mode – same lockup.

Update2: Strangely, if (and only if) I pump lots of data from the server to the client, it cures the lockup!?

Server code:

DWORD ReadMsg(char* aBuff, int aBuffLen, int& aBytesRead)
{
    DWORD byteCount;
    if (ReadFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
    {
        aBytesRead = (int)byteCount;
        aBuff[byteCount] = 0;
        return ERROR_SUCCESS;
    }

    return GetLastError();  
}

DWORD SendMsg(const char* aBuff, unsigned int aBuffLen)
{
    DWORD byteCount;
    if (WriteFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
    {
        return ERROR_SUCCESS;
    }

    mClientConnected = false;
    return GetLastError();  
}

DWORD CommsThread()
{
    while (1)
    {
        std::string fullPipeName = std::string("\\\\.\\pipe\\") + mPipeName;
        mPipe = CreateNamedPipeA(fullPipeName.c_str(),
                                PIPE_ACCESS_DUPLEX,
                                PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                                PIPE_UNLIMITED_INSTANCES,
                                KTxBuffSize, // output buffer size
                                KRxBuffSize, // input buffer size
                                5000, // client time-out ms
                                NULL); // no security attribute 

        if (mPipe == INVALID_HANDLE_VALUE)
            return 1;

        mClientConnected = ConnectNamedPipe(mPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
        if (!mClientConnected)
            return 1;

        char rxBuff[KRxBuffSize+1];
        DWORD error=0;
        while (mClientConnected)
        {
            Sleep(1);

            int bytesRead = 0;
            error = ReadMsg(rxBuff, KRxBuffSize, bytesRead);
            if (error == ERROR_SUCCESS)
            {
                rxBuff[bytesRead] = 0;  // terminate string.
                if (mMsgCallback && bytesRead>0)
                    mMsgCallback(rxBuff, bytesRead, mCallbackContext);
            }
            else
            {
                mClientConnected = false;
            }
        }

        Close();
        Sleep(1000);
    }

    return 0;
}

client code:

public void Start(string aPipeName)
{
    mPipeName = aPipeName;

    mPipeStream = new NamedPipeClientStream(".", mPipeName, PipeDirection.InOut, PipeOptions.None);

    Console.Write("Attempting to connect to pipe...");
    mPipeStream.Connect();
    Console.WriteLine("Connected to pipe '{0}' ({1} server instances open)", mPipeName, mPipeStream.NumberOfServerInstances);

    mPipeStream.ReadMode = PipeTransmissionMode.Message;
    mPipeWriter = new StreamWriter(mPipeStream);
    mPipeWriter.AutoFlush = true;

    mReadThread = new Thread(new ThreadStart(ReadThread));
    mReadThread.IsBackground = true;
    mReadThread.Start();

    if (mConnectionEventCallback != null)
    {
        mConnectionEventCallback(true);
    }
}

private void ReadThread()
{
    byte[] buffer = new byte[1024 * 400];

    while (true)
    {
        int len = 0;
        do
        {
            len += mPipeStream.Read(buffer, len, buffer.Length);
        } while (len>0 && !mPipeStream.IsMessageComplete);

        if (len==0)
        {
            OnPipeBroken();
            return;
        }

        if (mMessageCallback != null)
        {
            mMessageCallback(buffer, len);
        }

        Thread.Sleep(1);
    }
}

public void Write(string aMsg)
{
    try
    {
        mPipeWriter.Write(aMsg);
        mPipeWriter.Flush();
    }
    catch (Exception)
    {
        OnPipeBroken();
    }
}

Best Answer

If you are using separate threads you will be unable to read from the pipe at the same time you write to it. For example, if you are doing a blocking read from the pipe then a subsequent blocking write (from a different thread) then the write call will wait/block until the read call has completed and in many cases if this is unexpected behavior your program will become deadlocked.

I have not tested overlapped I/O, but it MAY be able to resolve this issue. However, if you are determined to use synchronous calls then the following models below may help you to solve the problem.

Master/Slave

You could implement a master/slave model in which the client or the server is the master and the other end only responds which is generally what you will find the MSDN examples to be.

In some cases you may find this problematic in the event the slave periodically needs to send data to the master. You must either use an external signaling mechanism (outside of the pipe) or have the master periodically query/poll the slave or you can swap the roles where the client is the master and the server is the slave.

Writer/Reader

You could use a writer/reader model where you use two different pipes. However, you must associate those two pipes somehow if you have multiple clients since each pipe will have a different handle. You could do this by having the client send a unique identifier value on connection to each pipe which would then let the server associate the two pipes. This number could be the current system time or even a unique identifier that is global or local.

Threads

If you are determined to use the synchronous API you can use threads with the master/slave model if you do not want to be blocked while waiting for a message on the slave side. You will however want to lock the reader after it reads a message (or encounters the end of a series of message) then write the response (as the slave should) and finally unlock the reader. You can lock and unlock the reader using locking mechanisms that put the thread to sleep as these would be most efficient.

Security Problem With TCP

The loss going with TCP instead of named pipes is also the biggest possible problem. A TCP stream does not contain any security natively. So if security is a concern you will have to implement that and you have the possibility of creating a security hole since you would have to handle authentication yourself. The named pipe can provide security if you properly set the parameters. Also, to note again more clearly: security is no simple matter and generally you will want to use existing facilities that have been designed to provide it.

Related Topic