9

When using C# NamedPipeServerStream, in case a client doesn't send any message-end-pattern (like \r\n when server reads with ReadLine()) NamedPipeServerStream Read methods will wait forever and no Abort() or Interupt() methods will work on that thread.

Since:
1) Stream.ReadTimeout not supported for NamedPipeServerStream
2) Abort() or Interupt() doesn't work on thread
3) NamedPipeServerStream.Disconnect() nether work
It is unclear, how to setup timeout on NamedPipeServerStream read operations?


Let me introduce an example. The specification of IPC we have require an exchange of \0-terminated strings. A client sends message, the server processes the message and as 'a must' sends a response. If the client doesn't send \0 in the end (client is not ours so we can't guarantee correctness of its working), the Read method will wait forever and client (since we don't control it) may wait forever for a response too.

Next is a simplified example of an implementation:

    public void RestartServer()
    {
        _pipeServerThread.Interrupt();  //doesn't affect Read wait
        _pipeServerThread.Abort();      //doesn't affect Read wait
    }

    private void PipeServerRun(object o) //runs on _pipeServerThread
    {
        _pipeServer = new NamedPipeServerStream(_pipeName, InOut, 100,
                      PipeTransmissionMode.Message, PipeOptions.WriteThrough);
        //_pipeServer.ReadTimeout = 100; //System.InvalidOperationException: Timeouts are not supporte d on this stream.

        // Wait for a client to connect
        while (true)
        {
            _pipeServer.WaitForConnection();
            string request = ReadPipeString();
            //... process request, send response and disconnect
        }
    }

    /// <summary>
    /// Read a \0 terminated string from the pipe
    /// </summary>
    private string ReadPipeString()
    {
        StringBuilder builder = new StringBuilder();
        var streamReader = new StreamReader(_pipeServer);

        while (true)
        {
            //read next byte 
            char[] chars = new char[1];
            streamReader.Read(chars, 0, 1); // <- This will wait forever if no \0 and no more data from client

            if (chars[0] == '\0') return builder.ToString();
            builder.Append(chars[0]);
        }
    }

So how to set timeout on NamedPipeServerStream read operations?

MajesticRa
  • 13,770
  • 12
  • 63
  • 77

2 Answers2

4

Since you are running the pipe in message mode, you should first read the whole message into a byte[] buffer or a memory stream and then decide whether it's valid and decode it. Pipe messages have a definite length. It cannot be retrieved explicitly, but it shows up when you are reading from a message-mode pipe. Win32 ReadFile fails with ERROR_MORE_DATA if there still are unread bytes in the message, then it returns TRUE to indicate that the message is over. After this, a call to ReadFile will block until a new message is available. StreamReader naturally doesn't know any of this and blocks your thread.

Update: to implement timeouts, use asynchronous I/O (Stream.BeginRead). StreamReader does not support this directly. If you absolutely must use it, write a wrapper stream which will implement Read in terms of BeginRead on the underlying stream and support timeouts, cancellation etc.

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
  • Thank you, but unfortunatelly it is not the answer to my question. C# NamedPipeServerStream has IsMessageComplete flag which is, I believe, relies on ERROR_MORE_DATA staff. But it happens to be set to true prior of real end of message. It was one of the first we check. If client uses TransactNamedPipe looks like it works, but when one use C# NamedPipeClient or Write function in WinApi - IsMessageComplete can be set to true prior message complete. I think it is, once again, because of Stream nature of sender. – MajesticRa Jul 20 '11 at 07:46
  • Yes, it is but then why run the pipe in message mode at all? In message mode, a pipe message is created for every Win32 `Write` call. If your clients aren't adhering to this convention, message mode is pointless. (By the way, do the clients actually set the client end of the pipe to message mode?) – Anton Tykhyy Jul 20 '11 at 08:03
  • 1
    Unfortunately the world isn't perfect and I just have the specification. Our test client is in message mode. But we can't guarantee this about existing clients. Looks like there is a problem with BeginRead too, but I still experiment with it. – MajesticRa Jul 22 '11 at 20:11
  • As I see it, the 'messages' your spec talks about aren't 'pipe messages' (i.a., there would have been no need for the `\0` terminator otherwise), so I'd try running the pipe server in byte mode. As for reading operations, if the `BeginRead` approach doesn't work out try `PeekNamedPipe`. – Anton Tykhyy Jul 23 '11 at 08:07
1

Try setting NamedPipeServerStream.ReadMode and/or .TransmissionMode to Byte. Regardless of these you should use the available BeginRead / EndRead methods with NamedPipeServerStream. This way you can implement the timeout logic yourself.

Yahia
  • 69,653
  • 9
  • 115
  • 144